Compare commits

..

10 commits

49 changed files with 5467 additions and 5791 deletions

1
.envrc
View file

@ -1 +0,0 @@
use flake

15
.gitignore vendored
View file

@ -11,10 +11,11 @@ npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
/.terraform.lock.hcl # Editor directories and files
/.terraform/ .idea
/terraform.tfstate .vscode
/terraform.tfstate.* *.suo
*.ntvs*
# Nix *.njsproj
/.direnv/ *.sln
*.sw*

View file

@ -4,34 +4,28 @@
A clone of [Acquia](https://www.acquia.com)s hosting dashboard, built with [Vue.js](https://vuejs.org) and [Tailwind CSS](https://tailwindcss.com). A clone of [Acquia](https://www.acquia.com)s hosting dashboard, built with [Vue.js](https://vuejs.org) and [Tailwind CSS](https://tailwindcss.com).
![A screenshot of the Rebuilding Acquia website](screenshot.png) ## Icons
[View the site live on Netlify →](https://rebuilding-acquia.oliverdavies.uk) The icons were downloaded from the live Acquia webite and re-used, but the HTML, CSS and JavaScript code was written from scratch using the above mentioned technologies.
## Project setup ## Screenshots
```
yarn install
```
### Compiles and hot-reloads for development ### Viewing all applications
```
yarn run serve
```
### Compiles and minifies for production ![A screenshot of the applications page in the list view](docs/images/applications-grid.png)
```
yarn run build
```
### Run your tests ### Viewing all environments for an application
```
yarn run test
```
### Lints and fixes files ![A screenshot of the applications page in the list view](docs/images/environments-with-help.png)
```
yarn run lint
```
### Customize configuration ### Viewing a single environment
See [Configuration Reference](https://cli.vuejs.org/config/).
![A screenshot of the applications page in the list view](docs/images/environment-prod.png)
## How it works
TODO: Add blog post link
## Author
[Oliver Davies](https://www.oliverdavies.uk) - Full Stack Developer

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

View file

@ -1,27 +0,0 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1659446231,
"narHash": "sha256-hekabNdTdgR/iLsgce5TGWmfIDZ86qjPhxDg/8TlzhE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "eabc38219184cc3e04a974fe31857d8e0eac098d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-21.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

View file

@ -1,23 +0,0 @@
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.11";
outputs =
{ nixpkgs, ... }:
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
inherit (pkgs) mkShell nodejs;
inherit (pkgs.nodePackages) yarn;
in
{
devShells.${system}.default = mkShell {
buildInputs = [
nodejs
yarn
];
};
formatter.${system} = pkgs.nixfmt-rfc-style;
};
}

111
main.tf
View file

@ -1,111 +0,0 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 3.0"
}
}
}
provider "aws" {
region = "eu-west-2"
}
provider "aws" {
alias = "us-east-1"
region = "us-east-1"
}
resource "aws_s3_bucket" "rebuilding-acquia" {
bucket = "rebuilding-acquia"
}
resource "aws_s3_bucket_acl" "rebuilding-acquia" {
acl = "private"
bucket = aws_s3_bucket.rebuilding-acquia.id
}
locals {
s3_origin_id = "rebuilding-acquia"
zone_name = "oliverdavies.uk"
}
resource "aws_cloudfront_origin_access_control" "rebuilding-acquia" {
name = "rebuilding-acquia"
description = "rebuilding-acquia"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
data "cloudflare_zone" "rebuilding-acquia" {
name = local.zone_name
}
data "aws_acm_certificate" "rebuilding-acquia" {
domain = local.zone_name
provider = aws.us-east-1
statuses = ["ISSUED"]
}
resource "aws_cloudfront_distribution" "s3_distribution" {
origin {
domain_name = aws_s3_bucket.rebuilding-acquia.bucket_regional_domain_name
origin_access_control_id = aws_cloudfront_origin_access_control.rebuilding-acquia.id
origin_id = local.s3_origin_id
}
comment = "Rebuilding Acquia - Tailwind CSS example"
default_root_object = "index.html"
enabled = true
is_ipv6_enabled = true
aliases = ["rebuilding-acquia.${local.zone_name}"]
default_cache_behavior {
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
target_origin_id = local.s3_origin_id
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
default_ttl = 3600
max_ttl = 86400
min_ttl = 0
viewer_protocol_policy = "allow-all"
}
price_class = "PriceClass_100"
restrictions {
geo_restriction {
locations = []
restriction_type = "none"
}
}
viewer_certificate {
acm_certificate_arn = data.aws_acm_certificate.rebuilding-acquia.arn
ssl_support_method = "sni-only"
}
}
resource "cloudflare_record" "rebuilding-acquia" {
name = "rebuilding-acquia"
proxied = false
ttl = 0
type = "CNAME"
value = aws_cloudfront_distribution.s3_distribution.domain_name
zone_id = data.cloudflare_zone.rebuilding-acquia.id
}

View file

@ -5,4 +5,4 @@ yarn test:unit
yarn build yarn build
""" """
publish = "dist" publish = "dist"
environment = { NODE_VERSION = "v12.6.0", YARN_VERSION = "1.13.0" } environment = { YARN_VERSION = "1.13.0" }

View file

@ -11,30 +11,27 @@
}, },
"dependencies": { "dependencies": {
"postcss-nested": "^4.1.2", "postcss-nested": "^4.1.2",
"sugarss": "^2.0.0", "tailwindcss": "^1.0.0-beta.3",
"tailwindcss": "^1.1.2",
"tailwindcss-spaced-items": "^0.1.0", "tailwindcss-spaced-items": "^0.1.0",
"tailwindcss-visuallyhidden": "^1.0.2", "tailwindcss-visuallyhidden": "^1.0.2",
"vue": "^2.6.6", "vue": "^2.6.6",
"vue-router": "^3.1.3" "vue-router": "^3.0.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.6.2", "@babel/core": "^7.4.3",
"@babel/preset-env": "^7.6.2", "@babel/preset-env": "^7.4.3",
"@fullhuman/postcss-purgecss": "^1.3.0", "@vue/cli-plugin-babel": "^3.5.1",
"@vue/cli-plugin-babel": "^3.11.0", "@vue/cli-plugin-eslint": "^3.5.1",
"@vue/cli-plugin-eslint": "^3.11.0", "@vue/cli-plugin-unit-jest": "^3.5.3",
"@vue/cli-plugin-unit-jest": "^3.11.0", "@vue/cli-service": "^3.5.1",
"@vue/cli-service": "^3.11.0",
"@vue/eslint-config-standard": "^4.0.0", "@vue/eslint-config-standard": "^4.0.0",
"@vue/test-utils": "1.0.0-beta.29", "@vue/test-utils": "1.0.0-beta.29",
"babel-core": "7.0.0-bridge.0", "babel-core": "7.0.0-bridge.0",
"babel-eslint": "^10.0.3", "babel-eslint": "^10.0.1",
"babel-jest": "^24.9.0", "babel-jest": "^24.7.1",
"eslint": "^5.8.0", "eslint": "^5.8.0",
"eslint-plugin-vue": "^5.0.0", "eslint-plugin-vue": "^5.0.0",
"jest": "^24.9.0", "jest": "^24.7.1",
"postcss-import": "^12.0.1",
"vue-template-compiler": "^2.5.21" "vue-template-compiler": "^2.5.21"
} }
} }

View file

@ -1,16 +1,7 @@
module.exports = { module.exports = {
parser: 'sugarss', plugins: {
plugins: [ tailwindcss: './tailwind.config.js',
require('postcss-import'), 'postcss-nested': {},
require('tailwindcss'), autoprefixer: {}
require('postcss-nested'), }
require('autoprefixer'),
process.env.NODE_ENV === 'production' && require('@fullhuman/postcss-purgecss')({
content: [
'./src/**/*.vue',
'./public/index.html',
],
defaultExtractor: content => content.match(/[A-Za-z0-9-_:/]+/g) || []
})
]
} }

View file

@ -1 +0,0 @@
https://rebuilding-acquia.netlify.com/* https://rebuilding-acquia.oliverdavies.uk/:splat 301!

View file

@ -7,7 +7,7 @@
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Rebuilding Acquia</title> <title>Rebuilding Acquia</title>
</head> </head>
<body> <body class="antialiased bg-gray-200 font-sans min-h-full flex flex-col flex-1">
<noscript> <noscript>
<strong>We're sorry but rebuilding-acquia doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> <strong>We're sorry but rebuilding-acquia doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript> </noscript>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

View file

@ -1,22 +1,44 @@
<template> <template>
<div id="app" class="min-h-screen antialiased bg-gray-200 font-sans flex flex-col flex-1"> <div id="app" class="flex flex-col flex-1">
<div class="flex flex-col flex-1"> <router-view class="flex flex-col flex-1"></router-view>
<router-view :applications="applications" :types="types" class="flex flex-col flex-1"/>
</div>
</div> </div>
</template> </template>
<style src="./assets/css/tailwind.css"/> <style lang="postcss">
@tailwind base;
<script> html {
import { applications, types } from '@/data' line-height: 1.15;
}
export default { @tailwind components;
data () {
return { h1,
applications, h2 {
types @apply font-bold
} }
h2 {
@apply text-2xl
}
.btn {
@apply text-sm font-thin antialiased text-white px-4 py-3 no-underline rounded mx-1 bg-blue-300;
&:hover,
&:focus {
@apply bg-blue-400
} }
} }
</script>
.btn.is-secondary {
@apply bg-gray-500;
&:hover,
&:focus {
@apply bg-gray-600
}
}
@tailwind utilities;
</style>

40
src/api-client.js Normal file
View file

@ -0,0 +1,40 @@
import _ from 'lodash'
let data = require('./data').default
export default {
methods: {
setData (newData) {
data = newData
},
getData () {
return data
},
getTypes () {
return data.types
},
getApplications () {
return data.applications
},
getApplication (applicationId) {
return _(data.applications).get(applicationId)
},
getEnvironment (applicationId, environment) {
return _(this.getApplication(applicationId))
.get('environments')[environment]
},
getApplicationType (application) {
return this.getTypes()[application.type]
},
getVersion (type, environment) {
return _(environment).get(`versions.${type}`)
}
}
}

View file

@ -1,9 +0,0 @@
html
line-height: 1.15
h1,
h2
@apply font-bold
h2
@apply text-2xl

View file

@ -1 +0,0 @@
@import 'components/button'

View file

@ -1,13 +0,0 @@
.btn
@apply text-sm font-thin antialiased text-white px-4 py-3 no-underline rounded mx-1 bg-blue-300
&:hover,
&:focus
@apply bg-blue-400
.btn.is-secondary
@apply bg-gray-500
&:hover,
&:focus
@apply bg-gray-600

View file

@ -1,7 +0,0 @@
@import 'tailwindcss/base'
@import './base.css'
@import 'tailwindcss/components'
@import './components.css'
@import 'tailwindcss/utilities'

View file

@ -1,31 +1,31 @@
<template> <template>
<div class="py-1 overflow-y-scroll"> <div class="py-1 overflow-y-scroll">
<ul class="-ml-3 flex"> <ul class="flex -ml-3">
<li class="ml-3 flex items-center"> <li class="flex items-center ml-3">
<div class="flex flex-col-reverse"> <div class="flex flex-col-reverse">
<span class="text-gray-700 uppercase text-sm">Organization</span> <span class="text-gray-700 uppercase text-sm">Organization</span>
<span v-if="!selectedOrganisation" class="mb-1 block font-bold">All</span> <span v-if="!selectedOrganisation" class="font-bold block mb-1">All</span>
<router-link v-else :to="{ name: 'applications' }" class="mb-1 block font-bold whitespace-no-wrap hover:text-blue-400" v-text="selectedOrganisation"></router-link> <router-link v-else :to="{ name: 'applications' }" class="font-bold block mb-1 hover:text-blue-400 whitespace-no-wrap" v-text="selectedOrganisation"></router-link>
</div> </div>
<svg class="ml-3" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M5.94 19.73L15.6 10 5.94.27A.88.88 0 0 0 5.3 0a.89.89 0 0 0-.65 1.51L13.06 10l-8.41 8.49a.89.89 0 0 0 1.29 1.24z"/></svg> <svg class="ml-3" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M5.94 19.73L15.6 10 5.94.27A.88.88 0 0 0 5.3 0a.89.89 0 0 0-.65 1.51L13.06 10l-8.41 8.49a.89.89 0 0 0 1.29 1.24z"/></svg>
</li> </li>
<li class="ml-3 flex items-center"> <li class="flex items-center ml-3">
<div class="flex flex-col-reverse"> <div class="flex flex-col-reverse">
<span class="text-gray-700 uppercase text-sm">Application</span> <span class="text-gray-700 uppercase text-sm">Application</span>
<span v-if="!selectedApplication" class="mb-1 block font-bold">All</span> <span v-if="!selectedApplication" class="font-bold block mb-1">All</span>
<span v-else-if="!selectedEnvironment" class="mb-1 block font-bold" v-text="selectedApplication"></span> <span v-else-if="!selectedEnvironment" class="font-bold block mb-1" v-text="selectedApplication"></span>
<router-link v-else :to="{ name: 'environments' }" class="mb-1 block font-bold whitespace-no-wrap hover:text-blue-400" v-text="selectedApplication"></router-link> <router-link v-else :to="{ name: 'environments' }" class="font-bold block mb-1 hover:text-blue-400 whitespace-no-wrap" v-text="selectedApplication"></router-link>
</div> </div>
<svg class="ml-3" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M5.94 19.73L15.6 10 5.94.27A.88.88 0 0 0 5.3 0a.89.89 0 0 0-.65 1.51L13.06 10l-8.41 8.49a.89.89 0 0 0 1.29 1.24z"/></svg> <svg class="ml-3" xmlns="http://www.w3.org/2000/svg" width="20" height="20"><path d="M5.94 19.73L15.6 10 5.94.27A.88.88 0 0 0 5.3 0a.89.89 0 0 0-.65 1.51L13.06 10l-8.41 8.49a.89.89 0 0 0 1.29 1.24z"/></svg>
</li> </li>
<li class="ml-3 flex items-center"> <li class="flex items-center ml-3">
<div class="flex flex-col-reverse"> <div class="flex flex-col-reverse">
<span class="text-gray-700 uppercase text-sm">Environment</span> <span class="text-gray-700 uppercase text-sm">Environment</span>
<span v-if="!selectedApplication" class="mb-1 block font-bold">---</span> <span v-if="!selectedApplication" class="font-bold block mb-1">---</span>
<span v-else-if="!selectedEnvironment" class="mb-1 block font-bold">All</span> <span v-else-if="!selectedEnvironment" class="font-bold block mb-1">All</span>
<span v-else class="mb-1 block font-bold" v-text="selectedEnvironment"></span> <span v-else class="font-bold block mb-1" v-text="selectedEnvironment"></span>
</div> </div>
</li> </li>
</ul> </ul>

View file

@ -9,40 +9,38 @@
</div> </div>
<div class="truncate text-blue-300"> <div class="truncate text-blue-300">
<a href="#0" class="text-inherit no-underline hover:underline focus:underline focus:outline-none" v-text="prodUrl"/> <a href="#0" class="text-inherit no-underline hover:underline focus:underline focus:outline-none">{{ application.environments['prod'].url }}</a>
</div> </div>
</div> </div>
<php-version class="mt-8" :application="application"></php-version> <php-version class="mt-8" :application="application"></php-version>
</div> </div>
<div class="w-1/6 flex-none text-right"> <div class="flex-none w-1/6 text-right">
<star-toggle :application="application" :starred="starred" @toggle="starred = !starred"></star-toggle> <star-toggle :application="application"></star-toggle>
</div> </div>
</div> </div>
</div> </div>
<div v-if="display == 'list'" class="p-3 bg-white border-gray-400 border-b"> <div v-if="display == 'list'" class="bg-white p-3 border-gray-400 border-b">
<div class="-mx-2"> <div class="-mx-2">
<div class="flex flex-row-reverse items-center justify-between"> <div class="flex flex-row-reverse items-center justify-between">
<div class="-mx-2 flex flex-1 justify-between items-center"> <div class="flex flex-1 justify-between items-center -mx-2">
<div class="px-2 flex-1"> <div class="flex-1 px-2">
<router-link :to="{name: 'environments', params: { id }}" class="text-blue-300 no-underline hover:underline focus:underline"> <router-link :to="{name: 'environments', params: {id: id}}" class="text-blue-300 no-underline hover:underline focus:underline"><h2 class="text-base font-normal mb-1">{{ application.name }}</h2></router-link>
<h2 class="mb-1 text-base font-normal">{{ application.name }}</h2>
</router-link>
</div> </div>
<div class="px-2 w-2/5"> <div class="w-2/5 px-2">
<a href="#0" class="text-blue-300 no-underline hover:underline focus:underline" v-text="prodUrl"/> <a href="#0" class="text-blue-300 no-underline hover:underline focus:underline">{{ application.environments['prod'].url }}</a>
</div> </div>
<div class="px-1 w-1/5"> <div class="w-1/5 px-2">
<php-version :application="application"/> <php-version :application="application"></php-version>
</div> </div>
</div> </div>
<div class="px-2"> <div class="px-2">
<star-toggle :application="application" :starred="starred" @toggle="starred = !starred"/> <star-toggle :application="application"></star-toggle>
</div> </div>
</div> </div>
</div> </div>
@ -51,28 +49,28 @@
</template> </template>
<script> <script>
import ApiClient from '@/api-client.js'
import PhpVersion from '@/components/Application/PhpVersion' import PhpVersion from '@/components/Application/PhpVersion'
import StarToggle from '@/components/Application/StarToggle' import StarToggle from '@/components/Application/StarToggle'
export default { export default {
components: { PhpVersion, StarToggle }, mixins: [ApiClient],
components: {
PhpVersion,
StarToggle
},
props: { props: {
application: Object, application: Object,
display: String, display: String,
id: String id: Number
}, },
data () { data () {
return { return {
starred: false starred: false
} }
},
computed: {
prodUrl () {
return this.application.environments['prod'].url
}
} }
} }
</script> </script>

View file

@ -1,8 +1,8 @@
<template> <template>
<div> <div>
<ul class="list-reset -ml-2 flex flex-wrap"> <ul class="list-reset flex flex-wrap -mr-2 -mb-2">
<li class="ml-2 py-1 px-2 text-2xs rounded uppercase border" :class="typeClasses" v-text="applicationTypeName"/> <li class="tag" :data-type="type.id">{{ type.name }}</li>
<li class="ml-2 py-1 px-2 text-2xs rounded uppercase border border-gray-400 bg-white">{{ application.level }}</li> <li class="tag">{{ level }}</li>
</ul> </ul>
</div> </div>
</template> </template>
@ -10,21 +10,22 @@
<script> <script>
export default { export default {
props: { props: {
application: Object level: String,
}, type: Object
computed: {
applicationTypeName () {
return this.$attrs.types[this.application.type].name
},
typeClasses () {
return {
'': 'bg-white border-gray-400',
drupal: 'bg-blue-100 border-blue-100 text-white',
nodejs: 'bg-green border-green text-white'
}[this.application.type]
}
} }
} }
</script> </script>
<style scoped>
.tag {
@apply text-2xs py-1 px-2 rounded uppercase border border-gray-400 bg-white mr-2 mb-2
}
.tag[data-type="drupal"] {
@apply bg-blue-100 border-blue-100 text-white
}
.tag[data-type="nodejs"] {
@apply bg-green border-green text-white
}
</style>

View file

@ -1,12 +1,16 @@
<template> <template>
<div class="flex items-center text-sm"> <div class="text-sm flex items-center">
<svg class="w-8 h-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M1.71 6.86h3.21A2.49 2.49 0 0 1 7 7.67a2.68 2.68 0 0 1 .42 2.19A4.1 4.1 0 0 1 7 11.1a3.55 3.55 0 0 1-.77 1.09A2.6 2.6 0 0 1 5 13a5.92 5.92 0 0 1-1.41.17H2.12l-.45 2.26H0l1.71-8.57m1.41 1.36L2.4 11.8h.31a6.55 6.55 0 0 0 1.91-.22c.52-.17.86-.75 1-1.75.14-.83 0-1.31-.44-1.44a5.52 5.52 0 0 0-1.59-.18zm11.15-1.36h3.21a2.52 2.52 0 0 1 2.05.81A2.75 2.75 0 0 1 20 9.86a4.12 4.12 0 0 1-.38 1.24 3.69 3.69 0 0 1-.76 1.09 2.65 2.65 0 0 1-1.28.79 6.07 6.07 0 0 1-1.42.17h-1.49l-.45 2.26h-1.67l1.72-8.55m1.4 1.36L15 11.8h.31a6.58 6.58 0 0 0 1.92-.22c.51-.17.85-.75 1-1.75.14-.83 0-1.31-.43-1.44a5.64 5.64 0 0 0-1.6-.18zm-7-3.63h1.65l-.44 2.27h1.49a3 3 0 0 1 1.82.51c.41.31.53.92.36 1.81l-.8 4h-1.68l.77-3.79a1.06 1.06 0 0 0-.08-.85c-.12-.17-.4-.25-.82-.25H9.61l-1 4.9H7l1.7-8.6" data-name="Layer 2"/></svg> <svg class="w-8 h-8" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M1.71 6.86h3.21A2.49 2.49 0 0 1 7 7.67a2.68 2.68 0 0 1 .42 2.19A4.1 4.1 0 0 1 7 11.1a3.55 3.55 0 0 1-.77 1.09A2.6 2.6 0 0 1 5 13a5.92 5.92 0 0 1-1.41.17H2.12l-.45 2.26H0l1.71-8.57m1.41 1.36L2.4 11.8h.31a6.55 6.55 0 0 0 1.91-.22c.52-.17.86-.75 1-1.75.14-.83 0-1.31-.44-1.44a5.52 5.52 0 0 0-1.59-.18zm11.15-1.36h3.21a2.52 2.52 0 0 1 2.05.81A2.75 2.75 0 0 1 20 9.86a4.12 4.12 0 0 1-.38 1.24 3.69 3.69 0 0 1-.76 1.09 2.65 2.65 0 0 1-1.28.79 6.07 6.07 0 0 1-1.42.17h-1.49l-.45 2.26h-1.67l1.72-8.55m1.4 1.36L15 11.8h.31a6.58 6.58 0 0 0 1.92-.22c.51-.17.85-.75 1-1.75.14-.83 0-1.31-.43-1.44a5.64 5.64 0 0 0-1.6-.18zm-7-3.63h1.65l-.44 2.27h1.49a3 3 0 0 1 1.82.51c.41.31.53.92.36 1.81l-.8 4h-1.68l.77-3.79a1.06 1.06 0 0 0-.08-.85c-.12-.17-.4-.25-.82-.25H9.61l-1 4.9H7l1.7-8.6" data-name="Layer 2"/></svg>
<span class="ml-1">{{ phpVersion }}</span> <span class="ml-1">{{ phpVersion }}</span>
</div> </div>
</template> </template>
<script> <script>
import ApiClient from '@/api-client.js'
export default { export default {
mixins: [ApiClient],
props: { props: {
application: Object application: Object
}, },
@ -17,7 +21,7 @@ export default {
return null return null
} }
return this.application.environments['prod'].versions['php'] return this.getVersion('php', this.getEnvironment(this.application.id, 'prod'))
} }
} }
} }

View file

@ -1,17 +1,19 @@
<template> <template>
<button type="button" @click="$emit('toggle')" class="focus:outline-none" :class="[starred ? 'text-orange-100 hover:text-orange-200 focus:text-orange-200' : 'text-gray-400 focus:text-orange-200 hover:text-orange-100']"> <button type="button" @click="starred = !starred" class="focus:outline-none" :class="[starred ? 'text-orange-100 hover:text-orange-200 focus:text-orange-200' : 'text-gray-400 focus:text-orange-200 hover:text-orange-100']">
<span class="visuallyhidden">{{ !starred ? 'Star' : 'Unstar' }} {{ application.name }}</span> <span class="visuallyhidden">{{ !starred ? 'Star' : 'Unstar' }} {{ application.name }}</span>
<svg class="-mr-1 h-6 w-6 fill-current" role="presentation"><use :xlink:href="`/img/icons.symbol.svg#state__${starred ? 'starred' : 'unstarred'}`"></use></svg> <svg class="h-6 w-6 fill-current -mr-1" role="presentation"><use :xlink:href="`/img/icons.symbol.svg#state__${starred ? 'starred' : 'unstarred'}`"></use></svg>
</button> </button>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
application: Object, application: Object
starred: { },
type: Boolean,
default: false data () {
return {
starred: false
} }
} }
} }

View file

@ -5,7 +5,7 @@
<div class="px-3 py-4"> <div class="px-3 py-4">
<div class="flex flex-row-reverse"> <div class="flex flex-row-reverse">
<div class="flex-1"> <div class="flex-1">
<h2 class="mb-1 text-gray-600 text-base">{{ title }}</h2> <h2 class="text-gray-600 text-base mb-1">{{ title }}</h2>
<div class="text-xs text-gray-600">{{ description }}</div> <div class="text-xs text-gray-600">{{ description }}</div>
</div> </div>
@ -39,6 +39,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
svg svg {
@apply h-8 w-8 fill-current text-teal @apply h-8 w-8 fill-current text-teal
}
</style> </style>

View file

@ -1,5 +1,5 @@
<template> <template>
<button type="button" class="py-3 w-full flex flex-col items-center text-blue-300 text-xs font-bold no-underline hover:underline focus:underline hover:bg-gray-200 focus:bg-gray-200 focus:outline-none"> <button type="button" class="w-full flex flex-col items-center py-3 text-blue-300 text-xs font-bold no-underline hover:underline focus:underline hover:bg-gray-200 focus:bg-gray-200 focus:outline-none block">
<slot></slot> <slot></slot>
<span class="block" v-text="label"></span> <span class="block" v-text="label"></span>
</button> </button>
@ -14,6 +14,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
svg svg {
@apply h-6 w-6 fill-current mb-2 @apply h-6 w-6 fill-current mb-2
}
</style> </style>

View file

@ -1,24 +1,24 @@
<template> <template>
<div> <div>
<ul class="flex flex-wrap md:hidden -mx-1 mb-5"> <ul class="flex flex-wrap md:hidden -mx-1 mb-5">
<li class="px-1 w-1/3"><button type="button" class="py-3 w-full text-sm text-white rounded" :class="[ active == 'code' ? 'bg-blue-400' : 'bg-blue-300' ]" @click="active = 'code'">Code</button></li> <li class="w-1/3 px-1"><button type="button" class="text-sm text-white w-full py-3 rounded" :class="[ active == 'code' ? 'bg-blue-400' : 'bg-blue-300' ]" @click="active = 'code'">Code</button></li>
<li class="px-1 w-1/3"><button type="button" class="py-3 w-full text-sm text-white rounded" :class="[ active == 'databases' ? 'bg-blue-400' : 'bg-blue-300' ]" @click="active = 'databases'">Databases</button></li> <li class="w-1/3 px-1"><button type="button" class="text-sm text-white w-full py-3 rounded" :class="[ active == 'databases' ? 'bg-blue-400' : 'bg-blue-300' ]" @click="active = 'databases'">Databases</button></li>
<li class="px-1 w-1/3"><button type="button" class="py-3 w-full text-sm text-white rounded" :class="[ active == 'files' ? 'bg-blue-400' : 'bg-blue-300' ]" @click="active = 'files'">Files</button></li> <li class="w-1/3 px-1"><button type="button" class="text-sm text-white w-full py-3 rounded" :class="[ active == 'files' ? 'bg-blue-400' : 'bg-blue-300' ]" @click="active = 'files'">Files</button></li>
</ul> </ul>
<div class="-mx-4 flex flex-wrap"> <div class="flex flex-wrap -mx-4 -mb-4">
<div class="px-4 w-full md:w-1/3 md:block" :class="[ active == 'code' ? 'block' : 'hidden' ]"> <div class="wrapper" :class="[ active == 'code' ? 'block' : 'hidden' ]">
<action-card title="Code" :description="`${ environment.name }: ${environment.label}`"> <action-card title="Code" :description="`${ environment.name }: ${environment.label}`">
<svg slot="icon" role="presentation"><use xlink:href="/img/icons.symbol.svg#objects__code"></use></svg> <svg slot="icon" role="presentation"><use xlink:href="/img/icons.symbol.svg#objects__code"></use></svg>
<ul slot="buttons" class="-ml-px flex"> <ul slot="buttons" class="button-list">
<li class="w-1/2 flex-1 text-center border-l border-gray-400"> <li class="button-list-item">
<action-card-button label="Deploy"> <action-card-button label="Deploy">
<svg role="presentation"><use xlink:href="/img/icons.symbol.svg#objects__fork"></use></svg> <svg role="presentation"><use xlink:href="/img/icons.symbol.svg#objects__fork"></use></svg>
</action-card-button> </action-card-button>
</li> </li>
<li class="w-1/2 flex-1 text-center border-l border-gray-400"> <li class="button-list-item">
<action-card-button label="Switch"> <action-card-button label="Switch">
<svg role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__switch"></use></svg> <svg role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__switch"></use></svg>
</action-card-button> </action-card-button>
@ -27,24 +27,24 @@
</action-card> </action-card>
</div> </div>
<div class="px-4 w-full md:block md:w-1/3" :class="[ active == 'databases' ? 'block' : 'hidden' ]"> <div class="wrapper" :class="[ active == 'databases' ? 'block' : 'hidden' ]">
<action-card title="Databases" :description="environment.name"> <action-card title="Databases" :description="environment.name">
<svg slot="icon" role="presentation"><use xlink:href="/img/icons.symbol.svg#objects__database"></use></svg> <svg slot="icon" role="presentation"><use xlink:href="/img/icons.symbol.svg#objects__database"></use></svg>
<ul slot="buttons" class="-ml-px flex"> <ul slot="buttons" class="button-list">
<li class="w-1/2 flex-1 text-center border-l border-gray-400"> <li class="button-list-item">
<action-card-button label="Copy"> <action-card-button label="Copy">
<svg role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__copy"></use></svg> <svg role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__copy"></use></svg>
</action-card-button> </action-card-button>
</li> </li>
<li class="w-1/2 flex-1 text-center border-l border-gray-400"> <li class="button-list-item">
<action-card-button label="Back up"> <action-card-button label="Back up">
<svg role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__backup"></use></svg> <svg role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__backup"></use></svg>
</action-card-button> </action-card-button>
</li> </li>
<li class="w-1/2 flex-1 text-center border-l border-gray-400"> <li class="button-list-item">
<action-card-button label="Restore" :disabled="isProduction" :class="{ <action-card-button label="Restore" :disabled="isProduction" :class="{
'text-blue-300': !isProduction, 'text-blue-300': !isProduction,
'text-gray-500 cursor-not-allowed': isProduction 'text-gray-500 cursor-not-allowed': isProduction
@ -56,12 +56,12 @@
</action-card> </action-card>
</div> </div>
<div class="px-4 w-full md:block md:w-1/3" :class="[ active == 'files' ? 'block' : 'hidden' ]"> <div class="wrapper" :class="[ active == 'files' ? 'block' : 'hidden' ]">
<action-card title="Files" :description="environment.name"> <action-card title="Files" :description="environment.name">
<svg slot="icon" class="h-8 w-8 fill-current text-teal" role="presentation"><use xlink:href="/img/icons.symbol.svg#objects__environment"></use></svg> <svg slot="icon" class="h-8 w-8 fill-current text-teal" role="presentation"><use xlink:href="/img/icons.symbol.svg#objects__environment"></use></svg>
<ul slot="buttons" class="-ml-px flex"> <ul slot="buttons" class="button-list">
<li class="w-1/2 flex-1 text-center border-l border-gray-400"> <li class="button-list-item">
<action-card-button label="Copy"> <action-card-button label="Copy">
<svg role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__copy"></use></svg> <svg role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__copy"></use></svg>
</action-card-button> </action-card-button>
@ -95,3 +95,21 @@ export default {
} }
} }
</script> </script>
<style scoped>
.wrapper {
@apply w-full px-4 mb-4;
@screen md {
@apply w-1/3 block
}
}
.button-list {
@apply flex -ml-px
}
.button-list-item {
@apply w-1/2 flex-1 text-center border-l border-gray-400
}
</style>

View file

@ -1,6 +1,7 @@
<template> <template>
<div class="grid gap-6 grid-cols-1 md:grid-cols-2 xl:grid-cols-3"> <div class="mb-6">
<div v-for="(environment, key) in application.environments" :key="key"> <div class="flex flex-wrap -mx-4 -mb-6">
<div v-for="(environment, key) in environments" class="w-full md:w-1/2 xl:w-1/3 px-4 mb-6" :key="key">
<div class="border border-gray-400 rounded overflow-hidden shadow"> <div class="border border-gray-400 rounded overflow-hidden shadow">
<div class="bg-white"> <div class="bg-white">
<div class="border-t-4 border-teal p-3"> <div class="border-t-4 border-teal p-3">
@ -36,20 +37,21 @@
</span> </span>
</button> </button>
<button type="button" class="px-1 py-3 w-full flex items-center text-white bg-blue-200 text-sm text-left border-t border-gray-400 hover:bg-blue-400 focus:bg-blue-400 focus:outline-none focus:underline"> <button type="button" class="flex items-center text-white bg-blue-200 hover:bg-blue-400 focus:bg-blue-400 focus:outline-none focus:underline px-1 py-3 text-sm w-full text-left py-1 border-t border-gray-400">
<svg class="mr-2 h-6 w-5 text-white fill-current" role="presentation"><use xlink:href="/img/icons.symbol.svg#objects__grip-handle"></use></svg> <svg class="h-6 w-5 text-white mr-2 fill-current" role="presentation"><use xlink:href="/img/icons.symbol.svg#objects__grip-handle"></use></svg>
Files Files
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
application: Object, environments: Object,
id: String id: String
} }
} }

View file

@ -1,22 +1,22 @@
<template> <template>
<div class="bg-white border-t-10 border-purple" :class="[hidden ? 'hidden' : 'block lg:flex']"> <div class="bg-white border-t-10 border-purple mb-6" :class="[hidden ? 'hidden' : 'block lg:flex']">
<div class="flex-none py-3 pl-3 pr-8 border-b lg:border-r border-gray-200"> <div class="flex-none py-3 pl-3 pr-8 border-b lg:border-r border-gray-200">
<div class="flex items-center"> <div class="flex items-center">
<svg class="mr-1 h-6 w-6 text-purple fill-current" role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__info"></use></svg> <svg class="h-6 w-6 mr-1 text-purple fill-current" role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__info"></use></svg>
Quick help Quick help
</div> </div>
</div> </div>
<div class="p-4"> <div class="p-4">
<div class="-mb-5 block leading-normal lg:hidden"> <div class="block lg:hidden leading-normal -mb-5">
<div v-for="item in items" :key="item.subject"> <div v-for="item in items" :key="item.subject">
<p class="mb-3 text-sm font-bold">{{ item.subject }}</p> <p class="text-sm mb-3 font-bold">{{ item.subject }}</p>
<p class="mb-5 text-sm">{{ item.text }}</p> <p class="text-sm mb-5">{{ item.text }}</p>
</div> </div>
</div> </div>
<div class="hidden lg:block"> <div class="hidden lg:block">
<div class="mb-6 w-full lg:w-auto lg:flex"> <div class="w-full lg:w-auto lg:flex mb-6">
<button <button
v-for="(item, i) in items" v-for="(item, i) in items"
:key="item.subject" :key="item.subject"

View file

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<div class="pb-4 pt-6 px-4 bg-gray-300"> <div class="bg-gray-300 pb-4 pt-6 px-4">
<h2 class="font-normal text-lg">Task Log</h2> <h2 class="font-normal text-lg">Task Log</h2>
</div> </div>
<div> <div>
@ -14,7 +14,7 @@
</template> </template>
<script> <script>
import reverse from 'lodash/reverse' import _ from 'lodash'
import TaskLogItem from '@/components/Environment/TaskLog/TaskLogItem' import TaskLogItem from '@/components/Environment/TaskLog/TaskLogItem'
export default { export default {
@ -30,7 +30,7 @@ export default {
reversedTasks: function () { reversedTasks: function () {
let tasks = this.tasks let tasks = this.tasks
return reverse(tasks) return _.reverse(tasks)
} }
} }
} }

View file

@ -2,13 +2,13 @@
<div> <div>
<div class="px-3 py-4 border-b border-gray-300 flex" :class="{'bg-yellow-lightest': open}"> <div class="px-3 py-4 border-b border-gray-300 flex" :class="{'bg-yellow-lightest': open}">
<div class="flex-none mr-3"> <div class="flex-none mr-3">
<svg v-if="task.loading" class="rotates h-5 w-5 text-gray-500 fill-current" role="presentation"><use xlink:href="/img/icons.symbol.svg#feedback__loading"></use></svg> <svg v-if="task.loading" class="h-5 w-5 text-gray-500 fill-current rotates" role="presentation"><use xlink:href="/img/icons.symbol.svg#feedback__loading"></use></svg>
<svg v-if="!task.loading && task.success" class="h-5 w-5 text-green fill-current" role="presentation"><use xlink:href="/img/icons.symbol.svg#feedback__success-circle"></use></svg> <svg v-if="!task.loading && task.success" class="h-5 w-5 text-green fill-current" role="presentation"><use xlink:href="/img/icons.symbol.svg#feedback__success-circle"></use></svg>
<svg v-if="!task.loading && !task.success" class="h-5 w-5 text-red fill-current" role="presentation"><use xlink:href="/img/icons.symbol.svg#feedback__warning"></use></svg> <svg v-if="!task.loading && !task.success" class="h-5 w-5 text-red fill-current" role="presentation"><use xlink:href="/img/icons.symbol.svg#feedback__warning"></use></svg>
</div> </div>
<div class="flex-1"> <div class="flex-1">
<div class="mb-2 text-sm font-bold">{{ task.text }}</div> <div class="text-sm font-bold mb-2">{{ task.text }}</div>
<div class="text-gray-600 text-2xs">{{ task.times.display }}</div> <div class="text-gray-600 text-2xs">{{ task.times.display }}</div>
</div> </div>
@ -21,29 +21,29 @@
</div> </div>
<div class="p-4 bg-gray-600 text-white antialiased" v-show="open"> <div class="p-4 bg-gray-600 text-white antialiased" v-show="open">
<div class="-mx-4 -mb-4 lg:flex lg:flex-wrap"> <div class="lg:flex lg:flex-wrap -mx-4 -mb-4">
<div class="px-4 mb-4"> <div class="px-4 mb-4">
<div class="mb-1 text-xs uppercase">Task ID</div> <div class="text-xs uppercase mb-1">Task ID</div>
<div class="text-sm font-bold">12345678</div> <div class="text-sm font-bold">12345678</div>
</div> </div>
<div class="mb-4 px-4 lg:w-1/6 lg:flex-none"> <div class="px-4 mb-4 lg:w-1/6 lg:flex-none">
<div class="mb-1 text-xs uppercase">User</div> <div class="text-xs uppercase mb-1">User</div>
<div class="truncate text-sm font-bold">{{ task.user }}</div> <div class="text-sm font-bold truncate">{{ task.user }}</div>
</div> </div>
<div class="px-4 mb-4 lg:w-1/4 lg:flex-none"> <div class="px-4 mb-4 lg:w-1/4 lg:flex-none">
<div class="mb-1 text-xs uppercase">Started</div> <div class="text-xs uppercase mb-1">Started</div>
<div class="truncate text-sm font-bold">{{ task.times.started }}</div> <div class="text-sm font-bold truncate">{{ task.times.started }}</div>
</div> </div>
<div class="px-4 mb-4 lg:w-1/4 lg:flex-none"> <div class="px-4 mb-4 lg:w-1/4 lg:flex-none">
<div class="mb-1 text-xs uppercase">Completed</div> <div class="text-xs uppercase mb-1">Completed</div>
<div class="truncate text-sm font-bold">{{ task.times.completed }}</div> <div class="text-sm font-bold truncate">{{ task.times.completed }}</div>
</div> </div>
<div class="px-4 mb-4"> <div class="px-4 mb-4">
<div class="mb-1 text-xs uppercase">Status</div> <div class="text-xs uppercase mb-1">Status</div>
<div class="text-sm font-bold">{{ status }}</div> <div class="text-sm font-bold">{{ status }}</div>
</div> </div>
</div> </div>
@ -80,12 +80,16 @@ export default {
</script> </script>
<style scoped> <style scoped>
.rotates .rotates {
animation: rotation 1s infinite linear animation: rotation 1s infinite linear
}
@-webkit-keyframes rotation @-webkit-keyframes rotation {
from from {
-webkit-transform: rotate(0deg) -webkit-transform: rotate(0deg)
to }
to {
-webkit-transform: rotate(-359deg) -webkit-transform: rotate(-359deg)
}
}
</style> </style>

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="hidden lg:block"> <div class="hidden lg:block">
<button type="button" class="text-blue-300 text-sm font-hairline flex items-end hover:underline focus:underline focus:outline-none" @click="$emit('toggle')"> <button type="button" class="text-blue-300 text-sm font-hairline flex items-end hover:underline focus:underline focus:outline-none" @click="$emit('toggle')">
<svg class="mr-1 h-5 w-5 text-blue-300 fill-current" role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__info"></use></svg> <svg class="h-5 w-5 mr-1 text-blue-300 fill-current" role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__info"></use></svg>
{{ hidden ? 'Show quick help' : 'Hide quick help' }} {{ hidden ? 'Show quick help' : 'Hide quick help' }}
</button> </button>
</div> </div>

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="hidden lg:block"> <div class="hidden lg:block">
<ul class="list-reset flex"> <ul class="list-reset flex">
<li v-for="(link, i) in links" :key="link.title" :class="{ 'ml-6': i !== 0 }"> <li v-for="(link, i) in links" :key="link.title" :class="{'ml-6': i !== 0}">
<button <button
type="button" type="button"
class="text-xs no-underline hover:underline flex flex-col items-center justify-center" class="text-xs no-underline hover:underline flex flex-col items-center justify-center"
@ -11,7 +11,7 @@
}" }"
:disabled="link.disabled" :disabled="link.disabled"
> >
<svg class="mb-2 h-6 w-6 fill-current" role="presentation"><use :xlink:href="`/img/icons.symbol.svg#${link.icon}`"></use></svg> <svg class="h-6 w-6 fill-current mb-2" role="presentation"><use :xlink:href="`/img/icons.symbol.svg#${link.icon}`"></use></svg>
{{ link.title }} {{ link.title }}
</button> </button>
</li> </li>
@ -20,7 +20,7 @@
</template> </template>
<script> <script>
import { omitBy, _ } from 'lodash' import _ from 'lodash'
export default { export default {
props: { props: {
@ -31,7 +31,7 @@ export default {
filteredLinks: function () { filteredLinks: function () {
let links = this.links let links = this.links
return omitBy(links, _.isEmpty) return _.omitBy(links, _.isEmpty)
} }
} }
} }

View file

@ -7,8 +7,8 @@
<div class="flex flex-col flex-1"> <div class="flex flex-col flex-1">
<nav class="border-t-2 border-transparent flex justify-end md:justify-start"> <nav class="border-t-2 border-transparent flex justify-end md:justify-start">
<a href="#0" class="mx-2 px-4 py-5 inline-block text-sm border-b-4 border-blue-300 text-blue-300 uppercase no-underline hover:underline focus:underline">Develop</a> <a href="#0" class="inline-block text-sm border-b-4 border-blue-300 mx-2 px-4 py-5 text-blue-300 uppercase no-underline hover:underline focus:underline">Develop</a>
<a href="#0" class="mx-2 px-4 py-5 inline-block text-sm border-b-4 border-transparent text-blue-300 uppercase no-underline hover:underline focus:underline">Manage</a> <a href="#0" class="inline-block text-sm border-b-4 border-transparent mx-2 px-4 py-5 text-blue-300 uppercase no-underline hover:underline focus:underline">Manage</a>
</nav> </nav>
</div> </div>

View file

@ -1,11 +1,11 @@
<template> <template>
<div class="fixed left-0 h-full w-auto bg-gray-300 text-gray-400 overflow-y-scroll z-30 antialiased lg:w-56"> <div class="w-auto lg:w-56 bg-gray-300 text-gray-400 antialiased fixed left-0 h-full overflow-y-scroll z-30">
<div> <div>
<ul class="list-reset"> <ul class="list-reset">
<li v-for="link in links" :key="link.title"> <li v-for="link in links" :key="link.title">
<a <a
href="#0" href="#0"
class="px-4 py-2 flex items-center no-underline text-sm focus:underline" class="flex items-center no-underline focus:underline px-4 py-2 text-sm"
:class="{ :class="{
'bg-gray-600 text-white hover:text-white': link.active, 'bg-gray-600 text-white hover:text-white': link.active,
'border-transparent hover:bg-gray-400': !link.active, 'border-transparent hover:bg-gray-400': !link.active,
@ -14,7 +14,7 @@
}" }"
> >
<svg class="h-6 w-6 fill-current" role="presentation"><use :xlink:href="`/img/icons.symbol.svg#${link.icon}`"></use></svg> <svg class="h-6 w-6 fill-current" role="presentation"><use :xlink:href="`/img/icons.symbol.svg#${link.icon}`"></use></svg>
<span class="hidden lg:ml-4 lg:block">{{ link.title }}</span> <span class="hidden lg:block lg:ml-4">{{ link.title }}</span>
</a> </a>
</li> </li>
</ul> </ul>

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="border-b-3 border-gray-300"> <div class="border-b-3 border-gray-300">
<div class="px-4 py-5 flex justify-between items-center bg-white border-t border-gray-200 lg:px-6"> <div class="bg-white px-4 lg:px-6 py-5 border-t border-gray-200 flex justify-between items-center">
<div class="w-full flex flex-col md:w-auto"> <div class="w-full md:w-auto flex flex-col">
<app-breadcrumb :application="application" :environment="environment"></app-breadcrumb> <app-breadcrumb :application="application" :environment="environment"></app-breadcrumb>
</div> </div>

View file

@ -1,4 +1,4 @@
module.exports = { export default {
types: { types: {
drupal: { drupal: {
id: 'drupal', id: 'drupal',

View file

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<div class="w-full fixed top-0 z-30"> <div class="fixed top-0 w-full z-30">
<navbar></navbar> <navbar></navbar>
<title-block> <title-block>
@ -13,43 +13,39 @@
</div> </div>
<div class="mt-48"> <div class="mt-48">
<div class="-mt-3 h-full flex flex-row-reverse"> <div class="-mt-3 flex flex-row-reverse h-full">
<div class="ml-16 p-4 flex-1 overflow-x-hidden lg:ml-56 lg:p-12"> <div class="flex-1 p-4 lg:p-12 ml-16 lg:ml-56 overflow-x-hidden">
<div class="mb-6"> <div class="mb-6">
<div class="lg:flex lg:items-baseline mb-12"> <div class="lg:flex lg:items-baseline mb-12">
<div class="mr-16 mb-4 lg:mb-0"> <div class="mr-16 mb-4 lg:mb-0">
<h1 class="mb-2 text-4xl font-thin">Applications</h1> <h1 class="text-4xl font-thin mb-2">Applications</h1>
</div> </div>
<application-display-switcher class="mr-3 hidden lg:flex-1 lg:flex lg:justify-end" :mode="display" @display-changed="handleDisplay"></application-display-switcher> <application-display-switcher class="hidden lg:flex-1 lg:flex lg:justify-end mr-3" :mode="display" @display-changed="handleDisplay"></application-display-switcher>
<div class="justify-between items-baseline lg:flex lg:flex-row-reverse"> <div class="lg:flex lg:flex-row-reverse justify-between items-baseline">
<div class="w-full"> <div class="w-full">
<form action="#"> <form action="#">
<label for="applications" class="visuallyhidden">Filter applications</label> <label for="applications" class="visuallyhidden">Filter applications</label>
<input id="applications" type="text" placeholder="Filter applications" class="py-2 px-3 w-full border border-gray-600 rounded"> <input id="applications" type="text" placeholder="Filter applications" class="w-full py-2 px-3 border border-gray-600 rounded">
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="-mt-6 -mx-3 flex flex-wrap"> <div class="flex flex-wrap -mx-3 -mb-6">
<div
v-for="application in sortedApplications"
:key="application.id"
class="px-3 w-full"
:class="{ 'mt-6 flex flex-col md:w-1/2 lg:w-1/3 xl:w-1/4': display == 'grid' }"
>
<application-card <application-card
:application="application" v-for="application in sortedApplications"
:display="display"
:id="application.id" :id="application.id"
class="flex flex-col flex-1" :application="application"
:key="application.id"
:display="display"
class="px-3 w-full"
:class="[display == 'grid' ? 'md:w-1/2 lg:w-1/3 xl:w-1/4 mb-6 flex flex-col' : '']"
></application-card> ></application-card>
</div> </div>
</div> </div>
</div>
<sidebar :links="[ <sidebar :links="[
{ title: 'All', icon: 'alpha__grid', active: true, disabled: false }, { title: 'All', icon: 'alpha__grid', active: true, disabled: false },
@ -62,21 +58,13 @@
</template> </template>
<script> <script>
import sortBy from 'lodash/sortBy' import _ from 'lodash'
import ApiClient from '@/api-client.js'
import ApplicationCard from '@/components/Application/ApplicationCard' import ApplicationCard from '@/components/Application/ApplicationCard'
import ApplicationDisplaySwitcher from '@/components/Application/ApplicationDisplaySwitcher' import ApplicationDisplaySwitcher from '@/components/Application/ApplicationDisplaySwitcher'
export default { export default {
props: { mixins: [ApiClient],
applications: {
type: Object,
required: true
},
types: {
type: Object,
required: true
}
},
components: { components: {
ApplicationCard, ApplicationCard,
@ -85,13 +73,14 @@ export default {
data () { data () {
return { return {
applications: this.getApplications(),
display: 'grid' display: 'grid'
} }
}, },
computed: { computed: {
sortedApplications: function () { sortedApplications: function () {
return sortBy(this.applications, [function (a) { return a.name }]) return _.sortBy(this.applications, [function (a) { return a.name }])
} }
}, },

View file

@ -18,12 +18,12 @@
<div class="mt-48"> <div class="mt-48">
<div class="-mt-3 flex flex-row-reverse h-full"> <div class="-mt-3 flex flex-row-reverse h-full">
<div class="ml-16 p-4 flex-1 overflow-x-hidden lg:ml-56 lg:p-12"> <div class="flex-1 p-4 lg:p-12 ml-16 lg:ml-56 overflow-x-hidden">
<div> <div>
<div> <div>
<h1 class="mb-2 text-4xl font-thin">Overview</h1> <h1 class="text-4xl font-thin mb-2">Overview</h1>
<application-tags :application="application" :types="$attrs.types"/> <application-tags :type="getApplicationType(application)" :level="application.level"></application-tags>
</div> </div>
<div class="my-10"> <div class="my-10">
@ -38,21 +38,21 @@
<action-cards :environment="environment" :is-production="isProduction"></action-cards> <action-cards :environment="environment" :is-production="isProduction"></action-cards>
</div> </div>
<div class="mt-8 shadow-md overflow-hidden"> <div class="shadow-md overflow-hidden">
<div class="p-3 bg-white border-b-2 border-gray-300"><h2 class="text-lg">Site Health</h2></div> <div class="bg-white border-b-2 border-gray-300 p-3"><h2 class="text-lg">Site Health</h2></div>
<div class="p-4 bg-white"> <div class="bg-white p-4">
<div> <div class="mb-6">
<p class="text-lg font-hairline text-gray-700">Uptime monitoring</p> <p class="text-lg font-hairline text-gray-700">Uptime monitoring</p>
</div> </div>
<div class="mt-6 text-center leading-normal"> <div class="text-center leading-normal mb-5">
<p class="text-gray-600"> <p class="text-gray-600">
Acquia uses a specially tuned uptime monitoring solution to keep track<br class="hidden md:inline"> Acquia uses a specially tuned uptime monitoring solution to keep track<br class="hidden md:inline">
of whether your Drupal site is really up and running. of whether your Drupal site is really up and running.
</p> </p>
</div> </div>
<div class="mt-5 flex justify-center"> <div class="flex justify-center">
<button type="button" class="btn">Enable Uptime</button> <button type="button" class="btn">Enable Uptime</button>
<button type="button" class="btn is-secondary">Learn more</button> <button type="button" class="btn is-secondary">Learn more</button>
</div> </div>
@ -60,47 +60,47 @@
</div> </div>
<div class="shadow-md overflow-hidden"> <div class="shadow-md overflow-hidden">
<div class="p-3 bg-white border-b-2 border-gray-300"><h2 class="text-lg">Information</h2></div> <div class="bg-white border-b-2 border-gray-300 p-3"><h2 class="text-lg">Information</h2></div>
<div class="px-4 py-6 bg-white"> <div class="bg-white px-4 py-6">
<div class="mb-6 relative"> <div class="mb-6 relative">
<div class="mb-1 text-xs">Git URL</div> <div class="text-xs mb-1">Git URL</div>
<div class="p-3 border border-gray-500 rounded truncate">{{ gitUrl }}</div> <div class="border border-gray-500 rounded p-3 truncate">{{ gitUrl }}</div>
<div class="absolute top-0 right-0"> <div class="absolute top-0 right-0">
<button type="button" class="flex items-center text-xs text-blue-300 hover:underline focus:underline"> <button type="button" class="flex items-center text-xs text-blue-300 hover:underline focus:underline">
<svg class="mr-1 h-3 w-3 fill-current" role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__copy"></use></svg> <svg class="h-3 w-3 fill-current mr-1" role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__copy"></use></svg>
Copy <span class="visuallyhidden">Git URL to your clipboard</span> Copy <span class="visuallyhidden">Git URL to your clipboard</span>
</button> </button>
</div> </div>
</div> </div>
<div class="mb-6 relative"> <div class="mb-6 relative">
<div class="mb-1 text-xs">SSH URL</div> <div class="text-xs mb-1">SSH URL</div>
<div class="p-3 border border-gray-500 rounded truncate">{{ sshUrl }}</div> <div class="border border-gray-500 rounded p-3 truncate">{{ sshUrl }}</div>
<div class="absolute top-0 right-0"> <div class="absolute top-0 right-0">
<button type="button" class="flex items-center text-xs text-blue-300 hover:underline focus:underline"> <button type="button" class="flex items-center text-xs text-blue-300 hover:underline focus:underline">
<svg class="mr-1 h-3 w-3 fill-current" role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__copy"></use></svg> <svg class="h-3 w-3 fill-current mr-1" role="presentation"><use xlink:href="/img/icons.symbol.svg#actions__copy"></use></svg>
Copy <span class="visuallyhidden">SSH URL to your clipboard</span> Copy <span class="visuallyhidden">SSH URL to your clipboard</span>
</button> </button>
</div> </div>
</div> </div>
<div class="-mx-4 -mb-4 flex flex-wrap lg:-mb-6"> <div class="flex flex-wrap -mx-4 -mb-4 lg:-mb-6">
<div class="mb-4 px-4 w-full lg:w-1/2 lg:mb-6"> <div class="w-full lg:w-1/2 px-4 mb-4 lg:mb-6">
<div class="mb-1 text-xs text-gray-600">IP Address</div> <div class="mb-1 text-xs text-gray-600">IP Address</div>
<div>1.2.3.4</div> <div>1.2.3.4</div>
</div> </div>
<div class="mb-4 px-4 w-full lg:w-1/2 lg:mb-6"> <div class="w-full lg:w-1/2 px-4 mb-4 lg:mb-6">
<div class="mb-1 text-xs text-gray-600">Region</div> <div class="mb-1 text-xs text-gray-600">Region</div>
<div>eu-west-1</div> <div>eu-west-1</div>
</div> </div>
<div class="mb-4 px-4 w-full lg:w-1/2 lg:mb-6"> <div class="w-full lg:w-1/2 px-4 mb-4 lg:mb-6">
<div class="mb-1 text-xs text-gray-600">PHP version</div> <div class="mb-1 text-xs text-gray-600">PHP version</div>
<div v-text="phpVersion"></div> <div v-text="phpVersion"></div>
</div> </div>
<div class="mb-4 px-4 w-full lg:w-1/2 lg:mb-6"> <div class="w-full lg:w-1/2 px-4 mb-4 lg:mb-6">
<div class="mb-1 text-xs text-gray-600">Live development mode</div> <div class="mb-1 text-xs text-gray-600">Live development mode</div>
<div>Off</div> <div>Off</div>
</div> </div>
@ -132,9 +132,12 @@
<script> <script>
import ActionCards from '@/components/Environment/ActionCards' import ActionCards from '@/components/Environment/ActionCards'
import ApiClient from '@/api-client.js'
import ApplicationTags from '@/components/Application/ApplicationTags' import ApplicationTags from '@/components/Application/ApplicationTags'
export default { export default {
mixins: [ApiClient],
components: { components: {
ActionCards, ActionCards,
ApplicationTags ApplicationTags
@ -147,11 +150,11 @@ export default {
computed: { computed: {
application: function () { application: function () {
return this.$attrs.applications[this.id] return this.getApplication(this.id)
}, },
environment: function () { environment: function () {
return this.application.environments[this.environmentName] return this.getEnvironment(this.id, this.environmentName)
}, },
gitUrl: function () { gitUrl: function () {
@ -167,7 +170,7 @@ export default {
}, },
phpVersion: function () { phpVersion: function () {
return this.environment.versions.php return this.getVersion('php', this.environment)
} }
} }
} }

View file

@ -1,13 +1,13 @@
<template> <template>
<div> <div>
<div class="w-full fixed top-0 z-30"> <div class="fixed top-0 w-full z-30">
<navbar></navbar> <navbar></navbar>
<title-block :application="application"> <title-block :application="application">
<template slot="right"> <template slot="right">
<div class="flex items-center"> <div class="flex items-center">
<div class="hidden md:block"> <div class="hidden md:block">
<button type="button" class="py-2 px-6 text-sm font-bold text-blue-300 uppercase border border-blue-300 hover:text-blue-400 hover:border-blue-400 focus:border-blue-400"> <button type="button" class="text-sm font-bold text-blue-300 uppercase py-2 px-6 border border-blue-300 hover:text-blue-400 hover:border-blue-400 focus:border-blue-400">
Steps to launch Steps to launch
</button> </button>
</div> </div>
@ -24,20 +24,20 @@
</div> </div>
<div class="mt-48"> <div class="mt-48">
<div class="-mt-3 h-full flex flex-row-reverse"> <div class="-mt-3 flex flex-row-reverse h-full">
<div class="ml-16 p-4 flex-1 overflow-x-hidden lg:p-12 lg:ml-56"> <div class="flex-1 p-4 lg:p-12 ml-16 lg:ml-56 overflow-x-hidden">
<div class="mb-6"> <div class="mb-6">
<div class="mb-2 lg:flex lg:items-baseline"> <div class="lg:flex lg:items-baseline mb-2">
<div class="mr-16 mb-4 lg:mb-0"> <div class="mr-16 mb-4 lg:mb-0">
<h1 class="mb-2 text-4xl font-thin">Environments</h1> <h1 class="text-4xl font-thin mb-2">Environments</h1>
<application-tags :application="application" :types="$attrs.types"/> <application-tags :type="getApplicationType(application)" :level="application.level"></application-tags>
</div> </div>
<div class="flex-1 justify-between items-baseline lg:flex lg:flex-row-reverse"> <div class="lg:flex lg:flex-row-reverse flex-1 justify-between _bg-blue items-baseline">
<div class="w-full lg:w-1/2 xl:w-1/3"> <div class="w-full lg:w-1/2 xl:w-1/3">
<form action="#"> <form action="#">
<input type="text" placeholder="Filter environments" class="py-2 px-3 w-full border border-gray-600 rounded"> <input type="text" placeholder="Filter environments" class="w-full py-2 px-3 border border-gray-600 rounded">
</form> </form>
</div> </div>
@ -48,14 +48,10 @@
</div> </div>
</div> </div>
<div class="spaced-y-6">
<quick-help :hidden="help.hidden"></quick-help> <quick-help :hidden="help.hidden"></quick-help>
<environment-cards :environments="application.environments" :id="id"></environment-cards>
<environment-cards :application="application" :id="id"/>
<task-log :tasks="application.tasks" v-if="application.tasks[0]"></task-log> <task-log :tasks="application.tasks" v-if="application.tasks[0]"></task-log>
</div> </div>
</div>
<sidebar :links="[ <sidebar :links="[
{ title: 'Environments', icon: 'alpha__grid', active: true, disabled: false }, { title: 'Environments', icon: 'alpha__grid', active: true, disabled: false },
@ -69,6 +65,7 @@
</template> </template>
<script> <script>
import ApiClient from '@/api-client.js'
import ApplicationTags from '@/components/Application/ApplicationTags' import ApplicationTags from '@/components/Application/ApplicationTags'
import EnvironmentCards from '@/components/Environment/EnvironmentCards' import EnvironmentCards from '@/components/Environment/EnvironmentCards'
import QuickHelp from '@/components/Environment/QuickHelp' import QuickHelp from '@/components/Environment/QuickHelp'
@ -76,6 +73,8 @@ import TaskLog from '@/components/Environment/TaskLog/TaskLog'
import ToggleHelp from '@/components/Environment/ToggleHelp' import ToggleHelp from '@/components/Environment/ToggleHelp'
export default { export default {
mixins: [ApiClient],
components: { components: {
ApplicationTags, ApplicationTags,
EnvironmentCards, EnvironmentCards,
@ -97,8 +96,8 @@ export default {
}, },
computed: { computed: {
application () { application: function () {
return this.$attrs.applications[this.id] return this.getApplication(this.id)
} }
} }
} }

View file

@ -1,4 +1,4 @@
const { fontFamily, spacing } = require('tailwindcss/defaultTheme') let defaultTheme = require('tailwindcss/defaultTheme')
module.exports = { module.exports = {
theme: { theme: {
@ -42,7 +42,7 @@ module.exports = {
md: '0 0 10px 0 rgba(0,0,0,0.1)' md: '0 0 10px 0 rgba(0,0,0,0.1)'
}, },
fontFamily: { fontFamily: {
sans: ['Proxima Nova', ...fontFamily.sans] sans: ['Proxima Nova', ...defaultTheme.fontFamily.sans]
}, },
fontSize: { fontSize: {
'2xs': '.625rem' // 10px '2xs': '.625rem' // 10px
@ -59,7 +59,7 @@ module.exports = {
} }
}, },
plugins: [ plugins: [
require('tailwindcss-spaced-items')({ values: spacing }), require('tailwindcss-spaced-items')({ values: defaultTheme.spacing }),
require('tailwindcss-visuallyhidden')() require('tailwindcss-visuallyhidden')()
] ]
} }

5
tests/unit/.eslintrc.js Normal file
View file

@ -0,0 +1,5 @@
module.exports = {
env: {
jest: true
}
}

View file

@ -0,0 +1,215 @@
import _ from 'lodash'
const apiClient = require('../../src/api-client')
const methods = apiClient.default.methods
test('it can get the application types', () => {
const data = {
types: {
drupal: {
id: 'drupal',
name: 'Drupal'
},
symfony: {
id: 'symfony',
name: 'Symfony'
}
}
}
methods.setData(data)
expect(methods.getTypes()).toBe(data.types)
})
test('it can get the applications', () => {
const data = {
applications: {
1: {
id: 1,
name: 'Rebuilding Acquia',
machineName: 'rebuildingacquia',
type: 'drupal',
level: 'Enterprise',
environments: {},
tasks: {}
},
2: {
id: 2,
name: 'Oliver Davies',
machineName: 'oliverdavies',
type: 'drupal',
level: 'Professional',
environments: {},
tasks: {}
}
}
}
methods.setData(data)
expect(methods.getApplications()).toBe(data.applications)
})
test('it can get a single application', () => {
const data = {
applications: {
1: {
id: 1,
name: 'Rebuilding Acquia',
machineName: 'rebuildingacquia',
type: 'drupal',
level: 'Enterprise',
environments: {},
tasks: {}
},
2: {
id: 2,
name: 'Oliver Davies',
machineName: 'oliverdavies',
type: 'drupal',
level: 'Professional',
environments: {},
tasks: {}
}
}
}
methods.setData(data)
_.forEach([1, 2], (applicationId) => {
expect(methods.getApplication(applicationId))
.toBe(data.applications[applicationId])
})
})
test('it can get an environment for an application', () => {
const data = {
applications: {
1: {
id: 1,
name: 'Rebuilding Acquia',
machineName: 'rebuildingacquia',
type: 'drupal',
level: 'Enterprise',
environments: {
dev: {
name: 'Dev',
url: 'dev.oliverdavies.uk',
label: 'develop',
versions: {
php: '7.2'
}
},
stage: {
name: 'Stage',
url: 'stg.oliverdavies.uk',
label: 'master',
versions: {
php: '7.2'
}
},
prod: {
name: 'Prod',
url: 'oliverdavies.uk',
label: 'tags/2018-12-21',
versions: {
php: '7.2'
}
},
tasks: {}
}
}
}
}
methods.setData(data)
_.forEach(['dev', 'stage', 'prod'], (environmentName) => {
expect(methods.getEnvironment(1, environmentName))
.toBe(data.applications[1].environments[environmentName])
})
})
test('it can get the type of an application', () => {
const data = {
types: {
drupal: {
id: 'drupal',
name: 'Drupal'
},
symfony: {
id: 'symfony',
name: 'Symfony'
}
}
}
methods.setData(data)
const applications = {
drupal: {
type: 'drupal'
},
symfony: {
type: 'symfony'
}
}
_.forEach(applications, (application, expected) => {
expect(methods.getApplicationType(application)).toBe(data.types[expected])
})
})
test('it can get a version from an environment', () => {
const data = {
applications: {
1: {
id: 1,
name: 'Rebuilding Acquia',
machineName: 'rebuildingacquia',
type: 'drupal',
level: 'Enterprise',
environments: {
dev: {
name: 'Dev',
url: 'dev.rebuilding-acquia.com',
label: 'develop',
versions: {
php: '5.6'
}
},
stage: {
name: 'Stage',
url: 'stg.rebuilding-acquia.com',
label: 'master',
versions: {
php: '7.1'
}
},
prod: {
name: 'Prod',
url: 'rebuilding-acquia.com',
label: 'tags/2018-12-21',
versions: {
php: '7.2'
}
}
}
}
}
}
methods.setData(data)
const expected = {
dev: '5.6',
stage: '7.1',
prod: '7.2'
}
_.forEach(expected, (version, environment) => {
expect(methods.getVersion('php', data.applications[1].environments[environment]))
.toBe(version)
})
})

View file

@ -0,0 +1,47 @@
import AppBreadcrumb from '@/components/AppBreadcrumb.vue'
import { RouterLinkStub, shallowMount } from '@vue/test-utils'
const stubs = {
RouterLink: RouterLinkStub
}
test('it returns default values', () => {
const wrapper = shallowMount(AppBreadcrumb, { stubs })
expect(wrapper.vm.selectedOrganisation).toBe(null)
expect(wrapper.vm.selectedApplication).toBe(null)
expect(wrapper.vm.selectedEnvironment).toBe(null)
})
test('it returns the organisation and application names', () => {
const wrapper = shallowMount(AppBreadcrumb, {
propsData: {
application: {
name: 'Oliver Davies'
}
},
stubs
})
expect(wrapper.vm.selectedOrganisation).toBe('Rebuilding Acquia')
expect(wrapper.vm.selectedApplication).toBe('Oliver Davies')
expect(wrapper.vm.selectedEnvironment).toBe(null)
})
test('it returns the environment name', () => {
const wrapper = shallowMount(AppBreadcrumb, {
propsData: {
application: {
name: 'Oliver Davies'
},
environment: {
name: 'Dev'
}
},
stubs
})
expect(wrapper.vm.selectedOrganisation).toBe('Rebuilding Acquia')
expect(wrapper.vm.selectedApplication).toBe('Oliver Davies')
expect(wrapper.vm.selectedEnvironment).toBe('Dev')
})

10119
yarn.lock

File diff suppressed because it is too large Load diff