Compare commits

..

No commits in common. "master" and "my-joy-alpha" have entirely different histories.

1160 changed files with 115252 additions and 103967 deletions

165
.dockerignore Normal file
View File

@ -0,0 +1,165 @@
### 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

View File

@ -1,9 +1,4 @@
packages/*/**
prototypes/*/**
artifacts
reports
.nyc_output
coverage
dist
styleguide
build
consoles/*/lib/app
node_modules

View File

@ -1,10 +1,8 @@
{
"extends": "joyent-portal",
"rules": {
"no-console": 1,
"new-cap": 0,
"jsx-a11y/href-no-hash": 0,
"no-negated-condition": 1,
"camelcase": 1
"new-cap": 0,
"no-console": 0
}
}

View File

@ -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.*

View File

@ -1,17 +1,27 @@
## 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

View File

@ -1,14 +1,22 @@
**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
View File

@ -165,5 +165,3 @@ prototypes/*/package-lock.json
_env*
keys*
/packages/*/public/index.html
/consoles/*/public/index.html

View File

@ -1,14 +1,11 @@
{
"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",

View File

@ -1,29 +0,0 @@
.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

View File

@ -1,31 +0,0 @@
{
"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
}
}
]
}

View File

@ -1,5 +1,8 @@
{
"libs": ["ecmascript", "browser"],
"libs": [
"ecmascript",
"browser"
],
"plugins": {
"doc_comment": true,
"local-scope": true,
@ -9,4 +12,4 @@
"configPath": "./webpack/index.js"
}
}
}
}

View File

@ -1,5 +1,5 @@
language: node_js
node_js:
- '9'
- '8'
script:
- yarn run test:ci
- npm run test-ci

28
.vscode/launch.json vendored
View File

@ -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}"
}
]
}

View File

@ -1 +0,0 @@
{}

View File

@ -26,13 +26,13 @@ In this scenario, the contributor should open a pull request instead.
Follow [Git blessed](http://chris.beams.io/posts/git-commit/) and
[Conventional Commits](https://conventionalcommits.org)
1. Separate subject from body with a blank line
1. Limit the subject line to 50 characters
1. Capitalize the subject line
1. Do not end the subject line with a period
1. Use the imperative mood in the subject line
1. Wrap the body at 72 characters
1. Use the body to explain what and why vs. how
1. Separate subject from body with a blank line
1. Limit the subject line to 50 characters
1. Capitalize the subject line
1. Do not end the subject line with a period
1. Use the imperative mood in the subject line
1. Wrap the body at 72 characters
1. Use the body to explain what and why vs. how
Types:

View File

@ -1,6 +1,6 @@
[![Travis](https://img.shields.io/travis/yldio/joyent-portal.svg?style=flat-square)](https://circleci.com/gh/yldio/joyent-portal)
[![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)
[![CircleCI](https://img.shields.io/circleci/project/github/yldio/joyent-portal/master.svg)](https://circleci.com/gh/yldio/joyent-portal)
[![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)
## Table of Contents

View File

@ -1,8 +0,0 @@
'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');

View File

@ -1,20 +0,0 @@
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'
}
];

View File

@ -1,89 +0,0 @@
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'
}
]
}
];

View File

@ -1,86 +0,0 @@
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'
}
]
}
];

View File

@ -1,72 +1,85 @@
// Requires .env.js file with the following exports:
// SDC_URL, SDC_KEY_ID, SDC_KEY_PATH
require('./.env.js');
const Main = require('apr-main');
const Brule = require('brule');
const Hapi = require('hapi');
const H2O2 = require('h2o2');
const Execa = require('execa');
const Path = require('path');
const Fs = require('fs');
const Inert = require('inert');
const Main = require('apr-main');
const Rollover = require('rollover');
const { PORT = 4000 } = process.env;
const ROOT = Path.join(__dirname, 'src');
const Sso = require('minio-proto-auth');
const Ui = require('my-joy-beta');
const Nav = require('joyent-navigation');
const Api = require('cloudapi-gql');
const calcPort = i => Number(PORT) + Number(i) + 1;
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 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);
const server = Hapi.server({
port: PORT,
host: '127.0.0.1'
});
Main(async () => {
const server = Hapi.server({
port: PORT,
routes: {
cors: {
origin: ['*'],
credentials: true,
additionalHeaders: ['Cookie', 'X-CSRF-Token']
await server.register([
// {
// plugin: Rollover,
// options: {
// rollbar: {
// accessToken: ROLLBAR_SERVER_TOKEN,
// reportLevel: 'error'
// }
// }
// },
{
plugin: Brule,
options: {
auth: false
}
},
debug: {
log: ['error'],
request: ['error']
{
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
}
});
]);
await server.register({
plugin: H2O2
});
routes.map(route => server.route(route));
server.auth.default('sso');
await server.start();
// eslint-disable-next-line no-console
console.log(`server started at http://0.0.0.0:${server.info.port}`);
console.log(`server started at http://localhost:${server.info.port}`);
});

View File

@ -4,28 +4,22 @@
"private": true,
"license": "MPL-2.0",
"scripts": {
"dev": "NODE_ENV=development PORT=4000 node index.js",
"build:test": "echo 0",
"build:lib": "echo 0",
"build:bundle": "echo 0",
"prepublish": "echo 0",
"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",
"test": "echo 0",
"test:ci": "echo 0"
"prepublish": "echo 0"
},
"dependencies": {
"apr-main": "^4.0.3",
"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"
"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"
}
}

40
bundle/scripts/gen-keys.sh Executable file
View File

@ -0,0 +1,40 @@
#!/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'"

235
bundle/scripts/setup.sh Normal file
View File

@ -0,0 +1,235 @@
#!/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

View File

@ -1,64 +0,0 @@
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();
});

View File

@ -1,64 +0,0 @@
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();
});

View File

@ -1,72 +0,0 @@
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();
});

View File

@ -1,84 +0,0 @@
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;
};

View File

@ -1,78 +0,0 @@
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();
});

View File

@ -1,78 +0,0 @@
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();
});

View File

@ -4,16 +4,7 @@ module.exports = {
'scope-enum': [
2,
'always',
[
'ui-toolkit',
'icons',
'instances',
'navigation',
'bundle',
'images',
'sg',
'templates'
]
['ui-toolkit', 'icons', 'my-joy-beta', 'navigation', 'bundle']
]
}
};

View File

@ -1,12 +0,0 @@
{
"ignore": ["_document.js"],
"presets": [
[
"joyent-portal",
{
"aliases": true,
"autoAliases": true
}
]
]
}

View File

@ -1,27 +0,0 @@
# 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

View File

@ -1,13 +0,0 @@
{
"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"
}
}
}

View File

@ -1,133 +0,0 @@
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');

View File

@ -1,82 +0,0 @@
{
"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"
}
}

View File

@ -1,31 +0,0 @@
@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');
}

View File

@ -1,15 +0,0 @@
@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');
}

View File

@ -1,93 +0,0 @@
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.

View File

@ -1,202 +0,0 @@
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.

View File

@ -1,10 +0,0 @@
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;

View File

@ -1,57 +0,0 @@
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
});

View File

@ -1,14 +0,0 @@
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>
);

View File

@ -1,71 +0,0 @@
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>
);

View File

@ -1,209 +0,0 @@
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>
);

View File

@ -1,38 +0,0 @@
import React from 'react';
import { Margin } from 'styled-components-spacing';
import { NavLink } from 'react-router-dom';
import forceArray from 'force-array';
import {
SectionList,
SectionListItem,
SectionListAnchor,
ViewContainer
} from 'joyent-ui-toolkit';
const getMenuItems = (links = []) =>
links.map(({ pathname, name }) => (
<SectionListItem key={pathname}>
<SectionListAnchor to={pathname} component={NavLink}>
{name}
</SectionListAnchor>
</SectionListItem>
));
const Menu = ({ links = [] }) => {
const _links = forceArray(links);
if (!_links.length) {
return null;
}
return (
<ViewContainer plain>
<Margin bottom="5" top="1">
<SectionList>{getMenuItems(_links)}</SectionList>
</Margin>
</ViewContainer>
);
};
export default Menu;

View File

@ -1,190 +0,0 @@
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>
);

View File

@ -1,44 +0,0 @@
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>
);

View File

@ -1,37 +0,0 @@
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'
};

View File

@ -1,54 +0,0 @@
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>;
};

View File

@ -1,181 +0,0 @@
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);

View File

@ -1,189 +0,0 @@
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);

View File

@ -1,197 +0,0 @@
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);

View File

@ -1,21 +0,0 @@
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} />;
};

View File

@ -1,113 +0,0 @@
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);

View File

@ -1,231 +0,0 @@
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);

View File

@ -1,18 +0,0 @@
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
}
}

View File

@ -1,19 +0,0 @@
query image($id: ID) {
image(id: $id) {
id
name
os
version
description
type
homepage
published_at
owner
public
state
error {
code
message
}
}
}

View File

@ -1,6 +0,0 @@
query instance($id: ID) {
machine(id: $id) {
id
name
}
}

View File

@ -1,3 +0,0 @@
query rndImageName {
rndName
}

View File

@ -1,11 +0,0 @@
query image($id: ID) {
image(id: $id) {
id
name
tags {
id
name
value
}
}
}

View File

@ -1,18 +0,0 @@
query images {
images(public: false) {
id
name
os
version
type
homepage
published_at
owner
public
state
error {
code
message
}
}
}

View File

@ -1,6 +0,0 @@
mutation removeImage($id: ID!) {
deleteImage(id: $id) {
id
name
}
}

View File

@ -1,5 +0,0 @@
mutation updateImageTags($id: ID!, $tags: [KeyValueInput]!) {
updateImage(id: $id, tags: $tags) {
id
}
}

View File

@ -1,28 +0,0 @@
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>
);

View File

@ -1,33 +0,0 @@
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')
);

View File

@ -1,8 +0,0 @@
import React from 'react';
import { ApolloProvider } from 'react-apollo';
import createClient from '@state/apollo-client';
export default ({ children }) => (
<ApolloProvider client={createClient()}>{children}</ApolloProvider>
);

View File

@ -1,122 +0,0 @@
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>
);

View File

@ -1,38 +0,0 @@
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>
);

View File

@ -1,42 +0,0 @@
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
});
};

View File

@ -1,29 +0,0 @@
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();

View File

@ -1,26 +0,0 @@
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
)
);
};

View File

@ -1,21 +0,0 @@
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
}
};

View File

@ -1,69 +0,0 @@
import intercept from 'apr-intercept';
import keys from 'lodash.keys';
import reduce from 'apr-reduce';
import assign from 'lodash.assign';
import * as yup from 'yup';
/*****************************************************************************/
const validateField = async (field, value) => {
const [err] = await intercept(field.validate(value));
return err ? err.errors.shift() : '';
};
const validateSchema = async (schema, value) => {
const errors = await reduce(
keys(schema),
async (errors, name) => {
const msg = await validateField(schema[name], value[name]);
return msg ? assign(errors, { [name]: msg }) : errors;
},
{}
);
if (keys(errors).length) {
throw errors;
}
};
/*****************************************************************************/
const matches = {
nameStart: /^[a-zA-Z]|\d/,
nameBody: /^([a-zA-Z]|\d|_|-)+$/
};
const msgs = {
required: prefix => `${prefix} must be defined.`,
nameStart: prefix => `${prefix} can only start with letters and numbers.`,
nameBody: prefix =>
`${prefix} cannot contain spaces and can only contain letters, numbers, underscores (_), and hyphens (-).`
};
const Schemas = {
tag: {
name: yup
.string()
.required(msgs.required('Key'))
.matches(matches.nameStart, msgs.nameStart('Key'))
.matches(matches.nameBody, msgs.nameBody('Key')),
value: yup
.string()
.required(msgs.required('Value'))
.matches(matches.nameStart, msgs.nameStart('Value'))
.matches(matches.nameBody, msgs.nameBody('Value'))
},
instanceName: {
name: yup
.string()
.matches(matches.nameStart, msgs.nameStart('Instance name'))
.matches(matches.nameBody, msgs.nameBody('Instance Name'))
}
};
/*****************************************************************************/
export const addTag = tag => validateSchema(Schemas.tag, tag);
export const instanceName = ({ name }) =>
name ? validateSchema(Schemas.instanceName, { name }) : null;

View File

@ -1,12 +0,0 @@
{
"ignore": ["_document.js", "_aliases.js"],
"presets": [
[
"joyent-portal",
{
"aliases": true,
"autoAliases": true
}
]
]
}

View File

@ -1,25 +0,0 @@
# 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

View File

@ -1,8 +0,0 @@
{
"setup": {
"compile": "npm run build",
"start": "serve -s build --port 3069 --single",
"href": "http://0.0.0.0:3069"
},
"extends": "lighthouse:default"
}

View File

@ -1,27 +0,0 @@
# 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

View File

@ -1,13 +0,0 @@
{
"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"
}
}
}

View File

@ -1,133 +0,0 @@
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 = 'instances', 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');

View File

@ -1,91 +0,0 @@
{
"name": "my-joy-instances",
"version": "2.3.5",
"private": true,
"license": "MPL-2.0",
"repository": "github:yldio/joyent-portal",
"main": "lib/index.js",
"scripts": {
"dev": "REACT_APP_DEV=1 NAMESPACE=instances NODE_ENV=development REACT_APP_GQL_PORT=4000 PORT=3069 joyent-react-scripts start",
"build:test": "echo 0",
"build:lib": "echo 0",
"build:bundle": "NAMESPACE=instances NODE_ENV=production redrun -p build:frontend build:ssr",
"prepublish": "NODE_ENV=production redrun build:bundle",
"test": "DEFAULT_TIMEOUT_INTERVAL=100000 NODE_ENV=test joyent-react-scripts test --env=jsdom --testPathIgnorePatterns='.ui.js'",
"test:ci": "NODE_ENV=test joyent-react-scripts test --env=jsdom --testPathIgnorePatterns='.ui.js'",
"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-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",
"bytes": "^3.0.0",
"clipboard-copy": "^2.0.0",
"cross-fetch": "^2.2.0",
"date-fns": "^1.29.0",
"declarative-redux-form": "^2.0.8",
"exenv": "^1.2.2",
"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-ui-resource-step": "^1.0.0",
"joyent-manifest-editor": "^1.4.0",
"joyent-react-styled-flexboxgrid": "^3.1.0",
"joyent-ui-toolkit": "^6.0.0",
"lodash.find": "^4.6.0",
"lodash.findindex": "^4.6.0",
"lodash.flatten": "^4.4.0",
"lodash.get": "^4.4.2",
"lodash.groupby": "^4.6.0",
"lodash.includes": "^4.3.0",
"lodash.isarray": "^4.0.0",
"lodash.isboolean": "^3.0.3",
"lodash.isfinite": "^3.3.2",
"lodash.isfunction": "^3.0.9",
"lodash.isinteger": "^4.0.4",
"lodash.isnan": "^3.0.2",
"lodash.reduce": "^4.6.0",
"lodash.reverse": "^4.0.1",
"lodash.some": "^4.6.0",
"lodash.sortby": "^4.7.0",
"lodash.values": "^4.3.0",
"mz": "^2.7.0",
"param-case": "^2.1.1",
"query-string": "^6.1.0",
"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",
"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"
}
}

View File

@ -1,15 +0,0 @@
{
"short_name": "Joyent",
"name": "My Joyent &beta;",
"icons": [
{
"src": "favicon.ico",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#1E313B",
"background_color": "#FAFAFA"
}

View File

@ -1,31 +0,0 @@
@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');
}

View File

@ -1,15 +0,0 @@
@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');
}

View File

@ -1,93 +0,0 @@
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.

View File

@ -1,202 +0,0 @@
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.

Some files were not shown because too many files have changed in this diff Show More