Compare commits
200 Commits
my-joy-alp
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
078513f603 | ||
|
a1ff036db1 | ||
|
fc81d666d0 | ||
|
deb1a8436f | ||
|
cad1431e79 | ||
|
2f9d135319 | ||
|
c268d88a4d | ||
|
6698a8eacb | ||
|
1e8e89b3c8 | ||
|
063e40859d | ||
|
fc84358dff | ||
|
6736caaf45 | ||
|
5cb02d709c | ||
|
b66f761a9e | ||
|
8422cdfe8c | ||
|
109988536b | ||
|
d75ae0f14f | ||
|
5c98a4cecb | ||
|
0189822a08 | ||
|
5d46689869 | ||
|
04cb9c32f8 | ||
|
8e6adb1ef4 | ||
|
a1154b2520 | ||
|
155a065281 | ||
|
f388e52549 | ||
|
882085a170 | ||
|
77fd895b6c | ||
|
bd332423be | ||
|
91e1fb192b | ||
|
88fe0ea92d | ||
|
8bb4c31aba | ||
|
867e9b35a0 | ||
|
574c3add4e | ||
|
c233b0d757 | ||
|
84b2d67d2c | ||
|
062ff0ba67 | ||
|
b00baa7028 | ||
|
588b833045 | ||
|
b3edb3aa02 | ||
|
542b491b52 | ||
|
7d8b478d20 | ||
|
7531e503f2 | ||
|
d1b2ba0002 | ||
|
98a8b2eb5b | ||
|
de356e1fbe | ||
|
f9c89cbccc | ||
|
1317894f27 | ||
|
f007889283 | ||
|
831be94521 | ||
|
e321c20906 | ||
|
429ad17262 | ||
|
d591d93547 | ||
|
33d3d4343d | ||
|
32d5cdc293 | ||
|
316773d9b7 | ||
|
0ae1290a5e | ||
|
3f75f812fa | ||
|
25223e7324 | ||
|
7651dff619 | ||
|
be18478f6c | ||
|
b929370124 | ||
|
e61399aa26 | ||
|
b429bc15ec | ||
|
1cd91b234f | ||
|
21317db964 | ||
|
95e420c419 | ||
|
c413677b0e | ||
|
f73a9f4eca | ||
|
682f9db749 | ||
|
5d274a419a | ||
|
0b9c464bb0 | ||
|
e2a3fb73df | ||
|
154b4cfeb6 | ||
|
cac551beaf | ||
|
495af5702d | ||
|
2accde1b4f | ||
|
39d4e0b853 | ||
|
281f9ef60e | ||
|
b51f135703 | ||
|
30b996fc48 | ||
|
023e8242b7 | ||
|
1298652193 | ||
|
86e60e6c5f | ||
|
76c6d18695 | ||
|
2937a37cdb | ||
|
35325cc573 | ||
|
7f0658e246 | ||
|
fab0bbfcc3 | ||
|
f5fbe0a169 | ||
|
9bdf02b1ba | ||
|
55d5d24475 | ||
|
95fc7092de | ||
|
63e7b31a4b | ||
|
5f63a54ba5 | ||
|
1c967f1b9b | ||
|
e95168bbf0 | ||
|
23ddc68ade | ||
|
90d6b31264 | ||
|
2787f051e0 | ||
|
d87c4e017b | ||
|
d1fa4c7950 | ||
|
da59d5e808 | ||
|
b64dbb289d | ||
|
4a7956782b | ||
|
9eabff8a60 | ||
|
4cf50a45c5 | ||
|
81c119a066 | ||
|
ec2f9fc141 | ||
|
b6e7af2422 | ||
|
50faa9d9ac | ||
|
3579c1d8f5 | ||
|
c673822cad | ||
|
11c41272d0 | ||
|
cb0b613034 | ||
|
ae866b401e | ||
|
110c80f65f | ||
|
7f136cc8bc | ||
|
31b46b216f | ||
|
3769e0b5ba | ||
|
bf0a1a0ba0 | ||
|
c6b245aebc | ||
|
d7f83c59fa | ||
|
cd242d7505 | ||
|
9d10a3fa92 | ||
|
abe7a58e3f | ||
|
577df6d187 | ||
|
ce232b7d6a | ||
|
e7cd8f7561 | ||
|
33d6b2ecab | ||
|
ac404ee33b | ||
|
584c8b2028 | ||
|
25c2735bf4 | ||
|
461d8697ca | ||
|
d8618eab30 | ||
|
255fd67f48 | ||
|
6848302022 | ||
|
943ef1daf6 | ||
|
84f6faf693 | ||
|
e405d47cea | ||
|
2b2e431830 | ||
|
e540425472 | ||
|
a6b32631c4 | ||
|
8b67cae8f3 | ||
|
21c0bc70a3 | ||
|
1a8a91e41e | ||
|
99374a8170 | ||
|
71c7743fef | ||
|
311dc6361b | ||
|
de5e7e9663 | ||
|
486fd8664f | ||
|
b72714fc94 | ||
|
5d333c57a6 | ||
|
8b36f40e9d | ||
|
5bd261ad6b | ||
|
f8675e4d8d | ||
|
318a510ee8 | ||
|
05550dd570 | ||
|
7d618d9992 | ||
|
9e67c3ab67 | ||
|
ef61bbce61 | ||
|
d13e23e15d | ||
|
4a8fa480c4 | ||
|
c7043f0b3a | ||
|
6f36e62f92 | ||
|
486d6f54f4 | ||
|
b5a29eb300 | ||
|
ffe9d2321c | ||
|
50c1b6e9b1 | ||
|
01e10c6962 | ||
|
9d7e4e22d5 | ||
|
3bfc7bd14b | ||
|
4593980883 | ||
|
929fcf0ab4 | ||
|
234a481c32 | ||
|
a9c63c20a7 | ||
|
17f58e4089 | ||
|
cb9ee4644e | ||
|
9966143337 | ||
|
d879202b4e | ||
|
bf9a85e4fd | ||
|
eae5345e62 | ||
|
a7ca59e4f2 | ||
|
a1423ea53f | ||
|
11b9f1978a | ||
|
a5ea102ab3 | ||
|
99ceda9033 | ||
|
a5eb142dcc | ||
|
bf5f0463e7 | ||
|
a7283454b8 | ||
|
e7a92656d6 | ||
|
4dc7b9179b | ||
|
29fc12d4dd | ||
|
ca1236fb80 | ||
|
1a53b0753c | ||
|
36756fe75b | ||
|
80af7af938 | ||
|
533e7d875e | ||
|
23e4554bf2 | ||
|
de5fba81e3 | ||
|
5eff7e0883 |
165
.dockerignore
165
.dockerignore
@ -1,165 +0,0 @@
|
||||
### Bower ###
|
||||
bower_components
|
||||
.bower-cache
|
||||
.bower-registry
|
||||
.bower-tmp
|
||||
|
||||
|
||||
### Git ###
|
||||
*.orig
|
||||
|
||||
|
||||
### macOS ###
|
||||
*.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
|
||||
### SublimeText ###
|
||||
# cache files for sublime text
|
||||
*.tmlanguage.cache
|
||||
*.tmPreferences.cache
|
||||
*.stTheme.cache
|
||||
|
||||
# workspace files are user-specific
|
||||
*.sublime-workspace
|
||||
|
||||
# project files should be checked into the repository, unless a significant
|
||||
# proportion of contributors will probably not be using SublimeText
|
||||
# *.sublime-project
|
||||
|
||||
# sftp configuration file
|
||||
sftp-config.json
|
||||
|
||||
# Package control specific files
|
||||
Package Control.last-run
|
||||
Package Control.ca-list
|
||||
Package Control.ca-bundle
|
||||
Package Control.system-ca-bundle
|
||||
Package Control.cache/
|
||||
Package Control.ca-certs/
|
||||
bh_unicode_properties.cache
|
||||
|
||||
# Sublime-github package stores a github token in this file
|
||||
# https://packagecontrol.io/packages/sublime-github
|
||||
GitHub.sublime-settings
|
||||
|
||||
|
||||
### Vim ###
|
||||
# swap
|
||||
[._]*.s[a-w][a-z]
|
||||
[._]s[a-w][a-z]
|
||||
# session
|
||||
Session.vim
|
||||
# temporary
|
||||
.netrwhist
|
||||
*~
|
||||
# auto-generated tag files
|
||||
tags
|
||||
|
||||
|
||||
### Windows ###
|
||||
# Windows image file caches
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
|
||||
### Application Specific ###
|
||||
.env
|
||||
|
||||
# PostCSS
|
||||
*.postcss.js
|
||||
|
||||
/cloudapi-graphql/credentials.json
|
||||
/docker-graphql/config.json
|
||||
tap-xunit
|
||||
/ui/dist
|
||||
|
||||
_todo
|
||||
packages/*/dist
|
||||
packages/*/buid
|
||||
packages/ui-toolkit/styleguide
|
||||
packages/*/node_modules
|
||||
packages/*/Dockerfile
|
||||
legacy
|
||||
|
||||
_env
|
@ -1,4 +1,9 @@
|
||||
packages/*/**
|
||||
prototypes/*/**
|
||||
artifacts
|
||||
reports
|
||||
.nyc_output
|
||||
coverage
|
||||
dist
|
||||
styleguide
|
||||
build
|
||||
consoles/*/lib/app
|
||||
node_modules
|
@ -1,8 +1,10 @@
|
||||
{
|
||||
"extends": "joyent-portal",
|
||||
"rules": {
|
||||
"jsx-a11y/href-no-hash": 0,
|
||||
"no-console": 1,
|
||||
"new-cap": 0,
|
||||
"no-console": 0
|
||||
"jsx-a11y/href-no-hash": 0,
|
||||
"no-negated-condition": 1,
|
||||
"camelcase": 1
|
||||
}
|
||||
}
|
||||
|
4
.github/COMMIT_GUIDELINES.md
vendored
4
.github/COMMIT_GUIDELINES.md
vendored
@ -17,9 +17,9 @@ perf
|
||||
refactor
|
||||
revert
|
||||
style
|
||||
test
|
||||
test
|
||||
```
|
||||
|
||||
And where scope is one of ui-toolkit, my-joy-beta, cloudapi-gql, boilerplate, and create-instance.
|
||||
|
||||
*The recommended method to commit should be by running npm run commit.*
|
||||
_The recommended method to commit should be by running npm run commit._
|
||||
|
16
.github/ISSUE_TEMPLATE.md
vendored
16
.github/ISSUE_TEMPLATE.md
vendored
@ -1,27 +1,17 @@
|
||||
## I'm submitting a...
|
||||
|
||||
- [ ] bug report
|
||||
- [ ] feature request
|
||||
- [ ] design request
|
||||
* [ ] bug report
|
||||
* [ ] feature request
|
||||
* [ ] design request
|
||||
|
||||
## What is the current behavior?
|
||||
|
||||
|
||||
|
||||
## If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem
|
||||
|
||||
|
||||
|
||||
## What is the expected behavior?
|
||||
|
||||
|
||||
|
||||
## What is the motivation / use case for changing the behavior?
|
||||
|
||||
|
||||
|
||||
## If the current behavior is a bug, please provide your browser
|
||||
|
||||
|
||||
|
||||
## Other information
|
||||
|
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,22 +1,14 @@
|
||||
**Please check if the PR fulfills these requirements**
|
||||
- [ ] The commit message follows our [guidelines](https://github.com/yldio/joyent-portal/blob/master/.github/COMMIT_GUIDELINES.md)
|
||||
- [ ] Tests for the changes have been added (for bug fixes / features)
|
||||
|
||||
* [ ] The commit message follows our [guidelines](https://github.com/yldio/joyent-portal/blob/master/.github/COMMIT_GUIDELINES.md)
|
||||
* [ ] Tests for the changes have been added (for bug fixes / features)
|
||||
|
||||
**What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
|
||||
|
||||
|
||||
|
||||
**Does this PR close an issue?** (If not please create one)
|
||||
|
||||
|
||||
|
||||
**What is the new behavior (if this is a feature change)?**
|
||||
|
||||
|
||||
|
||||
**Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?)
|
||||
|
||||
|
||||
|
||||
**Other information**
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -165,3 +165,5 @@ prototypes/*/package-lock.json
|
||||
|
||||
_env*
|
||||
keys*
|
||||
/packages/*/public/index.html
|
||||
/consoles/*/public/index.html
|
||||
|
@ -1,11 +1,14 @@
|
||||
{
|
||||
"ignoreDevDependencies": true,
|
||||
"allowedPackages": [{
|
||||
"name": "colors",
|
||||
"extraFieldsForDocumentation": "Licence is MIT, but was not found by tool: https://github.com/Marak/colors.js/blob/v0.5.1/MIT-LICENSE.txt",
|
||||
"date": "17 January 2017",
|
||||
"reason": "MIT Licenced"
|
||||
}],
|
||||
"allowedPackages": [
|
||||
{
|
||||
"name": "colors",
|
||||
"extraFieldsForDocumentation":
|
||||
"Licence is MIT, but was not found by tool: https://github.com/Marak/colors.js/blob/v0.5.1/MIT-LICENSE.txt",
|
||||
"date": "17 January 2017",
|
||||
"reason": "MIT Licenced"
|
||||
}
|
||||
],
|
||||
"allowedLicenses": [
|
||||
"CC-BY-4.0",
|
||||
"CC0-1.0",
|
||||
|
29
.prettierignore
Normal file
29
.prettierignore
Normal file
@ -0,0 +1,29 @@
|
||||
.git/*
|
||||
.DS_Store
|
||||
|
||||
license
|
||||
yarn.lock
|
||||
.travis.yml
|
||||
|
||||
.yarnclean
|
||||
.eslintignore
|
||||
.prettierignore
|
||||
.npmignore
|
||||
.gitignore
|
||||
.dockerignore
|
||||
|
||||
dist
|
||||
build
|
||||
packages/*/lib/app
|
||||
consoles/*/lib/app
|
||||
|
||||
*.ico
|
||||
*.html
|
||||
*.log
|
||||
*.svg
|
||||
*.map
|
||||
*.png
|
||||
*.snap
|
||||
*.ttf
|
||||
*.sh
|
||||
*.txt
|
31
.prettierrc
Normal file
31
.prettierrc
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"bracketSpacing": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"printWidth": 80,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "none",
|
||||
"useTabs": false,
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
".prettierrc",
|
||||
".eslintrc",
|
||||
".babelrc",
|
||||
".tern-project",
|
||||
".stylelintrc",
|
||||
".lighthouserc"
|
||||
],
|
||||
"options": {
|
||||
"parser": "json"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["package.json"],
|
||||
"options": {
|
||||
"printWidth": 180
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -1,8 +1,5 @@
|
||||
{
|
||||
"libs": [
|
||||
"ecmascript",
|
||||
"browser"
|
||||
],
|
||||
"libs": ["ecmascript", "browser"],
|
||||
"plugins": {
|
||||
"doc_comment": true,
|
||||
"local-scope": true,
|
||||
@ -12,4 +9,4 @@
|
||||
"configPath": "./webpack/index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '8'
|
||||
- '9'
|
||||
script:
|
||||
- npm run test-ci
|
||||
- yarn run test:ci
|
||||
|
28
.vscode/launch.json
vendored
28
.vscode/launch.json
vendored
@ -1,15 +1,15 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:3069/",
|
||||
"webRoot": "${workspaceRoot}"
|
||||
}
|
||||
]
|
||||
}
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:3069/",
|
||||
"webRoot": "${workspaceRoot}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
1
.vscode/settings.json
vendored
Normal file
1
.vscode/settings.json
vendored
Normal file
@ -0,0 +1 @@
|
||||
{}
|
8
bundle/.env.js
Normal file
8
bundle/.env.js
Normal file
@ -0,0 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
const { homedir } = require('os');
|
||||
const { join } = require('path');
|
||||
|
||||
const { SDC_KEY_PATH } = process.env;
|
||||
|
||||
process.env.SDC_KEY_PATH = SDC_KEY_PATH || join(homedir(), './.ssh/id_rsa');
|
20
bundle/data/account.js
Normal file
20
bundle/data/account.js
Normal file
@ -0,0 +1,20 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: 'Logout',
|
||||
slug: 'logout',
|
||||
description: 'Do the daggum logout',
|
||||
url: '/logout'
|
||||
},
|
||||
{
|
||||
name: 'Change Password',
|
||||
slug: 'change-password',
|
||||
description: 'Change yer own password',
|
||||
url: '/password'
|
||||
},
|
||||
{
|
||||
name: 'Account',
|
||||
slug: 'account',
|
||||
description: 'Your account information',
|
||||
url: '/account'
|
||||
}
|
||||
];
|
89
bundle/data/categories.js
Normal file
89
bundle/data/categories.js
Normal file
@ -0,0 +1,89 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: 'Compute',
|
||||
services: [
|
||||
{
|
||||
name: 'VMs & Containers',
|
||||
slug: 'instances',
|
||||
description: 'Run VMs and bare metal containers'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Network',
|
||||
services: [
|
||||
{
|
||||
name: 'VLANs',
|
||||
slug: 'vlans',
|
||||
description: 'Wire your application your way'
|
||||
},
|
||||
{
|
||||
name: 'Subnets',
|
||||
slug: 'subnets',
|
||||
description: 'A network for everything'
|
||||
},
|
||||
{
|
||||
name: 'Firewall Rules',
|
||||
slug: 'firewall',
|
||||
description: 'Control the bits coming and going'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Storage',
|
||||
services: [
|
||||
{
|
||||
name: 'Triton Object Storage',
|
||||
slug: 'object-storage',
|
||||
description: 'Modern cloud object storage',
|
||||
tags: ["'note'='was Manta'"]
|
||||
},
|
||||
{
|
||||
name: 'S3 Compatibility Bridge',
|
||||
slug: 's3-bridge',
|
||||
description: 'Modern storage, legacy compability'
|
||||
},
|
||||
{
|
||||
name: 'Triton Volumes',
|
||||
slug: 'volumes',
|
||||
description: 'Network filesystems for your apps',
|
||||
tags: ["'is-new'='true'"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Access',
|
||||
services: [
|
||||
{
|
||||
name: 'Role Based Access Control',
|
||||
slug: 'rbac',
|
||||
description: 'Manage users within your account'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Help & Support',
|
||||
services: [
|
||||
{
|
||||
name: 'Service Status',
|
||||
slug: 'status',
|
||||
description: 'Find out about the status of our services'
|
||||
},
|
||||
{
|
||||
name: 'Contact Support',
|
||||
slug: 'contact-support',
|
||||
description: 'Chat to us via phone or email'
|
||||
},
|
||||
{
|
||||
name: 'Support Plans',
|
||||
slug: 'support-plans',
|
||||
description: 'Write here about Support Plans'
|
||||
},
|
||||
{
|
||||
name: 'Getting Started',
|
||||
slug: 'getting-started',
|
||||
description: 'Write here about Getting Started'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
86
bundle/data/regions.js
Normal file
86
bundle/data/regions.js
Normal file
@ -0,0 +1,86 @@
|
||||
module.exports = [
|
||||
{
|
||||
name: 'Ashburn, Virginia, USA',
|
||||
continent: 'NORTH_AMERICA',
|
||||
datacenters: [
|
||||
{
|
||||
name: 'us-east-1',
|
||||
url: 'http://localhost'
|
||||
},
|
||||
{
|
||||
name: 'us-east-2',
|
||||
url: 'http://localhost'
|
||||
},
|
||||
{
|
||||
name: 'us-east-3',
|
||||
url: 'http://localhost'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Las Vegas, Nevada, USA',
|
||||
continent: 'NORTH_AMERICA',
|
||||
datacenters: [
|
||||
{
|
||||
name: 'us-sw-1',
|
||||
url: 'http://localhost'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Emeryville, California, USA',
|
||||
continent: 'NORTH_AMERICA',
|
||||
datacenters: [
|
||||
{
|
||||
name: 'us-west-1',
|
||||
url: 'http://localhost'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Amsterdam, Netherlands',
|
||||
continent: 'EUROPE',
|
||||
datacenters: [
|
||||
{
|
||||
name: 'us-ams-1',
|
||||
url: 'http://localhost'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Singapore',
|
||||
continent: 'ASIA',
|
||||
datacenters: [
|
||||
{
|
||||
name: 'ap-sg-1',
|
||||
url: 'http://localhost'
|
||||
},
|
||||
{
|
||||
name: 'ap-sg-2',
|
||||
url: 'http://localhost'
|
||||
},
|
||||
{
|
||||
name: 'ap-sg-3',
|
||||
url: 'http://localhost'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Seoul, South Korea',
|
||||
continent: 'ASIA',
|
||||
datacenters: [
|
||||
{
|
||||
name: 'ap-kr-1',
|
||||
url: 'http://localhost'
|
||||
},
|
||||
{
|
||||
name: 'ap-kr-2',
|
||||
url: 'http://localhost'
|
||||
},
|
||||
{
|
||||
name: 'ap-kr-3',
|
||||
url: 'http://localhost'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
127
bundle/index.js
127
bundle/index.js
@ -1,85 +1,72 @@
|
||||
// Requires .env.js file with the following exports:
|
||||
// SDC_URL, SDC_KEY_ID, SDC_KEY_PATH
|
||||
require('./.env.js');
|
||||
|
||||
|
||||
const Brule = require('brule');
|
||||
const Hapi = require('hapi');
|
||||
const Inert = require('inert');
|
||||
const Main = require('apr-main');
|
||||
const Rollover = require('rollover');
|
||||
const Hapi = require('hapi');
|
||||
const H2O2 = require('h2o2');
|
||||
const Execa = require('execa');
|
||||
const Path = require('path');
|
||||
const Fs = require('fs');
|
||||
|
||||
const Sso = require('minio-proto-auth');
|
||||
const Ui = require('my-joy-beta');
|
||||
const Nav = require('joyent-navigation');
|
||||
const Api = require('cloudapi-gql');
|
||||
const { PORT = 4000 } = process.env;
|
||||
const ROOT = Path.join(__dirname, 'src');
|
||||
|
||||
const {
|
||||
PORT = 3069,
|
||||
COOKIE_PASSWORD,
|
||||
COOKIE_DOMAIN,
|
||||
SDC_KEY_PATH,
|
||||
SDC_ACCOUNT,
|
||||
SDC_KEY_ID,
|
||||
SDC_URL,
|
||||
BASE_URL = `http://0.0.0.0:${PORT}`,
|
||||
ROLLBAR_SERVER_TOKEN,
|
||||
NODE_ENV = 'development'
|
||||
} = process.env;
|
||||
const calcPort = i => Number(PORT) + Number(i) + 1;
|
||||
|
||||
const server = Hapi.server({
|
||||
port: PORT,
|
||||
host: '127.0.0.1'
|
||||
const namespaces = Fs.readdirSync(ROOT)
|
||||
.filter(filename => /.js$/.test(filename))
|
||||
.map(filename => filename.replace(/.js$/, ''))
|
||||
.filter(filename => !['index', 'server'].includes(filename));
|
||||
|
||||
const routes = namespaces.map((namespace, i) => ({
|
||||
method: '*',
|
||||
path: `/${namespace}/{params*}`,
|
||||
handler: {
|
||||
proxy: {
|
||||
uri: `{protocol}://0.0.0.0:${calcPort(i)}/${namespace}/{params}`
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
namespaces.forEach((namespace, i) => {
|
||||
const child = Execa('node', [namespace], {
|
||||
cwd: ROOT,
|
||||
cleanup: true,
|
||||
env: Object.assign({}, process.env, {
|
||||
PORT: calcPort(i),
|
||||
PREFIX: namespace
|
||||
})
|
||||
});
|
||||
|
||||
child.stdout.pipe(process.stdout);
|
||||
child.stderr.pipe(process.stderr);
|
||||
});
|
||||
|
||||
Main(async () => {
|
||||
await server.register([
|
||||
// {
|
||||
// plugin: Rollover,
|
||||
// options: {
|
||||
// rollbar: {
|
||||
// accessToken: ROLLBAR_SERVER_TOKEN,
|
||||
// reportLevel: 'error'
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
{
|
||||
plugin: Brule,
|
||||
options: {
|
||||
auth: false
|
||||
const server = Hapi.server({
|
||||
port: PORT,
|
||||
routes: {
|
||||
cors: {
|
||||
origin: ['*'],
|
||||
credentials: true,
|
||||
additionalHeaders: ['Cookie', 'X-CSRF-Token']
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: Sso,
|
||||
options: {
|
||||
cookie: {
|
||||
password: COOKIE_PASSWORD,
|
||||
domain: COOKIE_DOMAIN,
|
||||
isSecure: false,
|
||||
isHttpOnly: true,
|
||||
ttl: 1000 * 60 * 60 // 1 hour
|
||||
},
|
||||
sso: {
|
||||
keyPath: SDC_KEY_PATH,
|
||||
keyId: '/' + SDC_ACCOUNT + '/keys/' + SDC_KEY_ID,
|
||||
apiBaseUrl: SDC_URL,
|
||||
url: 'https://sso.joyent.com/login',
|
||||
permissions: { cloudapi: ['/my/*'] },
|
||||
baseUrl: BASE_URL,
|
||||
isDev: NODE_ENV === 'development'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: Nav
|
||||
},
|
||||
{
|
||||
plugin: Ui
|
||||
},
|
||||
{
|
||||
plugin: Api
|
||||
debug: {
|
||||
log: ['error'],
|
||||
request: ['error']
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
server.auth.default('sso');
|
||||
await server.register({
|
||||
plugin: H2O2
|
||||
});
|
||||
|
||||
routes.map(route => server.route(route));
|
||||
|
||||
await server.start();
|
||||
console.log(`server started at http://localhost:${server.info.port}`);
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`server started at http://0.0.0.0:${server.info.port}`);
|
||||
});
|
||||
|
@ -4,22 +4,28 @@
|
||||
"private": true,
|
||||
"license": "MPL-2.0",
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=development PORT=3069 REACT_APP_GQL_PORT=3069 REACT_APP_GQL_PROTOCOL=http node -r ./_env.js index.js",
|
||||
"lint-ci": "echo 0",
|
||||
"lint": "echo 0",
|
||||
"test-ci": "echo 0",
|
||||
"dev": "NODE_ENV=development PORT=4000 node index.js",
|
||||
"build:test": "echo 0",
|
||||
"build:lib": "echo 0",
|
||||
"build:bundle": "echo 0",
|
||||
"prepublish": "echo 0",
|
||||
"test": "echo 0",
|
||||
"prepublish": "echo 0"
|
||||
"test:ci": "echo 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"apr-main": "^4.0.3",
|
||||
"brule": "^3.1.0",
|
||||
"cloudapi-gql": "^4.5.1",
|
||||
"hapi": "^17.2.0",
|
||||
"inert": "^5.1.0",
|
||||
"joyent-navigation": "^1.0.0",
|
||||
"minio-proto-auth": "^1.1.0",
|
||||
"my-joy-beta": "^1.0.0",
|
||||
"rollover": "^1.0.0"
|
||||
"cloudapi-gql": "^8.0.0",
|
||||
"execa": "^0.10.0",
|
||||
"graphi": "^5.7.0",
|
||||
"h2o2": "^8.1.2",
|
||||
"hapi": "^17.5.0",
|
||||
"hapi-triton-auth": "^3.0.0",
|
||||
"hapi-webconsole-nav": "^2.1.0",
|
||||
"my-joy-images": "*",
|
||||
"my-joy-instances": "*",
|
||||
"my-joy-navigation": "*",
|
||||
"my-joy-service-groups": "*",
|
||||
"my-joy-templates": "*",
|
||||
"tsg-graphql": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e -o pipefail
|
||||
|
||||
TRITON_ACCOUNT=$(triton account get | awk -F": " '/id:/{print $2}')
|
||||
TRITON_DC=$(triton profile get | awk -F"/" '/url:/{print $3}' | awk -F'.' '{print $1}')
|
||||
|
||||
DEFAULT_DOMAIN=${TRITON_ACCOUNT}.${TRITON_DC}.cns.triton.zone
|
||||
|
||||
read -p "Enter the domain name you plan to use for this key [$DEFAULT_DOMAIN]: " domain
|
||||
domain="${domain:-$DEFAULT_DOMAIN}"
|
||||
echo -n "Enter the password to use for the key: "
|
||||
read -s password
|
||||
echo
|
||||
echo "Generating key for $domain"
|
||||
|
||||
|
||||
|
||||
keys_path=keys-$domain
|
||||
mkdir -p $keys_path
|
||||
|
||||
openssl genrsa -aes256 -passout pass:$password -out $keys_path/ca.key 4096
|
||||
chmod 400 $keys_path/ca.key
|
||||
openssl req -new -x509 -sha256 -days 730 -key $keys_path/ca.key -out $keys_path/ca.crt -passin pass:$password -subj "/CN=copilot"
|
||||
chmod 444 $keys_path/ca.crt
|
||||
|
||||
|
||||
openssl genrsa -out $keys_path/server.key 2048
|
||||
chmod 400 $keys_path/server.key
|
||||
openssl req -new -key $keys_path/server.key -sha256 -out $keys_path/server.csr -passin pass:$password -subj "/CN=$domain"
|
||||
openssl x509 -req -days 365 -sha256 -in $keys_path/server.csr -passin pass:$password -CA $keys_path/ca.crt -CAkey $keys_path/ca.key -set_serial 1 -out $keys_path/server.crt
|
||||
chmod 444 $keys_path/server.crt
|
||||
|
||||
openssl genrsa -out $keys_path/client.key 2048
|
||||
openssl req -new -key $keys_path/client.key -out $keys_path/client.csr -subj "/CN=$domain"
|
||||
openssl x509 -req -days 365 -sha256 -in $keys_path/client.csr -CA $keys_path/ca.crt -CAkey $keys_path/ca.key -set_serial 2 -out $keys_path/client.crt -passin pass:$password
|
||||
openssl pkcs12 -export -clcerts -in $keys_path/client.crt -inkey $keys_path/client.key -out $keys_path/client.p12 -passout pass:$password
|
||||
|
||||
# open $keys_path/client.p12 &
|
||||
echo
|
||||
echo "You can complete setup by running './setup.sh ~/path/to/TRITON_PRIVATE_KEY $keys_path/ca.crt $keys_path/server.key $keys_path/server.crt'"
|
@ -1,235 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e -o pipefail
|
||||
|
||||
help() {
|
||||
echo
|
||||
echo 'Usage ./setup.sh ~/path/to/TRITON_PRIVATE_KEY ~/path/to/CA_CRT ~/path/to/SERVER_KEY ~/path/to/SERVER_CRT'
|
||||
echo
|
||||
echo 'Checks that your Triton and Docker environment is sane and configures'
|
||||
echo 'an environment file to use.'
|
||||
echo
|
||||
echo 'TRITON_PRIVATE_KEY is the filesystem path to an SSH private key'
|
||||
echo 'used to connect to Triton.'
|
||||
echo
|
||||
echo 'CA_CRT is the filesystem path to a certificate authority crt file.'
|
||||
echo
|
||||
echo 'SERVER_KEY is the filesystem path to a TLS server key file.'
|
||||
echo
|
||||
echo 'SERVER_CRT is the filesystem path to a TLS server crt file.'
|
||||
echo
|
||||
}
|
||||
|
||||
# Check for correct configuration
|
||||
check() {
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
tput rev # reverse
|
||||
tput bold # bold
|
||||
echo 'Please provide a path to a SSH private key to access Triton.'
|
||||
tput sgr0 # clear
|
||||
|
||||
help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$1" ]; then
|
||||
tput rev # reverse
|
||||
tput bold # bold
|
||||
echo 'SSH private key for Triton is unreadable.'
|
||||
tput sgr0 # clear
|
||||
|
||||
help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Assign args to named vars
|
||||
TRITON_PRIVATE_KEY_PATH=$1
|
||||
|
||||
|
||||
if [ -z "$2" ]; then
|
||||
tput rev # reverse
|
||||
tput bold # bold
|
||||
echo 'Please provide a path to the NGINX CA crt file.'
|
||||
tput sgr0 # clear
|
||||
|
||||
help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$2" ]; then
|
||||
tput rev # reverse
|
||||
tput bold # bold
|
||||
echo 'CA certificate for NGINX is unreadable.'
|
||||
tput sgr0 # clear
|
||||
|
||||
help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NGINX_CA_CRT_PATH=$2
|
||||
|
||||
|
||||
if [ -z "$3" ]; then
|
||||
tput rev # reverse
|
||||
tput bold # bold
|
||||
echo 'Please provide a path to the server key file.'
|
||||
tput sgr0 # clear
|
||||
|
||||
help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$3" ]; then
|
||||
tput rev # reverse
|
||||
tput bold # bold
|
||||
echo 'Server key file for NGINX is unreadable.'
|
||||
tput sgr0 # clear
|
||||
|
||||
help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NGINX_SERVER_KEY_PATH=$3
|
||||
|
||||
|
||||
if [ -z "$4" ]; then
|
||||
tput rev # reverse
|
||||
tput bold # bold
|
||||
echo 'Please provide a path to the server crt file.'
|
||||
tput sgr0 # clear
|
||||
|
||||
help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "$4" ]; then
|
||||
tput rev # reverse
|
||||
tput bold # bold
|
||||
echo 'Server crt file for NGINX is unreadable.'
|
||||
tput sgr0 # clear
|
||||
|
||||
help
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NGINX_SERVER_CRT_PATH=$4
|
||||
|
||||
command -v docker >/dev/null 2>&1 || {
|
||||
echo
|
||||
tput rev # reverse
|
||||
tput bold # bold
|
||||
echo 'Docker is required, but does not appear to be installed.'
|
||||
tput sgr0 # clear
|
||||
echo 'See https://docs.joyent.com/public-cloud/api-access/docker'
|
||||
exit 1
|
||||
}
|
||||
|
||||
command -v triton >/dev/null 2>&1 || {
|
||||
echo
|
||||
tput rev # reverse
|
||||
tput bold # bold
|
||||
echo 'Error! Joyent Triton CLI is required, but does not appear to be installed.'
|
||||
tput sgr0 # clear
|
||||
echo 'See https://www.joyent.com/blog/introducing-the-triton-command-line-tool'
|
||||
exit 1
|
||||
}
|
||||
|
||||
TRITON_USER=$(triton profile get | awk -F": " '/account:/{print $2}')
|
||||
TRITON_DC=$(triton profile get | awk -F"/" '/url:/{print $3}' | awk -F'.' '{print $1}')
|
||||
TRITON_ACCOUNT=$(triton account get | awk -F": " '/id:/{print $2}')
|
||||
|
||||
SDC_URL=$(triton env | grep SDC_URL | awk -F"=" '{print $2}' | awk -F"\"" '{print $2}')
|
||||
SDC_ACCOUNT=$(triton env | grep SDC_ACCOUNT | awk -F"=" '{print $2}' | awk -F"\"" '{print $2}')
|
||||
SDC_KEY_ID=$(triton env | grep SDC_KEY_ID | awk -F"=" '{print $2}' | awk -F"\"" '{print $2}')
|
||||
|
||||
DOCKER_CERT_PATH=$(triton env | grep DOCKER_CERT_PATH | awk -F"=" '{print $2}')
|
||||
DOCKER_HOST=$(triton env | grep DOCKER_HOST | awk -F"=" '{print $2}')
|
||||
|
||||
rm _env_consul
|
||||
rm _env_mysql
|
||||
rm _env
|
||||
|
||||
echo MYSQL_DATABASE=bridge-db >> _env_mysql
|
||||
echo 'MYSQL_ROOT_PASSWORD='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 12) >> _env_mysql
|
||||
echo MYSQL_USER=bridge-user >> _env_mysql
|
||||
echo 'MYSQL_PASSWORD='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 8) >> _env_mysql
|
||||
|
||||
echo >> _env_mysql
|
||||
|
||||
echo '# Consul discovery via Triton CNS' >> _env_consul
|
||||
echo CONSUL=bridge-consul.svc.${TRITON_ACCOUNT}.${TRITON_DC}.cns.joyent.com >> _env_consul
|
||||
echo CONSUL_AGENT=1 >> _env_consul
|
||||
echo >> _env_consul
|
||||
|
||||
TRITON_CREDS_PATH=/root/.triton
|
||||
|
||||
echo '# Allowed list of account Ids who can access the site' >> _env
|
||||
echo ALLOWED_ACCOUNTS=${TRITON_ACCOUNT} >> _env
|
||||
echo >> _env
|
||||
|
||||
echo '# Site URL' >> _env
|
||||
echo BASE_URL=https://bridge.svc.${TRITON_ACCOUNT}.${TRITON_DC}.cns.triton.zone >> _env
|
||||
echo COOKIE_DOMAIN=triton.zone >> _env
|
||||
echo >> _env
|
||||
|
||||
echo '# MySQL via Triton CNS' >> _env
|
||||
echo MYSQL_HOST=bridge-mysql.svc.${TRITON_ACCOUNT}.${TRITON_DC}.cns.joyent.com >> _env
|
||||
echo >> _env
|
||||
|
||||
echo PORT=8080 >> _env
|
||||
echo 'COOKIE_PASSWORD='$(cat /dev/urandom | LC_ALL=C tr -dc 'A-Za-z0-9' | head -c 36) >> _env
|
||||
echo SDC_KEY_PATH=/root/.ssh/id_rsa >> _env
|
||||
echo DOCKER_CERT_PATH=${TRITON_CREDS_PATH} >> _env
|
||||
echo TRITON_CREDS_PATH=${TRITON_CREDS_PATH} >> _env
|
||||
echo DOCKER_TLS_VERIFY=1 >> _env
|
||||
echo DOCKER_HOST=${DOCKER_HOST} >> _env
|
||||
echo SDC_URL=${SDC_URL} >> _env
|
||||
echo SDC_ACCOUNT=${SDC_ACCOUNT} >> _env
|
||||
echo SDC_KEY_ID=${SDC_KEY_ID} >> _env
|
||||
echo CONSUL=bridge-consul.svc.${TRITON_ACCOUNT}.${TRITON_DC}.cns.joyent.com >> _env
|
||||
|
||||
echo TRITON_CA=$(cat "${DOCKER_CERT_PATH}"/ca.pem | tr '\n' '#') >> _env
|
||||
echo TRITON_CA_PATH=${TRITON_CREDS_PATH}/ca.pem >> _env
|
||||
echo TRITON_KEY=$(cat "${DOCKER_CERT_PATH}"/key.pem | tr '\n' '#') >> _env
|
||||
echo TRITON_KEY_PATH=${TRITON_CREDS_PATH}/key.pem >> _env
|
||||
echo TRITON_CERT=$(cat "${DOCKER_CERT_PATH}"/cert.pem | tr '\n' '#') >> _env
|
||||
echo TRITON_CERT_PATH=${TRITON_CREDS_PATH}/cert.pem >> _env
|
||||
|
||||
echo SDC_KEY=$(cat "${TRITON_PRIVATE_KEY_PATH}" | tr '\n' '#') >> _env
|
||||
echo SDC_KEY_PUB=$(cat "${TRITON_PRIVATE_KEY_PATH}".pub | tr '\n' '#') >> _env
|
||||
|
||||
echo NGINX_CA_CRT=$(cat "${NGINX_CA_CRT_PATH}" | tr '\n' '#') >> _env
|
||||
echo NGINX_SERVER_KEY=$(cat "${NGINX_SERVER_KEY_PATH}" | tr '\n' '#') >> _env
|
||||
echo NGINX_SERVER_CRT=$(cat "${NGINX_SERVER_CRT_PATH}" | tr '\n' '#') >> _env
|
||||
|
||||
echo >> _env
|
||||
}
|
||||
|
||||
# ---------------------------------------------------
|
||||
# parse arguments
|
||||
|
||||
# Get function list
|
||||
funcs=($(declare -F -p | cut -d " " -f 3))
|
||||
|
||||
until
|
||||
if [ ! -z "$1" ]; then
|
||||
# check if the first arg is a function in this file, or use a default
|
||||
if [[ " ${funcs[@]} " =~ " $1 " ]]; then
|
||||
cmd=$1
|
||||
shift 1
|
||||
else
|
||||
cmd="check"
|
||||
fi
|
||||
|
||||
$cmd "$@"
|
||||
if [ $? == 127 ]; then
|
||||
help
|
||||
fi
|
||||
|
||||
exit
|
||||
else
|
||||
help
|
||||
fi
|
||||
do
|
||||
echo
|
||||
done
|
64
bundle/src/images.js
Normal file
64
bundle/src/images.js
Normal file
@ -0,0 +1,64 @@
|
||||
require('../.env.js');
|
||||
|
||||
const Main = require('apr-main');
|
||||
const CloudApiGql = require('cloudapi-gql');
|
||||
const Graphi = require('graphi');
|
||||
const Url = require('url');
|
||||
|
||||
const Server = require('./server');
|
||||
const Ui = require('my-joy-images');
|
||||
|
||||
const {
|
||||
PORT = 4003,
|
||||
BASE_URL = `http://0.0.0.0:${PORT}`,
|
||||
PREFIX = 'images',
|
||||
DC_NAME,
|
||||
SDC_URL,
|
||||
SDC_KEY_PATH,
|
||||
SDC_ACCOUNT,
|
||||
SDC_KEY_ID
|
||||
} = process.env;
|
||||
|
||||
const dcName = DC_NAME || Url.parse(SDC_URL).host.split('.')[0];
|
||||
const keyPath = SDC_KEY_PATH;
|
||||
const keyId = `/${SDC_ACCOUNT}/keys/${SDC_KEY_ID}`;
|
||||
const apiBaseUrl = SDC_URL;
|
||||
|
||||
Main(async () => {
|
||||
const server = await Server({
|
||||
PORT,
|
||||
BASE_URL
|
||||
});
|
||||
|
||||
await server.register([
|
||||
{
|
||||
plugin: Graphi,
|
||||
options: {
|
||||
graphqlPath: '/graphql',
|
||||
graphiqlPath: '/graphiql',
|
||||
authStrategy: 'sso'
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: CloudApiGql,
|
||||
options: {
|
||||
authStrategy: 'sso',
|
||||
keyPath,
|
||||
keyId,
|
||||
apiBaseUrl,
|
||||
dcName
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: Ui
|
||||
}
|
||||
]);
|
||||
|
||||
await server.start();
|
||||
});
|
64
bundle/src/instances.js
Normal file
64
bundle/src/instances.js
Normal file
@ -0,0 +1,64 @@
|
||||
require('../.env.js');
|
||||
|
||||
const Main = require('apr-main');
|
||||
const CloudApiGql = require('cloudapi-gql');
|
||||
const Graphi = require('graphi');
|
||||
const Url = require('url');
|
||||
|
||||
const Server = require('./server');
|
||||
const Ui = require('my-joy-instances');
|
||||
|
||||
const {
|
||||
PORT = 4002,
|
||||
BASE_URL = `http://0.0.0.0:${PORT}`,
|
||||
PREFIX = 'instances',
|
||||
DC_NAME,
|
||||
SDC_URL,
|
||||
SDC_KEY_PATH,
|
||||
SDC_ACCOUNT,
|
||||
SDC_KEY_ID
|
||||
} = process.env;
|
||||
|
||||
const dcName = DC_NAME || Url.parse(SDC_URL).host.split('.')[0];
|
||||
const keyPath = SDC_KEY_PATH;
|
||||
const keyId = `/${SDC_ACCOUNT}/keys/${SDC_KEY_ID}`;
|
||||
const apiBaseUrl = SDC_URL;
|
||||
|
||||
Main(async () => {
|
||||
const server = await Server({
|
||||
PORT,
|
||||
BASE_URL
|
||||
});
|
||||
|
||||
await server.register([
|
||||
{
|
||||
plugin: Graphi,
|
||||
options: {
|
||||
graphqlPath: '/graphql',
|
||||
graphiqlPath: '/graphiql',
|
||||
authStrategy: 'sso'
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: CloudApiGql,
|
||||
options: {
|
||||
authStrategy: 'sso',
|
||||
keyPath,
|
||||
keyId,
|
||||
apiBaseUrl,
|
||||
dcName
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: Ui
|
||||
}
|
||||
]);
|
||||
|
||||
await server.start();
|
||||
});
|
72
bundle/src/navigation.js
Normal file
72
bundle/src/navigation.js
Normal file
@ -0,0 +1,72 @@
|
||||
require('../.env.js');
|
||||
|
||||
const Main = require('apr-main');
|
||||
const Nav = require('hapi-webconsole-nav');
|
||||
const Graphi = require('graphi');
|
||||
const Url = require('url');
|
||||
|
||||
const Server = require('./server');
|
||||
const Ui = require('my-joy-navigation');
|
||||
|
||||
const Regions = require('../data/regions');
|
||||
const Categories = require('../data/categories');
|
||||
const Account = require('../data/account');
|
||||
|
||||
const {
|
||||
PORT = 4001,
|
||||
BASE_URL = `http://0.0.0.0:${PORT}`,
|
||||
PREFIX = 'navigation',
|
||||
DC_NAME,
|
||||
SDC_URL,
|
||||
SDC_KEY_PATH,
|
||||
SDC_ACCOUNT,
|
||||
SDC_KEY_ID
|
||||
} = process.env;
|
||||
|
||||
const dcName = DC_NAME || Url.parse(SDC_URL).host.split('.')[0];
|
||||
const keyPath = SDC_KEY_PATH;
|
||||
const keyId = `/${SDC_ACCOUNT}/keys/${SDC_KEY_ID}`;
|
||||
const apiBaseUrl = SDC_URL;
|
||||
const baseUrl = BASE_URL;
|
||||
|
||||
Main(async () => {
|
||||
const server = await Server({
|
||||
PORT,
|
||||
BASE_URL
|
||||
});
|
||||
|
||||
await server.register([
|
||||
{
|
||||
plugin: Graphi,
|
||||
options: {
|
||||
graphqlPath: '/graphql',
|
||||
graphiqlPath: '/graphiql',
|
||||
authStrategy: 'sso'
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: Nav,
|
||||
options: {
|
||||
keyPath,
|
||||
keyId,
|
||||
apiBaseUrl,
|
||||
dcName,
|
||||
baseUrl,
|
||||
regions: Regions,
|
||||
accountServices: Account,
|
||||
categories: Categories
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: Ui
|
||||
}
|
||||
]);
|
||||
|
||||
await server.start();
|
||||
});
|
84
bundle/src/server.js
Normal file
84
bundle/src/server.js
Normal file
@ -0,0 +1,84 @@
|
||||
require('../.env.js');
|
||||
|
||||
const Hapi = require('hapi');
|
||||
const Sso = require('hapi-triton-auth');
|
||||
|
||||
const {
|
||||
COOKIE_PASSWORD,
|
||||
COOKIE_DOMAIN,
|
||||
SDC_KEY_PATH,
|
||||
SDC_ACCOUNT,
|
||||
SDC_KEY_ID,
|
||||
SDC_URL
|
||||
} = process.env;
|
||||
|
||||
module.exports = async ({ PORT, BASE_URL }) => {
|
||||
const keyPath = SDC_KEY_PATH;
|
||||
const keyId = `/${SDC_ACCOUNT}/keys/${SDC_KEY_ID}`;
|
||||
const apiBaseUrl = SDC_URL;
|
||||
const ssoUrl = 'https://sso.joyent.com/login';
|
||||
const baseUrl = BASE_URL;
|
||||
const isDev = true;
|
||||
|
||||
const permissions = {
|
||||
cloudapi: ['/my/*']
|
||||
};
|
||||
|
||||
const cookie = {
|
||||
password: COOKIE_PASSWORD,
|
||||
domain: COOKIE_DOMAIN,
|
||||
isSecure: false,
|
||||
isHttpOnly: true,
|
||||
ttl: 1000 * 60 * 60 // 1 hour
|
||||
};
|
||||
|
||||
const server = Hapi.server({
|
||||
port: PORT,
|
||||
routes: {
|
||||
cors: {
|
||||
origin: ['*'],
|
||||
credentials: true,
|
||||
additionalHeaders: ['Cookie', 'X-CSRF-Token']
|
||||
}
|
||||
},
|
||||
debug: {
|
||||
log: ['error'],
|
||||
request: ['error']
|
||||
}
|
||||
});
|
||||
|
||||
server.events.on('log', (event, tags) => {
|
||||
if (tags.error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(event);
|
||||
}
|
||||
});
|
||||
|
||||
server.events.on('request', (request, event) => {
|
||||
const { tags } = event;
|
||||
if (tags.includes('error') && event.data && event.data.errors) {
|
||||
event.data.errors.forEach(error => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await server.register({
|
||||
plugin: Sso,
|
||||
options: {
|
||||
keyPath,
|
||||
keyId,
|
||||
apiBaseUrl,
|
||||
ssoUrl,
|
||||
permissions,
|
||||
baseUrl,
|
||||
isDev,
|
||||
cookie
|
||||
}
|
||||
});
|
||||
|
||||
server.auth.default('sso');
|
||||
|
||||
return server;
|
||||
};
|
78
bundle/src/service-groups.js
Normal file
78
bundle/src/service-groups.js
Normal file
@ -0,0 +1,78 @@
|
||||
require('../.env.js');
|
||||
|
||||
const Main = require('apr-main');
|
||||
const CloudApiGql = require('cloudapi-gql');
|
||||
const Tsg = require('tsg-graphql');
|
||||
const Graphi = require('graphi');
|
||||
const Url = require('url');
|
||||
|
||||
const Server = require('./server');
|
||||
const Ui = require('my-joy-service-groups');
|
||||
|
||||
const {
|
||||
PORT = 4004,
|
||||
BASE_URL = `http://0.0.0.0:${PORT}`,
|
||||
PREFIX = 'service-groups',
|
||||
DC_NAME,
|
||||
TSG_URL = 'http://0.0.0.0:3000',
|
||||
SDC_URL,
|
||||
SDC_KEY_PATH,
|
||||
SDC_ACCOUNT,
|
||||
SDC_KEY_ID
|
||||
} = process.env;
|
||||
|
||||
const dcName = DC_NAME || Url.parse(SDC_URL).host.split('.')[0];
|
||||
const keyPath = SDC_KEY_PATH;
|
||||
const keyId = `/${SDC_ACCOUNT}/keys/${SDC_KEY_ID}`;
|
||||
|
||||
Main(async () => {
|
||||
const server = await Server({
|
||||
PORT,
|
||||
BASE_URL
|
||||
});
|
||||
|
||||
await server.register([
|
||||
{
|
||||
plugin: Graphi,
|
||||
options: {
|
||||
graphqlPath: '/graphql',
|
||||
graphiqlPath: '/graphiql',
|
||||
authStrategy: 'sso'
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: Tsg,
|
||||
options: {
|
||||
authStrategy: 'sso',
|
||||
keyPath,
|
||||
keyId,
|
||||
apiBaseUrl: TSG_URL,
|
||||
dcName
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: CloudApiGql,
|
||||
options: {
|
||||
authStrategy: 'sso',
|
||||
keyPath,
|
||||
keyId,
|
||||
apiBaseUrl: SDC_URL,
|
||||
dcName
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: Ui
|
||||
}
|
||||
]);
|
||||
|
||||
await server.start();
|
||||
});
|
78
bundle/src/templates.js
Normal file
78
bundle/src/templates.js
Normal file
@ -0,0 +1,78 @@
|
||||
require('../.env.js');
|
||||
|
||||
const Main = require('apr-main');
|
||||
const CloudApiGql = require('cloudapi-gql');
|
||||
const Tsg = require('tsg-graphql');
|
||||
const Graphi = require('graphi');
|
||||
const Url = require('url');
|
||||
|
||||
const Server = require('./server');
|
||||
const Ui = require('my-joy-templates');
|
||||
|
||||
const {
|
||||
PORT = 4005,
|
||||
BASE_URL = `http://0.0.0.0:${PORT}`,
|
||||
PREFIX = 'templates',
|
||||
DC_NAME,
|
||||
TSG_URL = 'http://0.0.0.0:3000',
|
||||
SDC_URL,
|
||||
SDC_KEY_PATH,
|
||||
SDC_ACCOUNT,
|
||||
SDC_KEY_ID
|
||||
} = process.env;
|
||||
|
||||
const dcName = DC_NAME || Url.parse(SDC_URL).host.split('.')[0];
|
||||
const keyPath = SDC_KEY_PATH;
|
||||
const keyId = `/${SDC_ACCOUNT}/keys/${SDC_KEY_ID}`;
|
||||
|
||||
Main(async () => {
|
||||
const server = await Server({
|
||||
PORT,
|
||||
BASE_URL
|
||||
});
|
||||
|
||||
await server.register([
|
||||
{
|
||||
plugin: Graphi,
|
||||
options: {
|
||||
graphqlPath: '/graphql',
|
||||
graphiqlPath: '/graphiql',
|
||||
authStrategy: 'sso'
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: Tsg,
|
||||
options: {
|
||||
authStrategy: 'sso',
|
||||
keyPath,
|
||||
keyId,
|
||||
apiBaseUrl: TSG_URL,
|
||||
dcName
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: CloudApiGql,
|
||||
options: {
|
||||
authStrategy: 'sso',
|
||||
keyPath,
|
||||
keyId,
|
||||
apiBaseUrl: SDC_URL,
|
||||
dcName
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: Ui
|
||||
}
|
||||
]);
|
||||
|
||||
await server.start();
|
||||
});
|
@ -4,7 +4,16 @@ module.exports = {
|
||||
'scope-enum': [
|
||||
2,
|
||||
'always',
|
||||
['ui-toolkit', 'icons', 'my-joy-beta', 'navigation', 'bundle']
|
||||
[
|
||||
'ui-toolkit',
|
||||
'icons',
|
||||
'instances',
|
||||
'navigation',
|
||||
'bundle',
|
||||
'images',
|
||||
'sg',
|
||||
'templates'
|
||||
]
|
||||
]
|
||||
}
|
||||
};
|
||||
|
12
consoles/my-joy-images/.babelrc
Normal file
12
consoles/my-joy-images/.babelrc
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"ignore": ["_document.js"],
|
||||
"presets": [
|
||||
[
|
||||
"joyent-portal",
|
||||
{
|
||||
"aliases": true,
|
||||
"autoAliases": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
@ -21,3 +21,5 @@ yarn-error.log*
|
||||
|
||||
**/__diff_output__
|
||||
|
||||
|
||||
lib/app
|
27
consoles/my-joy-images/.npmignore
Normal file
27
consoles/my-joy-images/.npmignore
Normal file
@ -0,0 +1,27 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
|
||||
yarn-error.log*
|
||||
|
||||
## Image Snapshots Diff
|
||||
|
||||
**/__diff_output__
|
||||
|
||||
|
||||
!lib/app
|
||||
!dist
|
||||
!build
|
13
consoles/my-joy-images/.tern-project
Normal file
13
consoles/my-joy-images/.tern-project
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"libs": ["ecmascript", "browser"],
|
||||
"plugins": {
|
||||
"doc_comment": true,
|
||||
"local-scope": true,
|
||||
"jsx": true,
|
||||
"node": true,
|
||||
"webpack": {
|
||||
"configPath":
|
||||
"../../node_modules/joyent-react-scripts/src/webpack.config.dev.js"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,2 +1 @@
|
||||
build
|
||||
dist
|
133
consoles/my-joy-images/lib/index.js
Normal file
133
consoles/my-joy-images/lib/index.js
Normal file
@ -0,0 +1,133 @@
|
||||
const Boom = require('boom');
|
||||
const Inert = require('inert');
|
||||
const Path = require('path');
|
||||
const RenderReact = require('hapi-render-react');
|
||||
const Intercept = require('apr-intercept');
|
||||
const Fs = require('mz/fs');
|
||||
|
||||
const { NAMESPACE = 'images', NODE_ENV = 'development' } = process.env;
|
||||
|
||||
exports.register = async server => {
|
||||
let manifest = {};
|
||||
|
||||
try {
|
||||
manifest = require('../build/asset-manifest.json');
|
||||
} catch (err) {
|
||||
if (NODE_ENV === 'production') {
|
||||
throw err;
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
const relativeTo = Path.join(__dirname, 'app');
|
||||
const buildRoot = Path.join(__dirname, '../build');
|
||||
const buildStatic = Path.join(buildRoot, `${NAMESPACE}`);
|
||||
const publicRoot = Path.join(__dirname, `../public/static/`);
|
||||
|
||||
await server.register([
|
||||
{
|
||||
plugin: Inert
|
||||
},
|
||||
{
|
||||
plugin: RenderReact
|
||||
}
|
||||
]);
|
||||
|
||||
server.route([
|
||||
{
|
||||
method: 'GET',
|
||||
path: `/${NAMESPACE}/service-worker.js`,
|
||||
config: {
|
||||
auth: false,
|
||||
handler: {
|
||||
file: {
|
||||
path: Path.join(__dirname, '../build/service-worker.js')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: `/${NAMESPACE}/favicon.ico`,
|
||||
config: {
|
||||
auth: false,
|
||||
handler: {
|
||||
file: {
|
||||
path: Path.join(__dirname, '../build/favicon.ico')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: `/${NAMESPACE}/static/{rest*}`,
|
||||
config: {
|
||||
auth: false
|
||||
},
|
||||
handler: async (request, h) => {
|
||||
const { params } = request;
|
||||
const { rest } = params;
|
||||
|
||||
if (!rest) {
|
||||
return Boom.notFound();
|
||||
}
|
||||
|
||||
const publicPathname = Path.join(publicRoot, rest);
|
||||
const [err1] = await Intercept(
|
||||
Fs.access(publicPathname, Fs.constants.R_OK)
|
||||
);
|
||||
|
||||
if (!err1) {
|
||||
return h.file(publicPathname, {
|
||||
confine: publicRoot
|
||||
});
|
||||
}
|
||||
|
||||
const buildPathname = Path.join(buildStatic, 'static', rest);
|
||||
const [err2] = await Intercept(
|
||||
Fs.access(buildPathname, Fs.constants.R_OK)
|
||||
);
|
||||
|
||||
if (!err2) {
|
||||
return h.file(buildPathname, {
|
||||
confine: buildStatic
|
||||
});
|
||||
}
|
||||
|
||||
const filename = manifest[rest];
|
||||
if (!filename) {
|
||||
return Boom.notFound();
|
||||
}
|
||||
|
||||
const buildMapPathname = Path.join(buildRoot, filename);
|
||||
return h.file(buildMapPathname, {
|
||||
confine: buildStatic
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
method: '*',
|
||||
path: `/${NAMESPACE}/~server-error`,
|
||||
handler: {
|
||||
view: {
|
||||
name: 'server-error',
|
||||
relativeTo
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
method: '*',
|
||||
path: `/${NAMESPACE}/{path*}`,
|
||||
handler: {
|
||||
view: {
|
||||
name: 'app',
|
||||
relativeTo
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
||||
exports.pkg = require('../package.json');
|
82
consoles/my-joy-images/package.json
Normal file
82
consoles/my-joy-images/package.json
Normal file
@ -0,0 +1,82 @@
|
||||
{
|
||||
"name": "my-joy-images",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"license": "MPL-2.0",
|
||||
"repository": "github:yldio/joyent-portal",
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"dev": "REACT_APP_DEV=1 NAMESPACE=images NODE_ENV=development REACT_APP_GQL_PORT=4000 PORT=3070 joyent-react-scripts start",
|
||||
"build:test": "echo 0",
|
||||
"build:lib": "echo 0",
|
||||
"build:bundle": "NAMESPACE=images NODE_ENV=production redrun -p build:frontend build:ssr",
|
||||
"prepublish": "NODE_ENV=production redrun build:bundle",
|
||||
"test": "echo 0",
|
||||
"test:ci": "echo 0",
|
||||
"build:frontend": "joyent-react-scripts build",
|
||||
"build:ssr": "SSR=1 UMD=1 babel src --out-dir lib/app --copy-files"
|
||||
},
|
||||
"dependencies": {
|
||||
"@manaflair/redux-batch": "^0.1.0",
|
||||
"apollo": "^0.2.2",
|
||||
"apollo-cache-inmemory": "^1.2.2",
|
||||
"apollo-client": "^2.3.2",
|
||||
"apollo-link-http": "^1.5.4",
|
||||
"apr-intercept": "^3.0.3",
|
||||
"apr-reduce": "^3.0.3",
|
||||
"boom": "^7.2.0",
|
||||
"cross-fetch": "^2.2.0",
|
||||
"date-fns": "^1.29.0",
|
||||
"declarative-redux-form": "^2.0.8",
|
||||
"exenv": "^1.2.2",
|
||||
"force-array": "^3.1.0",
|
||||
"fuse.js": "^3.2.0",
|
||||
"hapi-render-react": "^2.5.2",
|
||||
"hapi-render-react-joyent-document": "^7.2.0",
|
||||
"inert": "^5.1.0",
|
||||
"joyent-logo-assets": "^1.1.0",
|
||||
"joyent-react-styled-flexboxgrid": "^3.1.0",
|
||||
"joyent-ui-resource-widgets": "^1.0.0",
|
||||
"joyent-ui-toolkit": "^6.0.0",
|
||||
"lodash.assign": "^4.2.0",
|
||||
"lodash.find": "^4.6.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.isfinite": "^3.3.2",
|
||||
"lodash.isfunction": "^3.0.9",
|
||||
"lodash.keys": "^4.2.0",
|
||||
"lodash.omit": "^4.5.0",
|
||||
"lodash.uniqby": "^4.7.0",
|
||||
"lunr": "^2.2.1",
|
||||
"mz": "^2.7.0",
|
||||
"param-case": "^2.1.1",
|
||||
"react": "^16.4.0",
|
||||
"react-apollo": "^2.1.4",
|
||||
"react-dom": "^16.4.0",
|
||||
"react-helmet-async": "0.1.0",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-redux-values": "^1.1.2",
|
||||
"react-router": "^4.2.0",
|
||||
"react-router-dom": "^4.2.2",
|
||||
"redux": "^4.0.0",
|
||||
"redux-form": "^7.3.0",
|
||||
"remcalc": "^1.0.10",
|
||||
"styled-components": "^3.3.0",
|
||||
"styled-components-spacing": "^3.0.0",
|
||||
"styled-flex-component": "^2.2.2",
|
||||
"styled-is": "^1.1.3",
|
||||
"title-case": "^2.1.1",
|
||||
"yup": "^0.25.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-cli": "^6.26.0",
|
||||
"babel-preset-joyent-portal": "^7.0.1",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-joyent-portal": "^3.3.1",
|
||||
"jest-image-snapshot": "^2.4.2",
|
||||
"jest-styled-components": "^5.0.1",
|
||||
"joyent-react-scripts": "^8.2.1",
|
||||
"react-screenshot-renderer": "^1.1.2",
|
||||
"react-test-renderer": "^16.4.0",
|
||||
"redrun": "^6.0.4"
|
||||
}
|
||||
}
|
31
consoles/my-joy-images/public/static/css/libre-franklin.css
Normal file
31
consoles/my-joy-images/public/static/css/libre-franklin.css
Normal file
@ -0,0 +1,31 @@
|
||||
@font-face {
|
||||
font-family: 'Libre Franklin';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Libre Franklin'), local('LibreFranklin-Regular'),
|
||||
url(../fonts/libre-franklin/libre-franklin-regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Libre Franklin';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: local('Libre Franklin Medium'), local('LibreFranklin-Medium'),
|
||||
url(../fonts/libre-franklin/libre-franklin-medium.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Libre Franklin';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: local('Libre Franklin SemiBold'), local('LibreFranklin-SemiBold'),
|
||||
url(../fonts/libre-franklin/libre-franklin-semibold.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Libre Franklin';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Libre Franklin Bold'), local('LibreFranklin-Bold'),
|
||||
url(../fonts/libre-franklin/libre-franklin-bold.ttf) format('truetype');
|
||||
}
|
15
consoles/my-joy-images/public/static/css/roboto-mono.css
Normal file
15
consoles/my-joy-images/public/static/css/roboto-mono.css
Normal file
@ -0,0 +1,15 @@
|
||||
@font-face {
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Roboto Mono'), local('RobotoMono-Regular'),
|
||||
url(../fonts/roboto-mono/roboto-mono-regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: local('Roboto Mono Bold'), local('RobotoMono-Bold'),
|
||||
url(../fonts/roboto-mono/roboto-mono-bold.ttf) format('truetype');
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
93
consoles/my-joy-images/public/static/fonts/libre-franklin/license.txt
Executable file
93
consoles/my-joy-images/public/static/fonts/libre-franklin/license.txt
Executable file
@ -0,0 +1,93 @@
|
||||
Copyright (c) 2015, Impallari Type (www.impallari.com)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
202
consoles/my-joy-images/public/static/fonts/roboto-mono/license.txt
Executable file
202
consoles/my-joy-images/public/static/fonts/roboto-mono/license.txt
Executable file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
BIN
consoles/my-joy-images/public/static/fonts/roboto-mono/roboto-mono-bold.ttf
Executable file
BIN
consoles/my-joy-images/public/static/fonts/roboto-mono/roboto-mono-bold.ttf
Executable file
Binary file not shown.
BIN
consoles/my-joy-images/public/static/fonts/roboto-mono/roboto-mono-regular.ttf
Executable file
BIN
consoles/my-joy-images/public/static/fonts/roboto-mono/roboto-mono-regular.ttf
Executable file
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
# my-joy-beta
|
||||
# my-joy-images
|
||||
|
||||
[![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg)](https://opensource.org/licenses/MPL-2.0)
|
||||
[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg)](https://github.com/RichardLitt/standard-readme)
|
||||
[![License: MPL 2.0](https://img.shields.io/badge/License-MPL%202.0-brightgreen.svg?style=flat-square)](https://opensource.org/licenses/MPL-2.0)
|
||||
[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
|
||||
|
||||
## Table of Contents
|
||||
|
10
consoles/my-joy-images/src/_aliases.js
Normal file
10
consoles/my-joy-images/src/_aliases.js
Normal file
@ -0,0 +1,10 @@
|
||||
const { SSR } = process.env;
|
||||
|
||||
const aliases = {};
|
||||
|
||||
if (SSR) {
|
||||
aliases['scroll-to-element'] = './src/mocks/scroll-to-element';
|
||||
aliases['^joyent-ui-toolkit/dist/es/editor$'] = './src/mocks/editor';
|
||||
}
|
||||
|
||||
module.exports = aliases;
|
57
consoles/my-joy-images/src/_document.js
Normal file
57
consoles/my-joy-images/src/_document.js
Normal file
@ -0,0 +1,57 @@
|
||||
const get = require('lodash.get');
|
||||
const Document = require('hapi-render-react-joyent-document');
|
||||
const path = require('path');
|
||||
const url = require('url');
|
||||
|
||||
const { theme } = require('joyent-ui-toolkit');
|
||||
const { default: createClient } = require('./state/apollo-client');
|
||||
const { default: createStore } = require('./state/redux-store');
|
||||
|
||||
const indexFile = path.join(__dirname, '../../build/index.html');
|
||||
const assets = require('../../build/asset-manifest.json');
|
||||
|
||||
const { NODE_ENV = 'development' } = process.env;
|
||||
|
||||
const getState = request => {
|
||||
const { req } = request.raw;
|
||||
const { headers } = req;
|
||||
const { host } = headers;
|
||||
|
||||
const protocol = NODE_ENV === 'development' ? 'http:' : 'https:';
|
||||
const _font = get(theme, 'font.href', () => '');
|
||||
const _mono = get(theme, 'monoFont.href', () => '');
|
||||
const _addr = url.parse(`${protocol}//${host}`);
|
||||
|
||||
const _theme = Object.assign({}, theme, {
|
||||
font: Object.assign({}, theme.font, {
|
||||
href: () =>
|
||||
_font(
|
||||
Object.assign(_addr, {
|
||||
namespace: 'images'
|
||||
})
|
||||
)
|
||||
}),
|
||||
monoFont: Object.assign({}, theme.monoFont, {
|
||||
href: () =>
|
||||
_mono(
|
||||
Object.assign(_addr, {
|
||||
namespace: 'images'
|
||||
})
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
return {
|
||||
theme: _theme,
|
||||
createClient,
|
||||
createStore
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = Document({
|
||||
namespace: 'images/',
|
||||
assets,
|
||||
Html: require('./html'),
|
||||
indexFile,
|
||||
getState
|
||||
});
|
14
consoles/my-joy-images/src/app.js
Normal file
14
consoles/my-joy-images/src/app.js
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react';
|
||||
import Helmet from 'react-helmet-async';
|
||||
|
||||
import { RootContainer } from 'joyent-ui-toolkit';
|
||||
import Routes from '@root/routes';
|
||||
|
||||
export default () => (
|
||||
<RootContainer>
|
||||
<Helmet>
|
||||
<title>Images</title>
|
||||
</Helmet>
|
||||
<Routes />
|
||||
</RootContainer>
|
||||
);
|
@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import { Field } from 'redux-form';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
import Flex, { FlexItem } from 'styled-flex-component';
|
||||
import { Row, Col } from 'joyent-react-styled-flexboxgrid';
|
||||
|
||||
import {
|
||||
FormGroup,
|
||||
FormLabel,
|
||||
Input,
|
||||
FormMeta,
|
||||
Button,
|
||||
RandomizeIcon,
|
||||
Textarea
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
export default ({ placeholderName, randomizing, onRandomize }) => (
|
||||
<form>
|
||||
<Flex wrap>
|
||||
<FlexItem flex>
|
||||
<FormGroup name="name" fluid field={Field}>
|
||||
<FormLabel>Image name</FormLabel>
|
||||
<Margin top="0.5">
|
||||
<Input placeholder={placeholderName} onBlur={null} required />
|
||||
</Margin>
|
||||
<FormMeta />
|
||||
</FormGroup>
|
||||
</FlexItem>
|
||||
<FlexItem>
|
||||
<Margin left="1">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onRandomize}
|
||||
loading={randomizing}
|
||||
marginless
|
||||
secondary
|
||||
icon
|
||||
>
|
||||
<RandomizeIcon />
|
||||
<span>Randomize</span>
|
||||
</Button>
|
||||
</Margin>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
<Margin top="3">
|
||||
<FormGroup name="version" fluid field={Field}>
|
||||
<FormLabel>Version</FormLabel>
|
||||
<Margin top="0.5">
|
||||
<Input placeholder="Example: v1.0" onBlur={null} required />
|
||||
</Margin>
|
||||
<FormMeta />
|
||||
</FormGroup>
|
||||
</Margin>
|
||||
<Row>
|
||||
<Col xs="12" sm="8">
|
||||
<Margin top="3">
|
||||
<FormGroup name="description" fluid field={Field}>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<Margin top="0.5">
|
||||
<Textarea
|
||||
placeholder="Example: JarJarBinks, Anakin Skywalker, Obi Wan Kenobi, Qui-Gon Jinn, Han Solo, Wookies"
|
||||
fluid
|
||||
/>
|
||||
</Margin>
|
||||
<FormMeta />
|
||||
</FormGroup>
|
||||
</Margin>
|
||||
</Col>
|
||||
</Row>
|
||||
</form>
|
||||
);
|
@ -16,14 +16,14 @@ const Container = styled.div`
|
||||
export default ({ icon, children, collapsed = true, ...rest }) => (
|
||||
<Container {...rest}>
|
||||
<Flex>
|
||||
<Margin right={1}>
|
||||
<Margin right="1">
|
||||
<Flex alignCenter full>
|
||||
{icon}
|
||||
</Flex>
|
||||
</Margin>
|
||||
<Small noMargin>{children}</Small>
|
||||
</Flex>
|
||||
<Margin top={1} bottom={collapsed ? 7 : 3}>
|
||||
<Margin top="1" bottom={collapsed ? 7 : 3}>
|
||||
<Divider height={remcalc(1)} />
|
||||
</Margin>
|
||||
</Container>
|
@ -5,8 +5,8 @@ import { P } from 'joyent-ui-toolkit';
|
||||
|
||||
export default ({ children }) => (
|
||||
<Row>
|
||||
<Col xs={12} sm={8}>
|
||||
<Margin bottom={3}>
|
||||
<Col xs="12" sm="8">
|
||||
<Margin bottom="3">
|
||||
<P>{children}</P>
|
||||
</Margin>
|
||||
</Col>
|
@ -4,10 +4,11 @@ import { Margin, Padding } from 'styled-components-spacing';
|
||||
import Flex from 'styled-flex-component';
|
||||
|
||||
import { H3, Card } from 'joyent-ui-toolkit';
|
||||
import NoPackagesImage from '@assets/no-packages.svg';
|
||||
import { EmptyState } from 'joyent-icons';
|
||||
|
||||
const NoPackagesTitle = styled(H3)`
|
||||
color: ${props => props.theme.greyDark};
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const FullWidthCard = styled(Card)`
|
||||
@ -16,10 +17,10 @@ const FullWidthCard = styled(Card)`
|
||||
|
||||
export default ({ children }) => (
|
||||
<FullWidthCard>
|
||||
<Padding all={6}>
|
||||
<Padding all="6">
|
||||
<Flex alignCenter justifyCenter column>
|
||||
<Margin bottom={2}>
|
||||
<img src={NoPackagesImage} alt="Sad Animal" />
|
||||
<Margin bottom="2">
|
||||
<EmptyState />
|
||||
</Margin>
|
||||
<NoPackagesTitle>{children}</NoPackagesTitle>
|
||||
</Flex>
|
209
consoles/my-joy-images/src/components/image.js
Normal file
209
consoles/my-joy-images/src/components/image.js
Normal file
@ -0,0 +1,209 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { Field } from 'redux-form';
|
||||
import Flex, { FlexItem } from 'styled-flex-component';
|
||||
import { Padding, Margin } from 'styled-components-spacing';
|
||||
import remcalc from 'remcalc';
|
||||
|
||||
import {
|
||||
Card,
|
||||
Anchor,
|
||||
CardHeader,
|
||||
Divider,
|
||||
ActionsIcon,
|
||||
PopoverTarget,
|
||||
Popover,
|
||||
PopoverItem,
|
||||
PopoverDivider as BasePopoverDivider,
|
||||
PopoverContainer,
|
||||
Radio,
|
||||
FormLabel,
|
||||
FormGroup,
|
||||
StatusLoader
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
import GLOBAL from '@state/global';
|
||||
import { ImageType, OS } from '@root/constants';
|
||||
|
||||
const A = styled(Anchor)`
|
||||
color: ${props => props.theme.text};
|
||||
text-decoration: none;
|
||||
font-weight: ${props => props.theme.font.weight.semibold};
|
||||
`;
|
||||
|
||||
const CardAnchor = styled(Anchor)`
|
||||
color: ${props => props.theme.text};
|
||||
text-decoration: none;
|
||||
`;
|
||||
|
||||
const ItemAnchor = styled(Anchor)`
|
||||
color: ${props => props.theme.text};
|
||||
-webkit-text-fill-color: currentcolor;
|
||||
text-decoration: none;
|
||||
`;
|
||||
|
||||
const Type = styled(Margin)`
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const Content = styled(Padding)`
|
||||
max-width: calc(100% - ${remcalc(48)});
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const Max = styled(Flex)`
|
||||
max-width: 100%;
|
||||
`;
|
||||
|
||||
const Actions = styled(Flex)`
|
||||
width: ${remcalc(48)};
|
||||
height: ${remcalc(48)};
|
||||
min-width: ${remcalc(48)};
|
||||
`;
|
||||
|
||||
const ActionsWrapper = styled(Flex)`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-left: ${remcalc(1)} solid ${props => props.theme.grey};
|
||||
`;
|
||||
|
||||
const PopoverDivider = styled(BasePopoverDivider)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const Image = ({
|
||||
name,
|
||||
id,
|
||||
os,
|
||||
version,
|
||||
type,
|
||||
removing,
|
||||
onRemove,
|
||||
onCreateInstance
|
||||
}) => (
|
||||
<Margin bottom="3">
|
||||
<CardAnchor to={`/images/${id}`} component={Link}>
|
||||
<Card radius>
|
||||
{removing ? (
|
||||
<Padding all="2">
|
||||
<StatusLoader />
|
||||
</Padding>
|
||||
) : (
|
||||
<Fragment>
|
||||
<CardHeader white radius>
|
||||
<Padding left="2" right="2">
|
||||
<Flex full alignCenter>
|
||||
<FlexItem>
|
||||
<Margin right="2">
|
||||
{React.createElement(OS[os], {
|
||||
width: '24',
|
||||
height: '24'
|
||||
})}
|
||||
</Margin>
|
||||
</FlexItem>
|
||||
<FlexItem>
|
||||
<A to={`/images/${id}/summary`} component={Link}>
|
||||
{name}
|
||||
</A>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
</Padding>
|
||||
</CardHeader>
|
||||
<Flex justifyBetween>
|
||||
<Content left="2" top="2" bottom="2">
|
||||
<Max justifyBetween>
|
||||
<Max alignCenter>
|
||||
<Flex>{version}</Flex>
|
||||
<Divider vertical />
|
||||
<Type>{ImageType[type]}</Type>
|
||||
</Max>
|
||||
</Max>
|
||||
</Content>
|
||||
<PopoverContainer clickable>
|
||||
<Actions>
|
||||
<PopoverTarget box>
|
||||
<ActionsWrapper alignCenter justifyCenter>
|
||||
<ActionsIcon />
|
||||
</ActionsWrapper>
|
||||
</PopoverTarget>
|
||||
<Popover noPadding placement="bottom">
|
||||
<Padding horizontal="3" vertical="2">
|
||||
<PopoverItem disabled={false} onClick={onCreateInstance}>
|
||||
<ItemAnchor
|
||||
href={`${
|
||||
GLOBAL.origin
|
||||
}/instances/~create/?image=${name}`}
|
||||
target="__blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Create Instance
|
||||
</ItemAnchor>
|
||||
</PopoverItem>
|
||||
</Padding>
|
||||
<PopoverDivider />
|
||||
<Padding horizontal="3" vertical="2">
|
||||
<PopoverItem disabled={removing} onClick={onRemove}>
|
||||
Remove
|
||||
</PopoverItem>
|
||||
</Padding>
|
||||
</Popover>
|
||||
</Actions>
|
||||
</PopoverContainer>
|
||||
</Flex>
|
||||
</Fragment>
|
||||
)}
|
||||
</Card>
|
||||
</CardAnchor>
|
||||
</Margin>
|
||||
);
|
||||
|
||||
export const Filters = ({ selected }) => (
|
||||
<Fragment>
|
||||
<FormGroup name="image-type" value="all" field={Field} type="radio">
|
||||
<Radio>
|
||||
<Flex alignCenter>
|
||||
<Margin horizontal="2">
|
||||
<FormLabel big normal={selected !== 'all'}>
|
||||
All
|
||||
</FormLabel>
|
||||
</Margin>
|
||||
</Flex>
|
||||
</Radio>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
name="image-type"
|
||||
value="hardware-virtual-machine"
|
||||
field={Field}
|
||||
type="radio"
|
||||
>
|
||||
<Radio noMargin>
|
||||
<Flex alignCenter>
|
||||
<Margin horizontal="2">
|
||||
<FormLabel big normal={selected !== 'hardware-virtual-machine'}>
|
||||
Virtual machines
|
||||
</FormLabel>
|
||||
</Margin>
|
||||
</Flex>
|
||||
</Radio>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
name="image-type"
|
||||
value="infrastructure-container"
|
||||
field={Field}
|
||||
type="radio"
|
||||
>
|
||||
<Radio noMargin>
|
||||
<Flex alignCenter>
|
||||
<Margin horizontal="2">
|
||||
<FormLabel big normal={selected !== 'infrastructure-container'}>
|
||||
Infrastructure container
|
||||
</FormLabel>
|
||||
</Margin>
|
||||
</Flex>
|
||||
</Radio>
|
||||
</FormGroup>
|
||||
</Fragment>
|
||||
);
|
@ -1,8 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import forceArray from 'force-array';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import forceArray from 'force-array';
|
||||
|
||||
import {
|
||||
SectionList,
|
||||
@ -29,20 +28,11 @@ const Menu = ({ links = [] }) => {
|
||||
|
||||
return (
|
||||
<ViewContainer plain>
|
||||
<Margin bottom={6}>
|
||||
<Margin bottom="5" top="1">
|
||||
<SectionList>{getMenuItems(_links)}</SectionList>
|
||||
</Margin>
|
||||
</ViewContainer>
|
||||
);
|
||||
};
|
||||
|
||||
Menu.propTypes = {
|
||||
links: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
pathname: PropTypes.string
|
||||
})
|
||||
)
|
||||
};
|
||||
|
||||
export default Menu;
|
190
consoles/my-joy-images/src/components/summary.js
Normal file
190
consoles/my-joy-images/src/components/summary.js
Normal file
@ -0,0 +1,190 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Row, Col } from 'joyent-react-styled-flexboxgrid';
|
||||
import { Margin, Padding } from 'styled-components-spacing';
|
||||
import styled from 'styled-components';
|
||||
import Flex, { FlexItem } from 'styled-flex-component';
|
||||
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
|
||||
import titleCase from 'title-case';
|
||||
import remcalc from 'remcalc';
|
||||
import { ValueBreakpoints as breakpoints } from 'joyent-ui-toolkit';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardOutlet,
|
||||
H2,
|
||||
P,
|
||||
Label as BaseLabel,
|
||||
Divider,
|
||||
Button,
|
||||
QueryBreakpoints,
|
||||
CopiableField,
|
||||
DuplicateIcon,
|
||||
DeleteIcon,
|
||||
DotIcon,
|
||||
FormLabel,
|
||||
Input
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
import GLOBAL from '@state/global';
|
||||
import { ImageType, OS } from '@root/constants';
|
||||
|
||||
const { SmallOnly, Medium } = QueryBreakpoints;
|
||||
|
||||
const VerticalDivider = styled.div`
|
||||
width: ${remcalc(1)};
|
||||
background: ${props => props.theme.grey};
|
||||
height: ${remcalc(24)};
|
||||
display: flex;
|
||||
align-self: flex-end;
|
||||
margin: 0 ${remcalc(18)};
|
||||
|
||||
@media (max-width: ${remcalc(breakpoints.small.upper)}) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const Label = styled(BaseLabel)`
|
||||
font-weight: 200;
|
||||
`;
|
||||
|
||||
const GreyLabel = styled(Label)`
|
||||
opacity: 0.5;
|
||||
padding-right: ${remcalc(3)};
|
||||
`;
|
||||
|
||||
const StateColor = {
|
||||
ACTIVE: 'green',
|
||||
UNACTIVATED: 'grey',
|
||||
DISABLED: 'secondaryActive',
|
||||
CREATING: 'primary',
|
||||
FAILED: 'red'
|
||||
};
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export const Meta = ({ name, version, type, published_at, state, os }) => (
|
||||
<Fragment>
|
||||
<Flex alignCenter>
|
||||
<FlexItem>
|
||||
<Margin right="2">
|
||||
{React.createElement(OS[os], {
|
||||
width: '30',
|
||||
height: '30'
|
||||
})}
|
||||
</Margin>
|
||||
</FlexItem>
|
||||
<FlexItem>
|
||||
<H2 bold>{name}</H2>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
<Margin top="2" bottom="3">
|
||||
<Flex>
|
||||
<Label>{version}</Label>
|
||||
<VerticalDivider />
|
||||
<Label>{ImageType[type]}</Label>
|
||||
<VerticalDivider />
|
||||
<Fragment>
|
||||
<GreyLabel>Created:</GreyLabel>
|
||||
<Label> {distanceInWordsToNow(published_at)} ago</Label>
|
||||
</Fragment>
|
||||
<VerticalDivider />
|
||||
<Flex>
|
||||
<FlexItem>
|
||||
<DotIcon
|
||||
right={remcalc(6)}
|
||||
size={remcalc(15)}
|
||||
color={StateColor[state]}
|
||||
/>
|
||||
</FlexItem>
|
||||
<FlexItem>
|
||||
<Label>{titleCase(state)}</Label>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Margin>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default ({ theme = {}, onRemove, removing, ...image }) => (
|
||||
<Row>
|
||||
<Col xs="12" sm="12" md="9">
|
||||
<Card>
|
||||
<CardOutlet>
|
||||
<Padding all="5">
|
||||
<Meta {...image} />
|
||||
<Row between="xs">
|
||||
<Col xs="9">
|
||||
<SmallOnly>
|
||||
<Button type="button" small icon>
|
||||
<DuplicateIcon light />
|
||||
</Button>
|
||||
</SmallOnly>
|
||||
<Medium>
|
||||
<Button
|
||||
type="button"
|
||||
href={`${GLOBAL.origin}/instances/~create/?image=${
|
||||
image.id
|
||||
}`}
|
||||
target="__blank"
|
||||
rel="noopener noreferrer"
|
||||
bold
|
||||
icon
|
||||
>
|
||||
<span>Create Instance</span>
|
||||
</Button>
|
||||
</Medium>
|
||||
</Col>
|
||||
<Col xs="3">
|
||||
<SmallOnly>
|
||||
<Button type="button" small icon error right>
|
||||
<DeleteIcon fill="red" />
|
||||
</Button>
|
||||
</SmallOnly>
|
||||
<Medium>
|
||||
<Button
|
||||
type="button"
|
||||
loading={removing}
|
||||
onClick={onRemove}
|
||||
bold
|
||||
icon
|
||||
error
|
||||
right
|
||||
>
|
||||
<Margin right="1">
|
||||
<DeleteIcon fill="red" />
|
||||
</Margin>
|
||||
<span>Delete</span>
|
||||
</Button>
|
||||
</Medium>
|
||||
</Col>
|
||||
</Row>
|
||||
<Margin bottom="4" top="4">
|
||||
<Divider height={remcalc(1)} />
|
||||
</Margin>
|
||||
<Margin bottom="2">
|
||||
<P>{image.description}</P>
|
||||
</Margin>
|
||||
<Margin bottom="3">
|
||||
<CopiableField text={(image.id || '').split('-')[0]} label="ID" />
|
||||
</Margin>
|
||||
<Margin bottom="3">
|
||||
<CopiableField text={image.id} label="UUID" />
|
||||
</Margin>
|
||||
<Row>
|
||||
<Col xs="12" md="7">
|
||||
<Margin bottom="3">
|
||||
<FormLabel>Operating system</FormLabel>
|
||||
<Input
|
||||
monospace
|
||||
onBlur={null}
|
||||
fluid
|
||||
value={titleCase(image.os)}
|
||||
/>
|
||||
</Margin>
|
||||
</Col>
|
||||
</Row>
|
||||
</Padding>
|
||||
</CardOutlet>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
|
||||
import { TagItem } from 'joyent-ui-toolkit';
|
||||
import KeyValue from '@components/key-value';
|
||||
import { KeyValue } from 'joyent-ui-resource-widgets';
|
||||
|
||||
export const AddForm = props => (
|
||||
<KeyValue {...props} method="add" input="input" type="tag" expanded />
|
||||
@ -14,8 +14,8 @@ export const EditForm = props => (
|
||||
|
||||
export default ({ norMargin, name, value, onClick, onRemoveClick, active }) => (
|
||||
<Margin
|
||||
right={norMargin ? 0 : 1}
|
||||
bottom={norMargin ? 0 : 1}
|
||||
right={norMargin ? '0' : '1'}
|
||||
bottom={norMargin ? '0' : '1'}
|
||||
key={`${name}-${value}`}
|
||||
>
|
||||
<TagItem onClick={onClick} active={active} onRemoveClick={onRemoveClick}>
|
44
consoles/my-joy-images/src/components/toolbar.js
Normal file
44
consoles/my-joy-images/src/components/toolbar.js
Normal file
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { Field } from 'redux-form';
|
||||
import Flex from 'styled-flex-component';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
|
||||
import { Button, FormGroup, Input, FormLabel } from 'joyent-ui-toolkit';
|
||||
|
||||
export const Toolbar = ({
|
||||
searchable = true,
|
||||
searchLabel = 'Filter',
|
||||
searchPlaceholder = '',
|
||||
action = false,
|
||||
actionLabel = '',
|
||||
actionable = false,
|
||||
onActionClick
|
||||
}) => (
|
||||
<Flex justifyBetween alignEnd>
|
||||
<FormGroup name="filter" field={Field}>
|
||||
<FormLabel>{searchLabel}</FormLabel>
|
||||
<Margin top="0.5">
|
||||
<Input placeholder={searchPlaceholder} disabled={!searchable} />
|
||||
</Margin>
|
||||
</FormGroup>
|
||||
{action ? (
|
||||
<FormGroup right>
|
||||
<Button
|
||||
type="button"
|
||||
disabled={!actionable}
|
||||
onClick={onActionClick}
|
||||
icon
|
||||
fluid
|
||||
>
|
||||
{actionLabel}
|
||||
</Button>
|
||||
</FormGroup>
|
||||
) : null}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export default ({ handleSubmit, ...rest }) => (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Toolbar {...rest} />
|
||||
</form>
|
||||
);
|
37
consoles/my-joy-images/src/constants.js
Normal file
37
consoles/my-joy-images/src/constants.js
Normal file
@ -0,0 +1,37 @@
|
||||
import {
|
||||
Linux,
|
||||
Freebsd,
|
||||
Illumos,
|
||||
Smart,
|
||||
Windows,
|
||||
Placeholder
|
||||
} from 'joyent-logo-assets';
|
||||
|
||||
export const ImageType = {
|
||||
ZONE_DATASET: 'Hardware Virtual Machine',
|
||||
LX_DATASET: 'Infrastructure Container',
|
||||
ZVOL: 'Hardware Virtual Machine',
|
||||
DOCKER: 'Docker Container',
|
||||
OTHER: 'Hardware Virtual Machine'
|
||||
};
|
||||
|
||||
export const OS = {
|
||||
SMARTOS: Smart,
|
||||
LINUX: Linux,
|
||||
WINDOWS: Windows,
|
||||
BSD: Freebsd,
|
||||
ILLUMOS: Illumos,
|
||||
OTHER: Placeholder
|
||||
};
|
||||
|
||||
export const Forms = {
|
||||
FORM_TAGS_CREATE: 'CREATE-IMAGE-TAGS-ADD',
|
||||
FORM_TAGS_EDIT: i => `CREATE-IMAGE-TAGS-EDIT-${i}`,
|
||||
FORM_DETAILS: 'CREATE-IMAGE-DETAILS',
|
||||
CREATE_FORM: 'CREATE-IMAGE',
|
||||
CREATE_TAGS: 'CREATE-IMAGE-TAGS',
|
||||
LIST_TOGGLE_TYPE_FORM: 'LIST-TOGGLE-TYPE-FORM',
|
||||
LIST_TOOLBAR_FORM: 'LIST-TOOLBAR-FORM',
|
||||
TAGS_TOOLBAR_FORM: 'TAGS-TOOLBAR-FORM',
|
||||
TAGS_ADD_FORM: 'TAGS-ADD-FORM'
|
||||
};
|
54
consoles/my-joy-images/src/containers/breadcrumb.js
Normal file
54
consoles/my-joy-images/src/containers/breadcrumb.js
Normal file
@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
import paramCase from 'param-case';
|
||||
import get from 'lodash.get';
|
||||
|
||||
import { Breadcrumb, BreadcrumbItem } from 'joyent-ui-toolkit';
|
||||
|
||||
export default ({ match }) => {
|
||||
const image = get(match, 'params.image');
|
||||
const create = get(match, 'params.step');
|
||||
const instance = get(match, 'params.instance');
|
||||
|
||||
const links = [
|
||||
{
|
||||
name: 'Compute',
|
||||
pathname: '/'
|
||||
},
|
||||
{
|
||||
name: 'Images',
|
||||
pathname: '/images'
|
||||
}
|
||||
]
|
||||
.concat(
|
||||
create && [
|
||||
{
|
||||
name: 'Create Image',
|
||||
pathname: `/images/~create`
|
||||
},
|
||||
{
|
||||
name: instance,
|
||||
pathname: `/images/~create/${instance}`
|
||||
}
|
||||
]
|
||||
)
|
||||
.concat(
|
||||
image && [
|
||||
{
|
||||
name: paramCase(image),
|
||||
pathname: `/images/${image}`
|
||||
}
|
||||
]
|
||||
)
|
||||
.filter(Boolean)
|
||||
.map(({ name, pathname }) => (
|
||||
<BreadcrumbItem key={name} to={pathname} component={Link}>
|
||||
<Margin horizontal="1" vertical="3">
|
||||
{name}
|
||||
</Margin>
|
||||
</BreadcrumbItem>
|
||||
));
|
||||
|
||||
return <Breadcrumb>{links}</Breadcrumb>;
|
||||
};
|
181
consoles/my-joy-images/src/containers/create-image/details.js
Normal file
181
consoles/my-joy-images/src/containers/create-image/details.js
Normal file
@ -0,0 +1,181 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import { set } from 'react-redux-values';
|
||||
import ReduxForm from 'declarative-redux-form';
|
||||
import { Row, Col } from 'joyent-react-styled-flexboxgrid';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
import { change } from 'redux-form';
|
||||
import { connect } from 'react-redux';
|
||||
import intercept from 'apr-intercept';
|
||||
import get from 'lodash.get';
|
||||
|
||||
import { NameIcon, H3, Button, H4, P } from 'joyent-ui-toolkit';
|
||||
|
||||
import Title from '@components/create-image/title';
|
||||
import Details from '@components/create-image/details';
|
||||
import Description from '@components/description';
|
||||
import GetRandomName from '@graphql/get-random-name.gql';
|
||||
import createClient from '@state/apollo-client';
|
||||
import { instanceName as validateName } from '@state/validators';
|
||||
import { Forms } from '@root/constants';
|
||||
|
||||
const NameContainer = ({
|
||||
expanded,
|
||||
proceeded,
|
||||
name,
|
||||
version,
|
||||
description,
|
||||
placeholderName,
|
||||
randomizing,
|
||||
handleAsyncValidate,
|
||||
shouldAsyncValidate,
|
||||
handleNext,
|
||||
handleRandomize,
|
||||
handleEdit,
|
||||
step
|
||||
}) => (
|
||||
<Fragment>
|
||||
<Title
|
||||
id={step}
|
||||
onClick={!expanded && !name && handleEdit}
|
||||
collapsed={!expanded && !proceeded}
|
||||
icon={<NameIcon />}
|
||||
>
|
||||
Image name and details
|
||||
</Title>
|
||||
{expanded ? (
|
||||
<Description>
|
||||
Here you can name your custom image, version it, and give it a
|
||||
description so that you can identify it elsewhere in the Triton
|
||||
ecosystem.
|
||||
</Description>
|
||||
) : null}
|
||||
<ReduxForm
|
||||
form={Forms.FORM_DETAILS}
|
||||
destroyOnUnmount={false}
|
||||
forceUnregisterOnUnmount={true}
|
||||
asyncValidate={handleAsyncValidate}
|
||||
shouldAsyncValidate={shouldAsyncValidate}
|
||||
onSubmit={handleNext}
|
||||
>
|
||||
{props =>
|
||||
expanded ? (
|
||||
<Details
|
||||
{...props}
|
||||
placeholderName={placeholderName}
|
||||
randomizing={randomizing}
|
||||
onRandomize={handleRandomize}
|
||||
/>
|
||||
) : name ? (
|
||||
<Margin top="3">
|
||||
<H3 bold noMargin>
|
||||
{name}
|
||||
</H3>
|
||||
{version ? (
|
||||
<Margin top="2">
|
||||
<H4 bold noMargin>
|
||||
{version}
|
||||
</H4>
|
||||
</Margin>
|
||||
) : null}
|
||||
{description ? (
|
||||
<Row>
|
||||
<Col xs="12" sm="8">
|
||||
<Margin top="1">
|
||||
<P>{description}</P>
|
||||
</Margin>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
</Margin>
|
||||
) : null
|
||||
}
|
||||
</ReduxForm>
|
||||
{expanded ? (
|
||||
<Margin top="4" bottom="7">
|
||||
<Button type="button" disabled={!name} onClick={handleNext}>
|
||||
Next
|
||||
</Button>
|
||||
</Margin>
|
||||
) : proceeded ? (
|
||||
<Margin top="4" bottom="7">
|
||||
<Button type="button" onClick={handleEdit} secondary>
|
||||
Edit
|
||||
</Button>
|
||||
</Margin>
|
||||
) : null}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default compose(
|
||||
graphql(GetRandomName, {
|
||||
options: () => ({
|
||||
fetchPolicy: 'network-only',
|
||||
ssr: false
|
||||
}),
|
||||
props: ({ data }) => ({
|
||||
placeholderName: data.rndName || ''
|
||||
})
|
||||
}),
|
||||
connect(
|
||||
({ form, values }, ownProps) => {
|
||||
const name = get(form, `${Forms.FORM_DETAILS}.values.name`, '');
|
||||
const version = get(form, `${Forms.FORM_DETAILS}.values.version`, '');
|
||||
|
||||
const description = get(
|
||||
form,
|
||||
`${Forms.FORM_DETAILS}.values.description`,
|
||||
''
|
||||
);
|
||||
|
||||
const proceeded = get(values, `${Forms.FORM_DETAILS}-proceeded`, false);
|
||||
const randomizing = get(values, 'create-image-name-randomizing', false);
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
proceeded,
|
||||
randomizing,
|
||||
name,
|
||||
version,
|
||||
description
|
||||
};
|
||||
},
|
||||
(dispatch, { history, match }) => ({
|
||||
handleNext: () => {
|
||||
dispatch(set({ name: `${Forms.FORM_DETAILS}-proceeded`, value: true }));
|
||||
return history.push(`/images/~create/${match.params.instance}/tag`);
|
||||
},
|
||||
handleEdit: () => {
|
||||
dispatch(set({ name: `${Forms.FORM_DETAILS}-proceeded`, value: true }));
|
||||
return history.push(`/images/~create/${match.params.instance}/name`);
|
||||
},
|
||||
shouldAsyncValidate: ({ trigger }) => {
|
||||
return trigger === 'change';
|
||||
},
|
||||
handleAsyncValidate: validateName,
|
||||
handleRandomize: async () => {
|
||||
dispatch(set({ name: 'create-image-name-randomizing', value: true }));
|
||||
|
||||
const [err, res] = await intercept(
|
||||
createClient().query({
|
||||
fetchPolicy: 'network-only',
|
||||
query: GetRandomName
|
||||
})
|
||||
);
|
||||
|
||||
dispatch(set({ name: 'create-image-name-randomizing', value: false }));
|
||||
|
||||
if (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
const { data } = res;
|
||||
const { rndName } = data;
|
||||
|
||||
return dispatch(change(Forms.FORM_DETAILS, 'name', rndName));
|
||||
}
|
||||
})
|
||||
)
|
||||
)(NameContainer);
|
@ -6,18 +6,16 @@ import { destroy, reset } from 'redux-form';
|
||||
import ReduxForm from 'declarative-redux-form';
|
||||
import { connect } from 'react-redux';
|
||||
import get from 'lodash.get';
|
||||
import remcalc from 'remcalc';
|
||||
import Flex from 'styled-flex-component';
|
||||
|
||||
import { TagsIcon, Button, H3, TagList, Divider } from 'joyent-ui-toolkit';
|
||||
import { TagsIcon, Button, H3, TagList } from 'joyent-ui-toolkit';
|
||||
import { KeyValue } from 'joyent-ui-resource-widgets';
|
||||
|
||||
import Title from '@components/create-instance/title';
|
||||
import Animated from '@containers/create-instance/animated';
|
||||
import KeyValue from '@components/key-value';
|
||||
import Title from '@components/create-image/title';
|
||||
import Description from '@components/description';
|
||||
import Tag from '@components/tags';
|
||||
|
||||
const FORM_NAME_CREATE = 'CREATE-INSTANCE-TAGS-ADD';
|
||||
const FORM_NAME_EDIT = i => `CREATE-INSTANCE-TAGS-EDIT-${i}`;
|
||||
import { addTag as validateTag } from '@state/validators';
|
||||
import { Forms } from '@root/constants';
|
||||
|
||||
export const Tags = ({
|
||||
tags = [],
|
||||
@ -30,9 +28,12 @@ export const Tags = ({
|
||||
handleToggleExpanded,
|
||||
handleCancelEdit,
|
||||
handleChangeAddOpen,
|
||||
handleAsyncValidate,
|
||||
shouldAsyncValidate,
|
||||
handleNext,
|
||||
step,
|
||||
handleEdit
|
||||
handleEdit,
|
||||
children
|
||||
}) => (
|
||||
<Fragment>
|
||||
<Title
|
||||
@ -45,7 +46,7 @@ export const Tags = ({
|
||||
</Title>
|
||||
{expanded ? (
|
||||
<Description>
|
||||
Tags can be used to identify your instances, group multiple instances
|
||||
Tags can be used to identify your images, group multiple images
|
||||
together, define firewall and affinity rules, and more.{' '}
|
||||
<a
|
||||
target="__blank"
|
||||
@ -58,7 +59,7 @@ export const Tags = ({
|
||||
) : null}
|
||||
{proceeded || expanded ? (
|
||||
<Fragment>
|
||||
<Margin bottom={4}>
|
||||
<Margin bottom="5">
|
||||
<H3>
|
||||
{tags.length} Tag{tags.length === 1 ? '' : 's'}
|
||||
</H3>
|
||||
@ -76,9 +77,11 @@ export const Tags = ({
|
||||
</Fragment>
|
||||
) : null}
|
||||
<ReduxForm
|
||||
form={FORM_NAME_CREATE}
|
||||
form={Forms.FORM_TAGS_CREATE}
|
||||
destroyOnUnmount={false}
|
||||
forceUnregisterOnUnmount={true}
|
||||
asyncValidate={handleAsyncValidate}
|
||||
shouldAsyncValidate={shouldAsyncValidate}
|
||||
onSubmit={handleAddTag}
|
||||
>
|
||||
{props =>
|
||||
@ -92,26 +95,26 @@ export const Tags = ({
|
||||
expanded
|
||||
onCancel={() => handleChangeAddOpen(false)}
|
||||
/>
|
||||
<Divider height={remcalc(18)} transparent />
|
||||
</Fragment>
|
||||
) : null
|
||||
}
|
||||
</ReduxForm>
|
||||
{expanded ? (
|
||||
<Margin top={1} bottom={7}>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => handleChangeAddOpen(true)}
|
||||
secondary
|
||||
>
|
||||
Add Tag
|
||||
</Button>
|
||||
<Button type="button" onClick={handleNext}>
|
||||
Next
|
||||
</Button>
|
||||
</Margin>
|
||||
) : proceeded ? (
|
||||
<Margin top={1} bottom={7}>
|
||||
<Margin top="1">
|
||||
<Flex alignCenter>
|
||||
{expanded ? (
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => handleChangeAddOpen(true)}
|
||||
secondary
|
||||
>
|
||||
Add Tag
|
||||
</Button>
|
||||
) : null}
|
||||
<Margin left="1">{children}</Margin>
|
||||
</Flex>
|
||||
</Margin>
|
||||
{proceeded ? (
|
||||
<Margin top="1">
|
||||
<Button type="button" onClick={handleEdit} secondary>
|
||||
Edit
|
||||
</Button>
|
||||
@ -121,33 +124,40 @@ export const Tags = ({
|
||||
);
|
||||
|
||||
export default compose(
|
||||
Animated,
|
||||
connect(({ values }, ownProps) => ({
|
||||
proceeded: get(values, 'create-instance-tags-proceeded', false),
|
||||
addOpen: get(values, 'create-instance-tags-add-open', false),
|
||||
tags: get(values, 'create-instance-tags', [])
|
||||
proceeded: get(values, `${Forms.CREATE_TAGS}-proceeded`, false),
|
||||
addOpen: get(values, `${Forms.CREATE_TAGS}-add-open`, false),
|
||||
tags: get(values, Forms.CREATE_TAGS, [])
|
||||
})),
|
||||
connect(null, (dispatch, { tags = [], history }) => ({
|
||||
connect(null, (dispatch, { tags = [], history, match }) => ({
|
||||
handleNext: () => {
|
||||
dispatch(set({ name: 'create-instance-tags-proceeded', value: true }));
|
||||
dispatch(set({ name: `${Forms.CREATE_TAGS}-proceeded`, value: true }));
|
||||
|
||||
return history.push(`/instances/~create/metadata`);
|
||||
return history.push(`/images/~create/${match.params.instance}/create`);
|
||||
},
|
||||
handleEdit: () => {
|
||||
return history.push(`/instances/~create/tags`);
|
||||
return history.push(`/images/~create/${match.params.instance}/tag`);
|
||||
},
|
||||
shouldAsyncValidate: ({ trigger }) => {
|
||||
return trigger === 'submit';
|
||||
},
|
||||
handleAsyncValidate: validateTag,
|
||||
handleAddTag: value => {
|
||||
const toggleToClosed = set({
|
||||
name: `create-instance-tags-add-open`,
|
||||
name: `${Forms.CREATE_TAGS}-add-open`,
|
||||
value: false
|
||||
});
|
||||
|
||||
const appendTag = set({
|
||||
name: `create-instance-tags`,
|
||||
name: Forms.CREATE_TAGS,
|
||||
value: tags.concat([{ ...value, expanded: false }])
|
||||
});
|
||||
|
||||
return dispatch([destroy(FORM_NAME_CREATE), toggleToClosed, appendTag]);
|
||||
return dispatch([
|
||||
destroy(Forms.FORM_TAGS_CREATE),
|
||||
toggleToClosed,
|
||||
appendTag
|
||||
]);
|
||||
},
|
||||
handleUpdateTag: (index, newTag) => {
|
||||
tags[index] = {
|
||||
@ -156,14 +166,14 @@ export default compose(
|
||||
};
|
||||
|
||||
return dispatch([
|
||||
destroy(FORM_NAME_EDIT(index)),
|
||||
set({ name: `create-instance-tags`, value: tags.slice() })
|
||||
destroy(Forms.FORM_TAGS_EDIT(index)),
|
||||
set({ name: Forms.CREATE_TAGS, value: tags.slice() })
|
||||
]);
|
||||
},
|
||||
handleChangeAddOpen: value => {
|
||||
return dispatch([
|
||||
reset(FORM_NAME_CREATE),
|
||||
set({ name: `create-instance-tags-add-open`, value })
|
||||
reset(Forms.FORM_TAGS_CREATE),
|
||||
set({ name: `${Forms.CREATE_TAGS}-add-open`, value })
|
||||
]);
|
||||
},
|
||||
handleToggleExpanded: index => {
|
||||
@ -174,7 +184,7 @@ export default compose(
|
||||
|
||||
return dispatch(
|
||||
set({
|
||||
name: `create-instance-tags`,
|
||||
name: Forms.CREATE_TAGS,
|
||||
value: tags.slice()
|
||||
})
|
||||
);
|
||||
@ -186,16 +196,16 @@ export default compose(
|
||||
};
|
||||
|
||||
return dispatch([
|
||||
reset(FORM_NAME_EDIT(index)),
|
||||
set({ name: `create-instance-tags`, value: tags.slice() })
|
||||
reset(Forms.FORM_TAGS_EDIT(index)),
|
||||
set({ name: Forms.CREATE_TAGS, value: tags.slice() })
|
||||
]);
|
||||
},
|
||||
handleRemoveTag: index => {
|
||||
tags.splice(index, 1);
|
||||
|
||||
return dispatch([
|
||||
destroy(FORM_NAME_EDIT(index)),
|
||||
set({ name: `create-instance-tags`, value: tags.slice() })
|
||||
destroy(Forms.FORM_TAGS_EDIT(index)),
|
||||
set({ name: Forms.CREATE_TAGS, value: tags.slice() })
|
||||
]);
|
||||
}
|
||||
}))
|
189
consoles/my-joy-images/src/containers/create.js
Normal file
189
consoles/my-joy-images/src/containers/create.js
Normal file
@ -0,0 +1,189 @@
|
||||
import React from 'react';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
import ReduxForm from 'declarative-redux-form';
|
||||
import { destroyAll } from 'react-redux-values';
|
||||
import { destroy, stopSubmit } from 'redux-form';
|
||||
import { connect } from 'react-redux';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import intercept from 'apr-intercept';
|
||||
import get from 'lodash.get';
|
||||
import uniqBy from 'lodash.uniqby';
|
||||
import omit from 'lodash.omit';
|
||||
|
||||
import {
|
||||
ViewContainer,
|
||||
H2,
|
||||
Button,
|
||||
StatusLoader,
|
||||
Message,
|
||||
MessageTitle,
|
||||
MessageDescription
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
import CreateImage from '@graphql/create-image.gql';
|
||||
import GetInstance from '@graphql/get-instance.gql';
|
||||
import Details from '@containers/create-image/details';
|
||||
import Tags from '@containers/create-image/tags';
|
||||
import { Forms } from '@root/constants';
|
||||
import parseError from '@state/parse-error';
|
||||
|
||||
const Create = ({
|
||||
step,
|
||||
history,
|
||||
location,
|
||||
match,
|
||||
disabled,
|
||||
loading,
|
||||
loadingError,
|
||||
handleSubmit
|
||||
}) => (
|
||||
<ViewContainer>
|
||||
{loading ? (
|
||||
<Margin top="4">
|
||||
<StatusLoader />
|
||||
</Margin>
|
||||
) : null}
|
||||
{loadingError ? (
|
||||
<Margin top="4">
|
||||
<Message error>
|
||||
<MessageTitle>Ooops!</MessageTitle>
|
||||
<MessageDescription>{loadingError}</MessageDescription>
|
||||
</Message>
|
||||
</Margin>
|
||||
) : null}
|
||||
{!loading && !loadingError ? (
|
||||
<Margin top="4" bottom="5">
|
||||
<H2>Create Image</H2>
|
||||
</Margin>
|
||||
) : null}
|
||||
{!loading && !loadingError ? (
|
||||
<Details
|
||||
history={history}
|
||||
match={match}
|
||||
step="name"
|
||||
expanded={step === 'name'}
|
||||
/>
|
||||
) : null}
|
||||
{!loading && !loadingError ? (
|
||||
<Tags
|
||||
history={history}
|
||||
match={match}
|
||||
step="tag"
|
||||
expanded={step === 'tag'}
|
||||
/>
|
||||
) : null}
|
||||
<ReduxForm form={Forms.CREATE_FORM} onSubmit={handleSubmit}>
|
||||
{({ handleSubmit, submitting }) =>
|
||||
!loading && !loadingError ? (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Margin top={step === 'tag' ? '7' : '4'}>
|
||||
<Button disabled={disabled} loading={submitting}>
|
||||
Create Image
|
||||
</Button>
|
||||
</Margin>
|
||||
</form>
|
||||
) : null
|
||||
}
|
||||
</ReduxForm>
|
||||
</ViewContainer>
|
||||
);
|
||||
|
||||
export default compose(
|
||||
graphql(CreateImage, { name: 'createImage' }),
|
||||
graphql(GetInstance, {
|
||||
options: ({ match }) => ({
|
||||
ssr: false,
|
||||
variables: {
|
||||
id: get(match, 'params.instance')
|
||||
}
|
||||
}),
|
||||
props: ({ data: { loading, error, machine, variables, ...rest } }) => {
|
||||
const notFoundMsg = `Instance "${variables.name}" not found!`;
|
||||
const notFound = !loading && !machine ? notFoundMsg : false;
|
||||
|
||||
return {
|
||||
instance: machine,
|
||||
loadingError: error ? parseError(error) : notFound,
|
||||
loading
|
||||
};
|
||||
}
|
||||
}),
|
||||
connect(({ form, values }, { match }) => {
|
||||
const step = get(match, 'params.step', 'name');
|
||||
|
||||
const name = get(form, `${Forms.FORM_DETAILS}.values.name`, '');
|
||||
const version = get(form, `${Forms.FORM_DETAILS}.values.version`, '');
|
||||
|
||||
const disabled = !(name.length && version.length);
|
||||
|
||||
if (disabled) {
|
||||
return { disabled, step };
|
||||
}
|
||||
|
||||
const description = get(
|
||||
form,
|
||||
`${Forms.FORM_DETAILS}.values.description`,
|
||||
'<instance-description>'
|
||||
);
|
||||
|
||||
const tags = get(values, Forms.CREATE_TAGS, []);
|
||||
|
||||
return {
|
||||
forms: Object.keys(form), // improve this
|
||||
name,
|
||||
description,
|
||||
version,
|
||||
tags,
|
||||
disabled,
|
||||
step
|
||||
};
|
||||
}),
|
||||
connect(null, (dispatch, ownProps) => {
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
version,
|
||||
tags,
|
||||
instance,
|
||||
forms,
|
||||
createImage,
|
||||
history
|
||||
} = ownProps;
|
||||
|
||||
return {
|
||||
handleSubmit: async () => {
|
||||
const _name = name.toLowerCase();
|
||||
const _description = description.toLowerCase();
|
||||
const _version = version.toLowerCase();
|
||||
const _tags = uniqBy(tags, 'name').map(a => omit(a, 'expanded'));
|
||||
|
||||
const [err, res] = await intercept(
|
||||
createImage({
|
||||
variables: {
|
||||
machine: instance.id,
|
||||
name: _name,
|
||||
version: _version,
|
||||
description: _description,
|
||||
tags: _tags
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (err) {
|
||||
return dispatch(
|
||||
stopSubmit(Forms.CREATE_FORM, {
|
||||
_error: parseError(err)
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
dispatch([destroyAll(), forms.map(name => destroy(name))]);
|
||||
|
||||
const { data } = res;
|
||||
const { createImageFromMachine } = data;
|
||||
|
||||
history.push(`/images/${createImageFromMachine.id}`);
|
||||
}
|
||||
};
|
||||
})
|
||||
)(Create);
|
197
consoles/my-joy-images/src/containers/list.js
Normal file
197
consoles/my-joy-images/src/containers/list.js
Normal file
@ -0,0 +1,197 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import ReduxForm from 'declarative-redux-form';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
import { Row, Col } from 'joyent-react-styled-flexboxgrid';
|
||||
import { connect } from 'react-redux';
|
||||
import get from 'lodash.get';
|
||||
import intercept from 'apr-intercept';
|
||||
import { set } from 'react-redux-values';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
import {
|
||||
ViewContainer,
|
||||
Divider,
|
||||
StatusLoader,
|
||||
Message,
|
||||
MessageTitle,
|
||||
MessageDescription
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
import GLOBAL from '@state/global';
|
||||
import ToolbarForm from '@components/toolbar';
|
||||
import Empty from '@components/empty';
|
||||
import { ImageType, Forms } from '@root/constants';
|
||||
import ListImages from '@graphql/list-images.gql';
|
||||
import { Image, Filters } from '@components/image';
|
||||
import RemoveImage from '@graphql/remove-image.gql';
|
||||
import parseError from '@state/parse-error';
|
||||
|
||||
const { LIST_TOOLBAR_FORM, LIST_TOGGLE_TYPE_FORM } = Forms;
|
||||
|
||||
export const List = ({
|
||||
images = [],
|
||||
allImages = [],
|
||||
loading = false,
|
||||
error = null,
|
||||
history,
|
||||
typeValue,
|
||||
handleCreateInstance,
|
||||
handleRemove
|
||||
}) => (
|
||||
<ViewContainer main>
|
||||
<Margin top="4">
|
||||
<ReduxForm form={LIST_TOOLBAR_FORM}>
|
||||
{props => <ToolbarForm {...props} actionable={!loading} />}
|
||||
</ReduxForm>
|
||||
</Margin>
|
||||
<Margin vertical="4">
|
||||
<Divider />
|
||||
</Margin>
|
||||
{loading && !images.length ? (
|
||||
<Fragment>
|
||||
<StatusLoader />
|
||||
</Fragment>
|
||||
) : null}
|
||||
{error && !images.length && !loading ? (
|
||||
<Margin bottom="5">
|
||||
<Message error>
|
||||
<MessageTitle>Ooops!</MessageTitle>
|
||||
<MessageDescription>
|
||||
An error occurred while loading your images
|
||||
</MessageDescription>
|
||||
</Message>
|
||||
</Margin>
|
||||
) : null}
|
||||
<Fragment>
|
||||
<Margin bottom="4">
|
||||
<ReduxForm
|
||||
form={LIST_TOGGLE_TYPE_FORM}
|
||||
initialValues={{ 'image-type': 'all' }}
|
||||
>
|
||||
{props =>
|
||||
allImages.length ? (
|
||||
<Filters selected={typeValue} {...props} />
|
||||
) : null
|
||||
}
|
||||
</ReduxForm>
|
||||
</Margin>
|
||||
<Row>
|
||||
{images.map(image => (
|
||||
<Col sm="4">
|
||||
<Image
|
||||
{...image}
|
||||
onCreateInstance={() => handleCreateInstance(image)}
|
||||
onRemove={() => handleRemove(image.id)}
|
||||
/>
|
||||
</Col>
|
||||
))}
|
||||
{!images.length && !loading ? (
|
||||
<Empty>No images to see here</Empty>
|
||||
) : null}
|
||||
</Row>
|
||||
</Fragment>
|
||||
</ViewContainer>
|
||||
);
|
||||
|
||||
export default compose(
|
||||
graphql(RemoveImage, {
|
||||
name: 'removeImage'
|
||||
}),
|
||||
graphql(ListImages, {
|
||||
options: () => ({
|
||||
ssr: false,
|
||||
pollInterval: 1000
|
||||
}),
|
||||
props: ({ data: { images = [], loading, error, refetch } }) => ({
|
||||
images: images || [],
|
||||
index: new Fuse(images || [], {
|
||||
keys: ['name', 'os', 'version', 'state', 'type']
|
||||
}),
|
||||
loading,
|
||||
error
|
||||
})
|
||||
}),
|
||||
connect(
|
||||
({ form, values }, { index, error, images = [] }) => {
|
||||
const filter = get(form, `${LIST_TOOLBAR_FORM}.values.filter`, false);
|
||||
const mutationError = get(values, 'remove-mutation-error', null);
|
||||
|
||||
const typeValue = get(
|
||||
form,
|
||||
`${LIST_TOGGLE_TYPE_FORM}.values.image-type`,
|
||||
'all'
|
||||
);
|
||||
|
||||
const virtual = Object.keys(ImageType).filter(
|
||||
i => ImageType[i] === 'Hardware Virtual Machine'
|
||||
);
|
||||
|
||||
const container = Object.keys(ImageType).filter(
|
||||
i => ImageType[i] === 'Infrastructure Container'
|
||||
);
|
||||
|
||||
const filtered = filter ? index.search(filter) : images;
|
||||
|
||||
return {
|
||||
images: filtered
|
||||
.filter(image => {
|
||||
switch (typeValue) {
|
||||
case 'all':
|
||||
return true;
|
||||
case 'hardware-virtual-machine':
|
||||
return virtual.includes(image.type);
|
||||
case 'infrastructure-container':
|
||||
return container.includes(image.type);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.map(({ id, ...image }) => ({
|
||||
...image,
|
||||
id,
|
||||
removing: get(values, `remove-mutation-${id}-loading`, false)
|
||||
})),
|
||||
allImages: images,
|
||||
mutationError,
|
||||
typeValue
|
||||
};
|
||||
},
|
||||
(dispatch, { removeImage, history }) => ({
|
||||
handleCreateInstance: image => {
|
||||
return window
|
||||
.open(
|
||||
`${GLOBAL.origin}/instances/~create/?image=${image.name}`,
|
||||
'_blank'
|
||||
)
|
||||
.focus();
|
||||
},
|
||||
handleRemove: async id => {
|
||||
dispatch([set({ name: `remove-mutation-${id}-loading`, value: true })]);
|
||||
|
||||
const [err, res] = await intercept(
|
||||
removeImage({
|
||||
variables: {
|
||||
id
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (err) {
|
||||
dispatch([
|
||||
set({ name: 'remove-mutation-error', value: parseError(err) }),
|
||||
set({ name: `remove-mutation-${id}-loading`, value: false })
|
||||
]);
|
||||
}
|
||||
|
||||
if (res) {
|
||||
dispatch(
|
||||
set({ name: `remove-mutation-${id}-loading`, value: false })
|
||||
);
|
||||
|
||||
history.push('/images');
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
)(List);
|
21
consoles/my-joy-images/src/containers/menu.js
Normal file
21
consoles/my-joy-images/src/containers/menu.js
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash.get';
|
||||
|
||||
import Menu from '@components/menu';
|
||||
|
||||
const SECTIONS = [
|
||||
{ name: 'Summary', pathname: 'summary' },
|
||||
{ name: 'Tags', pathname: 'tags' }
|
||||
];
|
||||
|
||||
export default ({ match }) => {
|
||||
const imageId = get(match, 'params.image');
|
||||
const sections = imageId === '~create' ? [] : SECTIONS;
|
||||
|
||||
const links = sections.map(({ name, pathname }) => ({
|
||||
name,
|
||||
pathname: `/images/${imageId}/${pathname}`
|
||||
}));
|
||||
|
||||
return <Menu links={links} />;
|
||||
};
|
113
consoles/my-joy-images/src/containers/summary.js
Normal file
113
consoles/my-joy-images/src/containers/summary.js
Normal file
@ -0,0 +1,113 @@
|
||||
import React from 'react';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
import { connect } from 'react-redux';
|
||||
import intercept from 'apr-intercept';
|
||||
import { set } from 'react-redux-values';
|
||||
import get from 'lodash.get';
|
||||
|
||||
import {
|
||||
ViewContainer,
|
||||
StatusLoader,
|
||||
Message,
|
||||
MessageTitle,
|
||||
MessageDescription
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
import ImageSummary from '@components/summary';
|
||||
import GetImage from '@graphql/get-image.gql';
|
||||
import RemoveImage from '@graphql/remove-image.gql';
|
||||
import parseError from '@state/parse-error';
|
||||
|
||||
export const Summary = ({
|
||||
image,
|
||||
loading = false,
|
||||
error = null,
|
||||
removing,
|
||||
mutationError,
|
||||
handleRemove
|
||||
}) => (
|
||||
<ViewContainer main>
|
||||
{loading && !image ? <StatusLoader /> : null}
|
||||
{error && !loading && !image ? (
|
||||
<Margin bottom="5">
|
||||
<Message error>
|
||||
<MessageTitle>Ooops!</MessageTitle>
|
||||
<MessageDescription>
|
||||
An error occurred while loading your instance summary
|
||||
</MessageDescription>
|
||||
</Message>
|
||||
</Margin>
|
||||
) : null}
|
||||
{mutationError ? (
|
||||
<Margin bottom="5">
|
||||
<Message error>
|
||||
<MessageTitle>Ooops!</MessageTitle>
|
||||
<MessageDescription>
|
||||
There was a problem deleting your image
|
||||
</MessageDescription>
|
||||
</Message>
|
||||
</Margin>
|
||||
) : null}
|
||||
{image ? (
|
||||
<ImageSummary removing={removing} onRemove={handleRemove} {...image} />
|
||||
) : null}
|
||||
</ViewContainer>
|
||||
);
|
||||
|
||||
export default compose(
|
||||
graphql(RemoveImage, { name: 'removeImage' }),
|
||||
graphql(GetImage, {
|
||||
options: ({ match }) => ({
|
||||
ssr: false,
|
||||
variables: {
|
||||
id: get(match, 'params.image')
|
||||
}
|
||||
}),
|
||||
props: ({ data }) => {
|
||||
const { loading = false, error = null, image } = data;
|
||||
return {
|
||||
image,
|
||||
loading,
|
||||
error
|
||||
};
|
||||
}
|
||||
}),
|
||||
connect(
|
||||
({ values }, ownProps) => {
|
||||
const removing = get(values, 'remove-mutation-loading', false);
|
||||
const mutationError = get(values, 'remove-mutation-error', null);
|
||||
|
||||
return {
|
||||
...ownProps,
|
||||
removing,
|
||||
mutationError
|
||||
};
|
||||
},
|
||||
(dispatch, { removeImage, image, history }) => ({
|
||||
handleRemove: async () => {
|
||||
dispatch(set({ name: 'remove-mutation-loading', value: true }));
|
||||
|
||||
const [err, res] = await intercept(
|
||||
removeImage({
|
||||
variables: {
|
||||
id: image.id
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (err) {
|
||||
dispatch([
|
||||
set({ name: 'remove-mutation-error', value: parseError(err) }),
|
||||
set({ name: 'remove-mutation-loading', value: false })
|
||||
]);
|
||||
}
|
||||
|
||||
if (res) {
|
||||
dispatch(set({ name: 'remove-mutation-loading', value: false }));
|
||||
history.push('/images');
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
)(Summary);
|
231
consoles/my-joy-images/src/containers/tags.js
Normal file
231
consoles/my-joy-images/src/containers/tags.js
Normal file
@ -0,0 +1,231 @@
|
||||
import React from 'react';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import { connect } from 'react-redux';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
import ReduxForm from 'declarative-redux-form';
|
||||
import { destroy } from 'redux-form';
|
||||
import { set } from 'react-redux-values';
|
||||
import intercept from 'apr-intercept';
|
||||
import get from 'lodash.get';
|
||||
import remcalc from 'remcalc';
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
import {
|
||||
H3,
|
||||
ViewContainer,
|
||||
StatusLoader,
|
||||
Message,
|
||||
MessageTitle,
|
||||
MessageDescription,
|
||||
TagList,
|
||||
Divider
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
import { Forms } from '@root/constants';
|
||||
import Tag, { AddForm } from '@components/tags';
|
||||
import ToolbarForm from '@components/toolbar';
|
||||
import UpdateImageTags from '@graphql/update-image-tags.gql';
|
||||
import GetTags from '@graphql/get-tags.gql';
|
||||
import { addTag as validateTag } from '@state/validators';
|
||||
import parseError from '@state/parse-error';
|
||||
|
||||
const { TAGS_TOOLBAR_FORM, TAGS_ADD_FORM } = Forms;
|
||||
|
||||
export const Tags = ({
|
||||
tags = [],
|
||||
addOpen = false,
|
||||
loading = false,
|
||||
error = null,
|
||||
mutationError = null,
|
||||
mutating = false,
|
||||
handleAsyncValidate,
|
||||
shouldAsyncValidate,
|
||||
handleToggleAddOpen,
|
||||
handleRemoveTag,
|
||||
handleAddTag
|
||||
}) => (
|
||||
<ViewContainer main>
|
||||
<ReduxForm form={TAGS_TOOLBAR_FORM}>
|
||||
{props => (
|
||||
<Margin bottom="4">
|
||||
<ToolbarForm
|
||||
{...props}
|
||||
searchable={!loading}
|
||||
actionLabel="Add Tag"
|
||||
actionable={!loading && !mutating && !addOpen}
|
||||
onActionClick={handleToggleAddOpen}
|
||||
action
|
||||
/>
|
||||
<Margin vertical="4">
|
||||
<Divider height={remcalc(1)} />
|
||||
</Margin>
|
||||
</Margin>
|
||||
)}
|
||||
</ReduxForm>
|
||||
{error && !loading && !tags.length ? (
|
||||
<Margin bottom="5">
|
||||
<Message error>
|
||||
<MessageTitle>Ooops!</MessageTitle>
|
||||
<MessageDescription>
|
||||
An error occurred while loading your instance tags
|
||||
</MessageDescription>
|
||||
</Message>
|
||||
</Margin>
|
||||
) : null}
|
||||
{mutationError ? (
|
||||
<Margin bottom="5">
|
||||
<Message error>
|
||||
<MessageTitle>Ooops!</MessageTitle>
|
||||
<MessageDescription>{mutationError}</MessageDescription>
|
||||
</Message>
|
||||
</Margin>
|
||||
) : null}
|
||||
<ReduxForm
|
||||
form={TAGS_ADD_FORM}
|
||||
shouldAsyncValidate={shouldAsyncValidate}
|
||||
asyncValidate={handleAsyncValidate}
|
||||
onSubmit={handleAddTag}
|
||||
>
|
||||
{props =>
|
||||
addOpen ? (
|
||||
<Margin bottom="5">
|
||||
<AddForm
|
||||
{...props}
|
||||
onToggleExpanded={() => handleToggleAddOpen(!addOpen)}
|
||||
onCancel={() => handleToggleAddOpen(!addOpen)}
|
||||
/>
|
||||
</Margin>
|
||||
) : null
|
||||
}
|
||||
</ReduxForm>
|
||||
{loading ? null : (
|
||||
<Margin bottom="5">
|
||||
<H3>
|
||||
{tags.length} tag{tags.length === 1 ? '' : 's'}
|
||||
</H3>
|
||||
</Margin>
|
||||
)}
|
||||
{loading && !tags.length ? <StatusLoader /> : null}
|
||||
<TagList>
|
||||
{tags.map(({ id, name, value }) => (
|
||||
<Tag
|
||||
key={id}
|
||||
id={id}
|
||||
name={name}
|
||||
value={value}
|
||||
onRemoveClick={!mutating && (() => handleRemoveTag(name))}
|
||||
active
|
||||
/>
|
||||
))}
|
||||
</TagList>
|
||||
</ViewContainer>
|
||||
);
|
||||
|
||||
export default compose(
|
||||
graphql(UpdateImageTags, {
|
||||
name: 'updateTags'
|
||||
}),
|
||||
graphql(GetTags, {
|
||||
options: ({ match }) => ({
|
||||
ssr: false,
|
||||
fetchPolicy: 'network-only',
|
||||
pollInterval: 1000,
|
||||
variables: {
|
||||
id: get(match, 'params.image')
|
||||
}
|
||||
}),
|
||||
props: ({ data }) => {
|
||||
const { loading = false, error = null, image, refetch } = data;
|
||||
|
||||
const tags = get(image || {}, 'tags', []);
|
||||
const index = new Fuse(tags, {
|
||||
keys: ['name', 'value']
|
||||
});
|
||||
|
||||
return {
|
||||
index,
|
||||
image: image || {},
|
||||
tags,
|
||||
loading,
|
||||
error,
|
||||
refetch
|
||||
};
|
||||
}
|
||||
}),
|
||||
connect(
|
||||
({ values, form }, { index, tags, image }) => {
|
||||
const filter = get(form, `${TAGS_TOOLBAR_FORM}.values.filter`, false);
|
||||
const filtered = filter ? index.search(filter) : tags;
|
||||
|
||||
return {
|
||||
tags: filtered,
|
||||
addOpen: get(values, `${image.id}-add-open`, false),
|
||||
mutationError: get(values, `${image.id}-mutation-error`, false),
|
||||
mutating: get(values, `${image.id}-mutating`, false)
|
||||
};
|
||||
},
|
||||
(dispatch, { image, tags = [], updateTags, refetch }) => ({
|
||||
shouldAsyncValidate: ({ trigger }) => {
|
||||
return trigger === 'submit';
|
||||
},
|
||||
handleAsyncValidate: validateTag,
|
||||
handleToggleAddOpen: addOpen => {
|
||||
dispatch(set({ name: `${image.id}-add-open`, value: addOpen }));
|
||||
},
|
||||
handleRemoveTag: async name => {
|
||||
dispatch(set({ name: `${image.id}-mutating`, value: true }));
|
||||
|
||||
const [err] = await intercept(
|
||||
updateTags({
|
||||
variables: {
|
||||
id: image.id,
|
||||
tags: tags
|
||||
.map(({ name, value }) => ({ name, value }))
|
||||
.filter(tag => tag.name !== name)
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (err) {
|
||||
dispatch([
|
||||
set({ name: `${image.id}-mutation-error`, value: parseError(err) }),
|
||||
set({ name: `${image.id}-mutating`, value: false })
|
||||
]);
|
||||
}
|
||||
|
||||
await refetch();
|
||||
|
||||
dispatch(set({ name: `${image.id}-mutating`, value: false }));
|
||||
},
|
||||
handleAddTag: async ({ name, value }) => {
|
||||
dispatch(set({ name: `${image.id}-mutating`, value: true }));
|
||||
|
||||
const [err] = await intercept(
|
||||
updateTags({
|
||||
variables: {
|
||||
id: image.id,
|
||||
tags: tags
|
||||
.map(({ name, value }) => ({ name, value }))
|
||||
.concat([{ name, value }])
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (err) {
|
||||
dispatch([
|
||||
set({ name: `${image.id}-mutation-error`, value: parseError(err) }),
|
||||
set({ name: `${image.id}-mutating`, value: false })
|
||||
]);
|
||||
}
|
||||
|
||||
await refetch();
|
||||
|
||||
dispatch([
|
||||
set({ name: `${image.id}-mutating`, value: false }),
|
||||
dispatch(set({ name: `${image.id}-add-open`, value: false })),
|
||||
destroy(TAGS_ADD_FORM)
|
||||
]);
|
||||
}
|
||||
})
|
||||
)
|
||||
)(Tags);
|
18
consoles/my-joy-images/src/graphql/create-image.gql
Normal file
18
consoles/my-joy-images/src/graphql/create-image.gql
Normal file
@ -0,0 +1,18 @@
|
||||
mutation createImage(
|
||||
$machine: ID!
|
||||
$name: String!
|
||||
$version: String!
|
||||
$description: String
|
||||
$tags: [KeyValueInput]
|
||||
) {
|
||||
createImageFromMachine(
|
||||
machine: $machine
|
||||
name: $name
|
||||
version: $version
|
||||
description: $description
|
||||
tags: $tags
|
||||
) {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
19
consoles/my-joy-images/src/graphql/get-image.gql
Normal file
19
consoles/my-joy-images/src/graphql/get-image.gql
Normal file
@ -0,0 +1,19 @@
|
||||
query image($id: ID) {
|
||||
image(id: $id) {
|
||||
id
|
||||
name
|
||||
os
|
||||
version
|
||||
description
|
||||
type
|
||||
homepage
|
||||
published_at
|
||||
owner
|
||||
public
|
||||
state
|
||||
error {
|
||||
code
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
6
consoles/my-joy-images/src/graphql/get-instance.gql
Normal file
6
consoles/my-joy-images/src/graphql/get-instance.gql
Normal file
@ -0,0 +1,6 @@
|
||||
query instance($id: ID) {
|
||||
machine(id: $id) {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
3
consoles/my-joy-images/src/graphql/get-random-name.gql
Normal file
3
consoles/my-joy-images/src/graphql/get-random-name.gql
Normal file
@ -0,0 +1,3 @@
|
||||
query rndImageName {
|
||||
rndName
|
||||
}
|
11
consoles/my-joy-images/src/graphql/get-tags.gql
Normal file
11
consoles/my-joy-images/src/graphql/get-tags.gql
Normal file
@ -0,0 +1,11 @@
|
||||
query image($id: ID) {
|
||||
image(id: $id) {
|
||||
id
|
||||
name
|
||||
tags {
|
||||
id
|
||||
name
|
||||
value
|
||||
}
|
||||
}
|
||||
}
|
18
consoles/my-joy-images/src/graphql/list-images.gql
Normal file
18
consoles/my-joy-images/src/graphql/list-images.gql
Normal file
@ -0,0 +1,18 @@
|
||||
query images {
|
||||
images(public: false) {
|
||||
id
|
||||
name
|
||||
os
|
||||
version
|
||||
type
|
||||
homepage
|
||||
published_at
|
||||
owner
|
||||
public
|
||||
state
|
||||
error {
|
||||
code
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
6
consoles/my-joy-images/src/graphql/remove-image.gql
Normal file
6
consoles/my-joy-images/src/graphql/remove-image.gql
Normal file
@ -0,0 +1,6 @@
|
||||
mutation removeImage($id: ID!) {
|
||||
deleteImage(id: $id) {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
5
consoles/my-joy-images/src/graphql/update-image-tags.gql
Normal file
5
consoles/my-joy-images/src/graphql/update-image-tags.gql
Normal file
@ -0,0 +1,5 @@
|
||||
mutation updateImageTags($id: ID!, $tags: [KeyValueInput]!) {
|
||||
updateImage(id: $id, tags: $tags) {
|
||||
id
|
||||
}
|
||||
}
|
28
consoles/my-joy-images/src/html.js
Normal file
28
consoles/my-joy-images/src/html.js
Normal file
@ -0,0 +1,28 @@
|
||||
const React = require('react');
|
||||
|
||||
module.exports = ({
|
||||
htmlAttrs = {},
|
||||
bodyAttrs = {},
|
||||
head = [],
|
||||
children = null
|
||||
}) => (
|
||||
<html {...htmlAttrs}>
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#1E313B" />
|
||||
|
||||
{head}
|
||||
</head>
|
||||
<body {...bodyAttrs}>
|
||||
<div id="header" />
|
||||
{children ? null : <div id="root" />}
|
||||
{children}
|
||||
<script src="/navigation/static/main.js" />
|
||||
</body>
|
||||
</html>
|
||||
);
|
33
consoles/my-joy-images/src/index.js
Normal file
33
consoles/my-joy-images/src/index.js
Normal file
@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { Provider as ReduxProvider } from 'react-redux';
|
||||
import { ApolloProvider } from 'react-apollo';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import isFunction from 'lodash.isfunction';
|
||||
import isFinite from 'lodash.isfinite';
|
||||
|
||||
import theme from '@state/theme';
|
||||
import createStore from '@state/redux-store';
|
||||
import createClient from '@state/apollo-client';
|
||||
import App from './app';
|
||||
|
||||
if (!isFunction(Number.isFinite)) {
|
||||
Number.isFinite = isFinite;
|
||||
}
|
||||
|
||||
ReactDOM.hydrate(
|
||||
<ApolloProvider client={createClient()}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<ReduxProvider store={createStore()}>
|
||||
<BrowserRouter>
|
||||
<HelmetProvider context={{}}>
|
||||
<App />
|
||||
</HelmetProvider>
|
||||
</BrowserRouter>
|
||||
</ReduxProvider>
|
||||
</ThemeProvider>
|
||||
</ApolloProvider>,
|
||||
document.getElementById('root')
|
||||
);
|
8
consoles/my-joy-images/src/mocks/store.js
Normal file
8
consoles/my-joy-images/src/mocks/store.js
Normal file
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import { ApolloProvider } from 'react-apollo';
|
||||
|
||||
import createClient from '@state/apollo-client';
|
||||
|
||||
export default ({ children }) => (
|
||||
<ApolloProvider client={createClient()}>{children}</ApolloProvider>
|
||||
);
|
122
consoles/my-joy-images/src/routes.js
Normal file
122
consoles/my-joy-images/src/routes.js
Normal file
@ -0,0 +1,122 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Route, Switch, Redirect } from 'react-router-dom';
|
||||
import get from 'lodash.get';
|
||||
|
||||
import {
|
||||
PageContainer,
|
||||
ViewContainer,
|
||||
Message,
|
||||
MessageDescription,
|
||||
MessageTitle,
|
||||
Footer
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
import Breadcrumb from '@containers/breadcrumb';
|
||||
import Menu from '@containers/menu';
|
||||
import List from '@containers/list';
|
||||
import Summary from '@containers/summary';
|
||||
import Create from '@containers/create';
|
||||
import Tags from '@containers/tags';
|
||||
import { Route as ServerError } from '@root/server-error';
|
||||
|
||||
const { REACT_APP_DEV = false } = process.env;
|
||||
|
||||
export default () => (
|
||||
<PageContainer>
|
||||
{/* Breadcrumb */}
|
||||
<Switch>
|
||||
<Route path="/images/~server-error" component={Breadcrumb} />
|
||||
<Route
|
||||
path="/images/~create/:instance/:step?"
|
||||
exact
|
||||
component={Breadcrumb}
|
||||
/>
|
||||
<Route path="/images/:image?" component={Breadcrumb} />
|
||||
</Switch>
|
||||
|
||||
{/* Menu */}
|
||||
<Switch>
|
||||
<Route path="/images/~server-error" component={() => null} />
|
||||
<Route path="/images/:image/:section?" component={Menu} />
|
||||
<Route path="/images/~create/:instance/:step?" component={() => {}} />
|
||||
</Switch>
|
||||
|
||||
{/* Images */}
|
||||
<Switch>
|
||||
{/* <Route path="/images/~server-error" component={() => null} /> */}
|
||||
<Route path="/images/" exact component={List} />
|
||||
<Route path="/images/:image/summary" exact component={Summary} />
|
||||
<Route path="/images/:image/tags" exact component={Tags} />
|
||||
<Route
|
||||
path="/images/:image"
|
||||
exact
|
||||
component={({ match }) => (
|
||||
<Redirect to={`/images/${get(match, 'params.image')}/summary`} />
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
|
||||
{/* Create Image */}
|
||||
<Switch>
|
||||
<Route
|
||||
path="/images/~create/:instance?"
|
||||
exact
|
||||
component={({ match }) => (
|
||||
<Redirect to={`/images/~create/${match.params.instance}/name`} />
|
||||
)}
|
||||
/>
|
||||
<Route path="/images/~create/:instance/:step" component={Create} />
|
||||
</Switch>
|
||||
|
||||
<Route path="/images/~server-error" component={ServerError} />
|
||||
|
||||
<Route path="/" exact component={() => <Redirect to="/images" />} />
|
||||
|
||||
{REACT_APP_DEV ? (
|
||||
<Fragment>
|
||||
<Route
|
||||
path="/instances"
|
||||
component={({ location }) =>
|
||||
window.location.replace(
|
||||
`${window.location.protocol}//${window.location.hostname}:3069${
|
||||
location.pathname
|
||||
}${location.search}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/templates"
|
||||
component={({ location }) =>
|
||||
window.location.replace(
|
||||
`${window.location.protocol}//${window.location.hostname}:3071${
|
||||
location.pathname
|
||||
}${location.search}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/service-groups"
|
||||
component={({ location }) =>
|
||||
window.location.replace(
|
||||
`${window.location.protocol}//${window.location.hostname}:3072${
|
||||
location.pathname
|
||||
}${location.search}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Fragment>
|
||||
) : null}
|
||||
|
||||
<noscript>
|
||||
<ViewContainer main>
|
||||
<Message warning>
|
||||
<MessageTitle>Ooops!</MessageTitle>
|
||||
<MessageDescription>
|
||||
You need to enable JavaScript to run this app.
|
||||
</MessageDescription>
|
||||
</Message>
|
||||
</ViewContainer>
|
||||
</noscript>
|
||||
<Footer />
|
||||
</PageContainer>
|
||||
);
|
38
consoles/my-joy-images/src/server-error.js
Normal file
38
consoles/my-joy-images/src/server-error.js
Normal file
@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
import remcalc from 'remcalc';
|
||||
|
||||
import {
|
||||
RootContainer,
|
||||
PageContainer,
|
||||
ViewContainer,
|
||||
Message,
|
||||
MessageDescription,
|
||||
MessageTitle,
|
||||
Divider
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
import Breadcrumb from '@containers/breadcrumb';
|
||||
|
||||
export const Route = () => (
|
||||
<ViewContainer main>
|
||||
<Divider height={remcalc(30)} transparent />
|
||||
<Margin bottom="5">
|
||||
<Message error>
|
||||
<MessageTitle>Ooops!</MessageTitle>
|
||||
<MessageDescription>
|
||||
An error occurred while loading your page
|
||||
</MessageDescription>
|
||||
</Message>
|
||||
</Margin>
|
||||
</ViewContainer>
|
||||
);
|
||||
|
||||
export default () => (
|
||||
<RootContainer>
|
||||
<PageContainer>
|
||||
<Breadcrumb />
|
||||
<Route />
|
||||
</PageContainer>
|
||||
</RootContainer>
|
||||
);
|
42
consoles/my-joy-images/src/state/apollo-client.js
Normal file
42
consoles/my-joy-images/src/state/apollo-client.js
Normal file
@ -0,0 +1,42 @@
|
||||
import { ApolloClient } from 'apollo-client';
|
||||
import { HttpLink } from 'apollo-link-http';
|
||||
import { InMemoryCache } from 'apollo-cache-inmemory';
|
||||
import fetch from 'cross-fetch';
|
||||
import get from 'lodash.get';
|
||||
|
||||
import global from './global';
|
||||
|
||||
const {
|
||||
REACT_APP_GQL_PORT = global.port,
|
||||
REACT_APP_GQL_PROTOCOL = global.protocol,
|
||||
REACT_APP_GQL_HOSTNAME = global.hostname
|
||||
} = process.env;
|
||||
|
||||
const PORT = REACT_APP_GQL_PORT ? `:${REACT_APP_GQL_PORT}` : '';
|
||||
const URI = `${REACT_APP_GQL_PROTOCOL}://${REACT_APP_GQL_HOSTNAME}${PORT}/images/graphql`;
|
||||
|
||||
export default (opts = {}, request = {}) => {
|
||||
const host = get(request, 'raw.req.headers.host', '');
|
||||
|
||||
let cache = new InMemoryCache();
|
||||
|
||||
if (global.__APOLLO_STATE__) {
|
||||
cache = cache.restore(global.__APOLLO_STATE__);
|
||||
}
|
||||
|
||||
return new ApolloClient({
|
||||
cache,
|
||||
link: new HttpLink({
|
||||
uri: host ? `${REACT_APP_GQL_PROTOCOL}//${host}/images/graphql` : URI,
|
||||
credentials: 'same-origin',
|
||||
fetch,
|
||||
headers: {
|
||||
'X-CSRF-Token': global.cookie.replace(
|
||||
/(?:(?:^|.*;\s*)crumb\s*=\s*([^;]*).*$)|^.*$/,
|
||||
'$1'
|
||||
)
|
||||
}
|
||||
}),
|
||||
...opts
|
||||
});
|
||||
};
|
29
consoles/my-joy-images/src/state/global.js
Normal file
29
consoles/my-joy-images/src/state/global.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { canUseDOM } from 'exenv';
|
||||
import queryString from 'query-string';
|
||||
|
||||
const { NODE_ENV = 'development' } = process.env;
|
||||
|
||||
export const Global = () => {
|
||||
if (!canUseDOM) {
|
||||
return {
|
||||
protocol: NODE_ENV === 'development' ? 'http:' : 'https:',
|
||||
cookie: ''
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
port: window.location.port,
|
||||
protocol: window.location.protocol.replace(/:$/, ''),
|
||||
hostname: window.location.hostname,
|
||||
pathname: window.location.pathname,
|
||||
origin: window.location.origin,
|
||||
cookie: document.cookie || '',
|
||||
search: window.location.search,
|
||||
query: queryString.parse(window.location.search || ''),
|
||||
__REDUX_DEVTOOLS_EXTENSION__: window.__REDUX_DEVTOOLS_EXTENSION__,
|
||||
__APOLLO_STATE__: window.__APOLLO_STATE__,
|
||||
__REDUX_STATE__: window.__REDUX_STATE__
|
||||
};
|
||||
};
|
||||
|
||||
export default Global();
|
26
consoles/my-joy-images/src/state/redux-store.js
Normal file
26
consoles/my-joy-images/src/state/redux-store.js
Normal file
@ -0,0 +1,26 @@
|
||||
import { reduxBatch } from '@manaflair/redux-batch';
|
||||
import { createStore, combineReducers, compose } from 'redux';
|
||||
import { reducer as formReducer } from 'redux-form';
|
||||
import { reducer as valuesReducer } from 'react-redux-values';
|
||||
import global from './global';
|
||||
|
||||
const initialState = {};
|
||||
|
||||
export default () => {
|
||||
return createStore(
|
||||
combineReducers({
|
||||
values: valuesReducer,
|
||||
form: formReducer,
|
||||
ui: (state = {}) => state
|
||||
}),
|
||||
global.__REDUX_STATE__ || initialState,
|
||||
compose(
|
||||
reduxBatch,
|
||||
// If you are using the devToolsExtension, you can add it here also
|
||||
// eslint-disable-next-line no-negated-condition
|
||||
typeof global.__REDUX_DEVTOOLS_EXTENSION__ !== 'undefined'
|
||||
? global.__REDUX_DEVTOOLS_EXTENSION__()
|
||||
: f => f
|
||||
)
|
||||
);
|
||||
};
|
21
consoles/my-joy-images/src/state/theme.js
Normal file
21
consoles/my-joy-images/src/state/theme.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { theme } from 'joyent-ui-toolkit';
|
||||
|
||||
const font = theme.font.href({
|
||||
namespace: 'images'
|
||||
});
|
||||
|
||||
const monoFont = theme.monoFont.href({
|
||||
namespace: 'images'
|
||||
});
|
||||
|
||||
export default {
|
||||
...theme,
|
||||
font: {
|
||||
...theme.font,
|
||||
href: () => font
|
||||
},
|
||||
monoFont: {
|
||||
...theme.monoFont,
|
||||
href: () => monoFont
|
||||
}
|
||||
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user