Compare commits
No commits in common. "master" and "nav-copy" have entirely different histories.
@ -1,9 +1,4 @@
|
||||
packages/*/**
|
||||
prototypes/*/**
|
||||
artifacts
|
||||
reports
|
||||
.nyc_output
|
||||
coverage
|
||||
dist
|
||||
styleguide
|
||||
build
|
||||
consoles/*/lib/app
|
||||
node_modules
|
@ -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
|
||||
}
|
||||
}
|
||||
|
2
.gitignore
vendored
@ -165,5 +165,3 @@ prototypes/*/package-lock.json
|
||||
|
||||
_env*
|
||||
keys*
|
||||
/packages/*/public/index.html
|
||||
/consoles/*/public/index.html
|
||||
|
@ -15,7 +15,6 @@ yarn.lock
|
||||
dist
|
||||
build
|
||||
packages/*/lib/app
|
||||
consoles/*/lib/app
|
||||
|
||||
*.ico
|
||||
*.html
|
||||
|
1
.vscode/settings.json
vendored
@ -1 +0,0 @@
|
||||
{}
|
@ -9,23 +9,24 @@
|
||||
"build:lib": "echo 0",
|
||||
"build:bundle": "echo 0",
|
||||
"prepublish": "echo 0",
|
||||
"lint": "echo 0",
|
||||
"lint:ci": "echo 0",
|
||||
"test": "echo 0",
|
||||
"test:ci": "echo 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"apr-main": "^4.0.3",
|
||||
"cloudapi-gql": "^8.0.0",
|
||||
"brule": "^3.1.0",
|
||||
"cloudapi-gql": "^7.1.4",
|
||||
"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",
|
||||
"h2o2": "^8.0.1",
|
||||
"hapi": "^17.3.1",
|
||||
"hapi-triton-auth": "^2.0.1",
|
||||
"hapi-webconsole-nav": "^1.2.0",
|
||||
"inert": "^5.1.0",
|
||||
"my-joy-images": "*",
|
||||
"my-joy-instances": "*",
|
||||
"my-joy-navigation": "*",
|
||||
"my-joy-service-groups": "*",
|
||||
"my-joy-templates": "*",
|
||||
"tsg-graphql": "^1.0.0"
|
||||
"rollover": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,7 @@
|
||||
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,
|
||||
@ -30,35 +25,19 @@ Main(async () => {
|
||||
BASE_URL
|
||||
});
|
||||
|
||||
await server.register([
|
||||
{
|
||||
plugin: Graphi,
|
||||
options: {
|
||||
graphqlPath: '/graphql',
|
||||
graphiqlPath: '/graphiql',
|
||||
authStrategy: 'sso'
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
await server.register({
|
||||
plugin: CloudApiGql,
|
||||
options: {
|
||||
authStrategy: 'sso',
|
||||
keyPath,
|
||||
keyId,
|
||||
apiBaseUrl,
|
||||
dcName
|
||||
},
|
||||
{
|
||||
plugin: CloudApiGql,
|
||||
options: {
|
||||
authStrategy: 'sso',
|
||||
keyPath,
|
||||
keyId,
|
||||
apiBaseUrl,
|
||||
dcName
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: Ui
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
await server.start();
|
||||
});
|
||||
|
@ -1,12 +1,7 @@
|
||||
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,
|
||||
@ -30,35 +25,19 @@ Main(async () => {
|
||||
BASE_URL
|
||||
});
|
||||
|
||||
await server.register([
|
||||
{
|
||||
plugin: Graphi,
|
||||
options: {
|
||||
graphqlPath: '/graphql',
|
||||
graphiqlPath: '/graphiql',
|
||||
authStrategy: 'sso'
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
await server.register({
|
||||
plugin: CloudApiGql,
|
||||
options: {
|
||||
authStrategy: 'sso',
|
||||
keyPath,
|
||||
keyId,
|
||||
apiBaseUrl,
|
||||
dcName
|
||||
},
|
||||
{
|
||||
plugin: CloudApiGql,
|
||||
options: {
|
||||
authStrategy: 'sso',
|
||||
keyPath,
|
||||
keyId,
|
||||
apiBaseUrl,
|
||||
dcName
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: Ui
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
await server.start();
|
||||
});
|
||||
|
@ -1,12 +1,7 @@
|
||||
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');
|
||||
@ -35,38 +30,22 @@ Main(async () => {
|
||||
BASE_URL
|
||||
});
|
||||
|
||||
await server.register([
|
||||
{
|
||||
plugin: Graphi,
|
||||
options: {
|
||||
graphqlPath: '/graphql',
|
||||
graphiqlPath: '/graphiql',
|
||||
authStrategy: 'sso'
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
await server.register({
|
||||
plugin: Nav,
|
||||
options: {
|
||||
keyPath,
|
||||
keyId,
|
||||
apiBaseUrl,
|
||||
dcName,
|
||||
baseUrl,
|
||||
regions: Regions,
|
||||
accountServices: Account,
|
||||
categories: Categories
|
||||
},
|
||||
{
|
||||
plugin: Nav,
|
||||
options: {
|
||||
keyPath,
|
||||
keyId,
|
||||
apiBaseUrl,
|
||||
dcName,
|
||||
baseUrl,
|
||||
regions: Regions,
|
||||
accountServices: Account,
|
||||
categories: Categories
|
||||
},
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
},
|
||||
{
|
||||
plugin: Ui
|
||||
routes: {
|
||||
prefix: `/${PREFIX}`
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
await server.start();
|
||||
});
|
||||
|
@ -1,7 +1,6 @@
|
||||
require('../.env.js');
|
||||
|
||||
const Hapi = require('hapi');
|
||||
const Sso = require('hapi-triton-auth');
|
||||
const Url = require('url');
|
||||
|
||||
const {
|
||||
COOKIE_PASSWORD,
|
||||
@ -9,10 +8,12 @@ const {
|
||||
SDC_KEY_PATH,
|
||||
SDC_ACCOUNT,
|
||||
SDC_KEY_ID,
|
||||
SDC_URL
|
||||
SDC_URL,
|
||||
DC_NAME
|
||||
} = process.env;
|
||||
|
||||
module.exports = async ({ PORT, BASE_URL }) => {
|
||||
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;
|
||||
@ -49,7 +50,6 @@ module.exports = async ({ PORT, BASE_URL }) => {
|
||||
|
||||
server.events.on('log', (event, tags) => {
|
||||
if (tags.error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(event);
|
||||
}
|
||||
});
|
||||
@ -58,7 +58,6 @@ module.exports = async ({ PORT, BASE_URL }) => {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
@ -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();
|
||||
});
|
@ -4,16 +4,7 @@ module.exports = {
|
||||
'scope-enum': [
|
||||
2,
|
||||
'always',
|
||||
[
|
||||
'ui-toolkit',
|
||||
'icons',
|
||||
'instances',
|
||||
'navigation',
|
||||
'bundle',
|
||||
'images',
|
||||
'sg',
|
||||
'templates'
|
||||
]
|
||||
['ui-toolkit', 'icons', 'instances', 'navigation', 'bundle', 'images']
|
||||
]
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
@ -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>
|
||||
);
|
@ -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')
|
||||
);
|
@ -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
|
||||
});
|
||||
};
|
@ -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();
|
@ -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');
|
@ -1,54 +0,0 @@
|
||||
const get = require('lodash.get');
|
||||
const Document = require('hapi-render-react-joyent-document');
|
||||
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 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: 'instances'
|
||||
})
|
||||
)
|
||||
}),
|
||||
monoFont: Object.assign({}, theme.monoFont, {
|
||||
href: () =>
|
||||
_mono(
|
||||
Object.assign(_addr, {
|
||||
namespace: 'instances'
|
||||
})
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
return {
|
||||
theme: _theme,
|
||||
createClient,
|
||||
createStore
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = Document({
|
||||
namespace: 'instances/',
|
||||
assets,
|
||||
Html: require('./html'),
|
||||
getState
|
||||
});
|
@ -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>Instances</title>
|
||||
</Helmet>
|
||||
<Routes />
|
||||
</RootContainer>
|
||||
);
|
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 13 KiB |
@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import { P as BaseP } from 'joyent-ui-toolkit';
|
||||
import { Row, Col } from 'joyent-react-styled-flexboxgrid';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const P = styled(BaseP)`
|
||||
font-weight: 200;
|
||||
`;
|
||||
|
||||
export default ({ href = '', children }) => (
|
||||
<Row>
|
||||
<Col xs="12" sm="7">
|
||||
<P>
|
||||
{children}{' '}
|
||||
{href ? (
|
||||
<a target="__blank" href={href} rel="noopener noreferrer">
|
||||
Read the docs
|
||||
</a>
|
||||
) : null}
|
||||
</P>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
@ -1,173 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Row, Col } from 'joyent-react-styled-flexboxgrid';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
import Flex from 'styled-flex-component';
|
||||
|
||||
import {
|
||||
Button,
|
||||
StickyFooter,
|
||||
QueryBreakpoints,
|
||||
StartIcon,
|
||||
StopIcon,
|
||||
ResetIcon,
|
||||
DeleteIcon
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
const { SmallOnly, Medium } = QueryBreakpoints;
|
||||
|
||||
export default ({
|
||||
submitting = false,
|
||||
statuses = {},
|
||||
allowedActions = {},
|
||||
onStart,
|
||||
onStop,
|
||||
onReboot,
|
||||
onRemove
|
||||
}) => (
|
||||
<StickyFooter fill="disabled" fixed bottom>
|
||||
<Row between="xs" middle="xs">
|
||||
<Col xs="7">
|
||||
<Flex>
|
||||
{onStart && [
|
||||
<SmallOnly key="small-only">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onStart}
|
||||
disabled={submitting || !allowedActions.start}
|
||||
loading={submitting && statuses.starting}
|
||||
secondary
|
||||
small
|
||||
icon
|
||||
>
|
||||
<StartIcon disabled={submitting || !allowedActions.start} />
|
||||
</Button>
|
||||
</SmallOnly>,
|
||||
<Margin right="1">
|
||||
<Medium key="medium">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onStart}
|
||||
disabled={submitting || !allowedActions.start}
|
||||
loading={submitting && statuses.starting}
|
||||
secondary
|
||||
icon
|
||||
>
|
||||
<Margin right="1">
|
||||
<StartIcon disabled={submitting || !allowedActions.start} />
|
||||
</Margin>
|
||||
<span>Start</span>
|
||||
</Button>
|
||||
</Medium>
|
||||
</Margin>
|
||||
]}
|
||||
{onStop && [
|
||||
<SmallOnly key="small-only">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onStop}
|
||||
disabled={submitting || !allowedActions.stop}
|
||||
loading={submitting && statuses.stopping}
|
||||
secondary
|
||||
small
|
||||
icon
|
||||
>
|
||||
<StopIcon disabled={submitting || !allowedActions.stop} />
|
||||
</Button>
|
||||
</SmallOnly>,
|
||||
<Margin right="1">
|
||||
<Medium key="medium">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onStop}
|
||||
disabled={submitting || !allowedActions.stop}
|
||||
loading={submitting && statuses.stopping}
|
||||
secondary
|
||||
icon
|
||||
>
|
||||
<Margin right="1">
|
||||
<StopIcon disabled={submitting || !allowedActions.stop} />
|
||||
</Margin>
|
||||
<span>Stop</span>
|
||||
</Button>
|
||||
</Medium>
|
||||
</Margin>
|
||||
]}
|
||||
{onReboot && [
|
||||
<SmallOnly key="small-only">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onReboot}
|
||||
disabled={submitting || !allowedActions.reboot}
|
||||
loading={submitting && statuses.rebooting}
|
||||
secondary
|
||||
small
|
||||
icon
|
||||
>
|
||||
<ResetIcon disabled={submitting || !allowedActions.reboot} />
|
||||
</Button>
|
||||
</SmallOnly>,
|
||||
<Margin right="1">
|
||||
<Medium key="medium">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onReboot}
|
||||
disabled={submitting || !allowedActions.reboot}
|
||||
loading={submitting && statuses.rebooting}
|
||||
secondary
|
||||
icon
|
||||
>
|
||||
<ResetIcon disabled={submitting || !allowedActions.reboot} />
|
||||
<span>Reboot</span>
|
||||
</Button>
|
||||
</Medium>
|
||||
</Margin>
|
||||
]}
|
||||
</Flex>
|
||||
</Col>
|
||||
{onRemove && (
|
||||
<Col xs="5">
|
||||
<SmallOnly key="small-only">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onRemove}
|
||||
disabled={submitting || !allowedActions.remove}
|
||||
loading={submitting && statuses.removing}
|
||||
secondary
|
||||
error
|
||||
right
|
||||
small
|
||||
icon
|
||||
>
|
||||
<DeleteIcon
|
||||
disabled={submitting}
|
||||
fill={submitting || !allowedActions.remove ? undefined : 'red'}
|
||||
/>
|
||||
</Button>
|
||||
</SmallOnly>
|
||||
<Medium key="medium">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={onRemove}
|
||||
disabled={submitting || !allowedActions.remove}
|
||||
loading={submitting && statuses.removing}
|
||||
error
|
||||
secondary
|
||||
right
|
||||
icon
|
||||
>
|
||||
<Margin right="1">
|
||||
<DeleteIcon
|
||||
disabled={submitting || !allowedActions.remove}
|
||||
fill={
|
||||
submitting || !allowedActions.remove ? undefined : 'red'
|
||||
}
|
||||
/>
|
||||
</Margin>
|
||||
<span>Remove</span>
|
||||
</Button>
|
||||
</Medium>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
</StickyFooter>
|
||||
);
|
@ -1,394 +0,0 @@
|
||||
import React from 'react';
|
||||
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
|
||||
import { Row, Col } from 'joyent-react-styled-flexboxgrid';
|
||||
import Flex, { FlexItem } from 'styled-flex-component';
|
||||
import styled from 'styled-components';
|
||||
import { Margin, Padding } from 'styled-components-spacing';
|
||||
import titleCase from 'title-case';
|
||||
import get from 'lodash.get';
|
||||
import remcalc from 'remcalc';
|
||||
import { Field } from 'redux-form';
|
||||
import { ValueBreakpoints as breakpoints } from 'joyent-ui-toolkit';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardOutlet,
|
||||
Divider,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
PopoverButton,
|
||||
PopoverItem as BasePopoverItem,
|
||||
H2 as BaseH2,
|
||||
Label as BaseLabel,
|
||||
CopiableField,
|
||||
QueryBreakpoints,
|
||||
DotIcon,
|
||||
DeleteIcon,
|
||||
StartIcon,
|
||||
StopIcon,
|
||||
EditIcon,
|
||||
Input,
|
||||
FormMeta,
|
||||
FormGroup
|
||||
} from 'joyent-ui-toolkit';
|
||||
|
||||
import GLOBAL from '@state/global';
|
||||
|
||||
const { SmallOnly, Medium } = QueryBreakpoints;
|
||||
|
||||
const stateColor = {
|
||||
PROVISIONING: 'primary',
|
||||
RUNNING: 'green',
|
||||
STOPPING: 'grey',
|
||||
STOPPED: 'grey',
|
||||
DELETED: 'secondaryActive',
|
||||
FAILED: 'red'
|
||||
};
|
||||
|
||||
const Label = styled(BaseLabel)`
|
||||
font-weight: 200;
|
||||
`;
|
||||
|
||||
const GreyLabel = styled(Label)`
|
||||
opacity: 0.5;
|
||||
padding-right: ${remcalc(3)};
|
||||
`;
|
||||
|
||||
const TrimedLabel = styled(Label)`
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const Actionable = styled(Margin)`
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const VerticalDivider = styled.div`
|
||||
width: ${remcalc(1)};
|
||||
background: ${props => props.theme.grey};
|
||||
height: ${remcalc(24)};
|
||||
display: flex;
|
||||
align-self: flex-end;
|
||||
margin: 0 ${remcalc(12)};
|
||||
|
||||
@media (max-width: ${remcalc(breakpoints.small.upper)}) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const H2 = styled(BaseH2)`
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
const PopoverItem = styled(BasePopoverItem)`
|
||||
padding-bottom: ${remcalc(11)};
|
||||
`;
|
||||
|
||||
export const Meta = ({
|
||||
created,
|
||||
updated,
|
||||
state,
|
||||
brand,
|
||||
image,
|
||||
editingName,
|
||||
handleSubmit,
|
||||
editName,
|
||||
disabled,
|
||||
submitting,
|
||||
...instance
|
||||
}) => [
|
||||
<Row middle="xs">
|
||||
<Col xs="12">
|
||||
<Margin bottom="1">
|
||||
<H2>
|
||||
{editingName ? (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Flex alignStart>
|
||||
<FormGroup name="name" field={Field}>
|
||||
<Input
|
||||
onBlur={null}
|
||||
type="text"
|
||||
placeholder={instance.name}
|
||||
disabled={disabled || submitting}
|
||||
/>
|
||||
<FormMeta />
|
||||
</FormGroup>
|
||||
<Margin left="1">
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={submitting}
|
||||
loading={submitting}
|
||||
inline
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Margin>
|
||||
</Flex>
|
||||
</form>
|
||||
) : (
|
||||
<Flex>
|
||||
{instance.name}
|
||||
<Actionable left="2" onClick={editName}>
|
||||
<EditIcon />
|
||||
</Actionable>
|
||||
</Flex>
|
||||
)}
|
||||
</H2>
|
||||
</Margin>
|
||||
</Col>
|
||||
</Row>,
|
||||
<Margin vertical="1">
|
||||
<Flex>
|
||||
<TrimedLabel>
|
||||
{image && image.name ? titleCase(image.name) : 'Custom Image'}
|
||||
</TrimedLabel>
|
||||
<VerticalDivider />
|
||||
<TrimedLabel>
|
||||
{brand === 'LX'
|
||||
? 'Infrastructure container'
|
||||
: 'Hardware virtual machine'}
|
||||
</TrimedLabel>
|
||||
<VerticalDivider />
|
||||
<TrimedLabel>{(instance.package || {}).name}</TrimedLabel>
|
||||
<VerticalDivider />
|
||||
<Flex>
|
||||
<DotIcon
|
||||
right={remcalc(6)}
|
||||
size={remcalc(15)}
|
||||
color={stateColor[state]}
|
||||
/>
|
||||
{titleCase(state)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Margin top="1">
|
||||
<Flex>
|
||||
<Flex>
|
||||
<GreyLabel>Created: </GreyLabel>
|
||||
<Label> {distanceInWordsToNow(created)} ago</Label>
|
||||
</Flex>
|
||||
<VerticalDivider />
|
||||
<Flex>
|
||||
<GreyLabel>Updated: </GreyLabel>
|
||||
<Label> {distanceInWordsToNow(updated)} ago</Label>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Margin>
|
||||
</Margin>
|
||||
];
|
||||
|
||||
export default ({
|
||||
instance = {},
|
||||
starting = false,
|
||||
stopping = false,
|
||||
rebooting = false,
|
||||
removing = false,
|
||||
onAction,
|
||||
...props
|
||||
}) => (
|
||||
<Row>
|
||||
<Col xs="12" sm="12" md="9">
|
||||
<Card>
|
||||
<CardOutlet>
|
||||
<Padding all="5">
|
||||
<Meta {...instance} {...props} />
|
||||
<Margin top="3">
|
||||
<Row between="xs">
|
||||
<Col xs="9">
|
||||
<Flex>
|
||||
<FlexItem>
|
||||
<Margin right="1">
|
||||
<ButtonGroup>
|
||||
{instance.state === 'STOPPED' ? (
|
||||
<Button
|
||||
type="button"
|
||||
loading={starting}
|
||||
disabled={instance.state !== 'STOPPED'}
|
||||
onClick={() => onAction('start')}
|
||||
secondary
|
||||
bold
|
||||
icon
|
||||
>
|
||||
<Margin right="2">
|
||||
<StartIcon
|
||||
disabled={instance.state !== 'STOPPED'}
|
||||
/>
|
||||
</Margin>
|
||||
<span>Start</span>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="button"
|
||||
loading={stopping}
|
||||
disabled={instance.state !== 'RUNNING'}
|
||||
onClick={() => onAction('stop')}
|
||||
secondary
|
||||
bold
|
||||
icon
|
||||
>
|
||||
<Margin right="2">
|
||||
<StopIcon
|
||||
disabled={instance.state !== 'RUNNING'}
|
||||
/>
|
||||
</Margin>
|
||||
<span>Stop</span>
|
||||
</Button>
|
||||
)}
|
||||
<PopoverButton secondary>
|
||||
<PopoverItem
|
||||
disabled={instance.state === 'RUNNING'}
|
||||
onClick={() =>
|
||||
instance.state === 'RUNNING'
|
||||
? null
|
||||
: onAction('start')
|
||||
}
|
||||
>
|
||||
Start
|
||||
</PopoverItem>
|
||||
<PopoverItem
|
||||
disabled={instance.state === 'STOPPED'}
|
||||
onClick={() =>
|
||||
instance.state === 'STOPPED'
|
||||
? null
|
||||
: onAction('reboot')
|
||||
}
|
||||
>
|
||||
Restart
|
||||
</PopoverItem>
|
||||
<PopoverItem
|
||||
disabled={instance.state === 'STOPPED'}
|
||||
onClick={() =>
|
||||
instance.state === 'STOPPED'
|
||||
? null
|
||||
: onAction('stop')
|
||||
}
|
||||
>
|
||||
Stop
|
||||
</PopoverItem>
|
||||
</PopoverButton>
|
||||
</ButtonGroup>
|
||||
</Margin>
|
||||
</FlexItem>
|
||||
<FlexItem>
|
||||
<Button
|
||||
href={`${GLOBAL.origin}/images/~create/${instance.id}`}
|
||||
target="__blank"
|
||||
rel="noopener noreferrer"
|
||||
secondary
|
||||
bold
|
||||
icon
|
||||
>
|
||||
Create Image
|
||||
</Button>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
</Col>
|
||||
<Col xs="3">
|
||||
<SmallOnly>
|
||||
<Button
|
||||
type="button"
|
||||
loading={removing}
|
||||
disabled={
|
||||
['RUNNING', 'STOPPED'].indexOf(instance.state) < 0
|
||||
}
|
||||
onClick={() => onAction('remove')}
|
||||
secondary
|
||||
small
|
||||
right
|
||||
icon
|
||||
error
|
||||
>
|
||||
<Margin right="2">
|
||||
<DeleteIcon
|
||||
fill="red"
|
||||
disabled={
|
||||
['RUNNING', 'STOPPED'].indexOf(instance.state) < 0
|
||||
}
|
||||
/>
|
||||
</Margin>
|
||||
</Button>
|
||||
</SmallOnly>
|
||||
<Medium>
|
||||
<Button
|
||||
type="button"
|
||||
loading={removing}
|
||||
disabled={
|
||||
['RUNNING', 'STOPPED'].indexOf(instance.state) < 0
|
||||
}
|
||||
onClick={() => onAction('remove')}
|
||||
secondary
|
||||
bold
|
||||
right
|
||||
icon
|
||||
error
|
||||
>
|
||||
<Margin right="2">
|
||||
<DeleteIcon
|
||||
fill={
|
||||
['RUNNING', 'STOPPED'].indexOf(instance.state) >= 0
|
||||
? 'red'
|
||||
: undefined
|
||||
}
|
||||
disabled={
|
||||
['RUNNING', 'STOPPED'].indexOf(instance.state) < 0
|
||||
}
|
||||
/>
|
||||
</Margin>
|
||||
<span>Delete</span>
|
||||
</Button>
|
||||
</Medium>
|
||||
</Col>
|
||||
</Row>
|
||||
</Margin>
|
||||
<Margin bottom="5" top="3">
|
||||
<Divider height={1} />
|
||||
</Margin>
|
||||
<Margin bottom="3">
|
||||
<CopiableField
|
||||
text={(instance.id || '').split('-')[0]}
|
||||
label="Short ID"
|
||||
/>
|
||||
</Margin>
|
||||
<Margin bottom="3">
|
||||
<CopiableField text={instance.id} label="ID" />
|
||||
</Margin>
|
||||
<Margin bottom="3">
|
||||
<CopiableField text={instance.compute_node} label="CN UUID" />
|
||||
</Margin>
|
||||
{instance.image &&
|
||||
instance.image.id && (
|
||||
<Margin bottom="3">
|
||||
<CopiableField text={instance.image.id} label="Image UUID" />
|
||||
</Margin>
|
||||
)}
|
||||
<Margin bottom="3">
|
||||
<CopiableField
|
||||
text={`ssh root@${instance.primary_ip}`}
|
||||
label="Login"
|
||||
/>
|
||||
</Margin>
|
||||
{get(instance, 'ips.public', []).map((ip, i, ips) => (
|
||||
<Margin bottom="3">
|
||||
<CopiableField
|
||||
key={`public-${i}`}
|
||||
label={`Public IP address ${ips.length > 1 ? i + 1 : ''}`}
|
||||
text={ip}
|
||||
/>
|
||||
</Margin>
|
||||
))}
|
||||
{get(instance, 'ips.private', []).map((ip, i, ips) => (
|
||||
<Margin bottom="3">
|
||||
<CopiableField
|
||||
key={`private-${i}`}
|
||||
noMargin={i === ips.length - 1}
|
||||
label={`Private IP address ${ips.length > 1 ? i + 1 : ''}`}
|
||||
text={ip}
|
||||
/>
|
||||
</Margin>
|
||||
))}
|
||||
</Padding>
|
||||
</CardOutlet>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
@ -1,353 +0,0 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Margin, Padding } from 'styled-components-spacing';
|
||||
import { graphql, compose } from 'react-apollo';
|
||||
import ReduxForm from 'declarative-redux-form';
|
||||
import { connect } from 'react-redux';
|
||||
import { SubmissionError, destroy } from 'redux-form';
|
||||
import { set, destroyAll } from 'react-redux-values';
|
||||
import Flex, { FlexItem } from 'styled-flex-component';
|
||||
import intercept from 'apr-intercept';
|
||||
import get from 'lodash.get';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
Name,
|
||||
Image,
|
||||
Package,
|
||||
Networks,
|
||||
Tags,
|
||||
Metadata,
|
||||
UserScript,
|
||||
Firewall,
|
||||
CNS,
|
||||
Affinity,
|
||||
generatePayload,
|
||||
Footer
|
||||
} from 'joyent-ui-instance-steps';
|
||||
|
||||
import { Provider as ResourceSteps } from 'joyent-ui-resource-step';
|
||||
import { Anchor, H3, ViewContainer, Button } from 'joyent-ui-toolkit';
|
||||
|
||||
import { Forms, Values } from '@root/constants';
|
||||
import parseError from '@state/parse-error';
|
||||
import CreateInstanceMutation from '@graphql/create-instance.gql';
|
||||
|
||||
const { IC_F } = Forms;
|
||||
const { IC_SHOW_CLI } = Values;
|
||||
|
||||
const names = {
|
||||
name: 'IC_NAME',
|
||||
image: 'IC_IMAGE',
|
||||
package: 'IC_PACKAGE',
|
||||
networks: 'IC_NETWORKS',
|
||||
tags: 'IC_TAGS',
|
||||
metadata: 'IC_METADATA',
|
||||
'user-script': 'IC_USERSCRIPT',
|
||||
firewall: 'IC_FIREWALL',
|
||||
cns: 'IC_CNS',
|
||||
affinity: 'IC_AFFINITY'
|
||||
};
|
||||
|
||||
class CreateInstance extends Component {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.isValids = {};
|
||||
}
|
||||
|
||||
setIsValid = name => ref => {
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { isValid } = ref;
|
||||
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isValids = Object.assign({}, this.isValids, {
|
||||
[name]: isValid
|
||||
});
|
||||
};
|
||||
|
||||
isFormValid = () => {
|
||||
const { steps } = this.props;
|
||||
|
||||
return Object.keys(this.isValids).every(name =>
|
||||
this.isValids[name](steps[name] || {})
|
||||
);
|
||||
};
|
||||
|
||||
isStepValid = step => {
|
||||
const { steps } = this.props;
|
||||
const fn = this.isValids[step];
|
||||
const values = steps[step];
|
||||
|
||||
if (!fn || !values) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return fn(values);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
match,
|
||||
steps,
|
||||
showCli = false,
|
||||
handleDefocus,
|
||||
handleToggleShowCli,
|
||||
handleSubmit,
|
||||
disabled
|
||||
} = this.props;
|
||||
|
||||
const { params } = match;
|
||||
const { step } = params;
|
||||
|
||||
const {
|
||||
name,
|
||||
image,
|
||||
package: pkg,
|
||||
networks,
|
||||
tags,
|
||||
metadata,
|
||||
firewall,
|
||||
cns,
|
||||
affinity
|
||||
} = steps;
|
||||
|
||||
const Mask = styled.div`
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
position: absolute;
|
||||
display: ${showCli ? 'block' : 'none'};
|
||||
`;
|
||||
|
||||
return (
|
||||
<ViewContainer main>
|
||||
<Margin top="5">
|
||||
<H3>Create Instance</H3>
|
||||
</Margin>
|
||||
<Padding top="5">
|
||||
<ResourceSteps namespace="instances/~create">
|
||||
<Margin bottom="5">
|
||||
<Name
|
||||
ref={this.setIsValid('name')}
|
||||
expanded={step === 'name'}
|
||||
next="image"
|
||||
saved={get(steps, 'name.name', false)}
|
||||
onDefocus={handleDefocus('name')}
|
||||
preview={name}
|
||||
isValid={this.isStepValid('name')}
|
||||
/>
|
||||
</Margin>
|
||||
<Margin bottom="5">
|
||||
<Image
|
||||
ref={this.setIsValid('image')}
|
||||
expanded={step === 'image'}
|
||||
next="package"
|
||||
saved={steps.image && steps.image.id}
|
||||
onDefocus={handleDefocus('image')}
|
||||
preview={image}
|
||||
/>
|
||||
</Margin>
|
||||
<Margin bottom="5">
|
||||
<Package
|
||||
ref={this.setIsValid('package')}
|
||||
expanded={step === 'package'}
|
||||
next="networks"
|
||||
saved={steps.package}
|
||||
onDefocus={handleDefocus('package')}
|
||||
preview={pkg}
|
||||
/>
|
||||
</Margin>
|
||||
<Margin bottom="5">
|
||||
<Networks
|
||||
ref={this.setIsValid('networks')}
|
||||
expanded={step === 'networks'}
|
||||
next="tags"
|
||||
saved={steps.networks}
|
||||
onDefocus={handleDefocus('networks')}
|
||||
preview={networks}
|
||||
/>
|
||||
</Margin>
|
||||
<Margin bottom="5">
|
||||
<Tags
|
||||
ref={this.setIsValid('tags')}
|
||||
expanded={step === 'tags'}
|
||||
next="metadata"
|
||||
saved={steps.tags && steps.tags.length}
|
||||
onDefocus={handleDefocus('tags')}
|
||||
preview={tags}
|
||||
optional
|
||||
/>
|
||||
</Margin>
|
||||
<Margin bottom="5">
|
||||
<Metadata
|
||||
ref={this.setIsValid('metadata')}
|
||||
expanded={step === 'metadata'}
|
||||
next="user-script"
|
||||
saved={steps.metadata && steps.metadata.length}
|
||||
onDefocus={handleDefocus('metadata')}
|
||||
preview={metadata}
|
||||
optional
|
||||
/>
|
||||
</Margin>
|
||||
<Margin bottom="5">
|
||||
<UserScript
|
||||
ref={this.setIsValid('user-script')}
|
||||
expanded={step === 'user-script'}
|
||||
next="firewall"
|
||||
saved={get(steps, 'user-script.lines', false)}
|
||||
onDefocus={handleDefocus('user-script')}
|
||||
preview={steps['user-script']}
|
||||
optional
|
||||
/>
|
||||
</Margin>
|
||||
<Margin bottom="5">
|
||||
<Firewall
|
||||
ref={this.setIsValid('firewall')}
|
||||
expanded={step === 'firewall'}
|
||||
next="cns"
|
||||
saved={steps.firewall}
|
||||
onDefocus={handleDefocus('firewall')}
|
||||
preview={firewall}
|
||||
optional
|
||||
/>
|
||||
</Margin>
|
||||
<Margin bottom="5">
|
||||
<CNS
|
||||
ref={this.setIsValid('cns')}
|
||||
expanded={step === 'cns'}
|
||||
next="affinity"
|
||||
saved={steps.cns}
|
||||
onDefocus={handleDefocus('cns')}
|
||||
preview={cns}
|
||||
optional
|
||||
/>
|
||||
</Margin>
|
||||
<Margin bottom="5">
|
||||
<Affinity
|
||||
ref={this.setIsValid('affinity')}
|
||||
expanded={step === 'affinity'}
|
||||
next=""
|
||||
saved={steps.affinity}
|
||||
onDefocus={handleDefocus('affinity')}
|
||||
preview={affinity}
|
||||
optional
|
||||
/>
|
||||
</Margin>
|
||||
</ResourceSteps>
|
||||
<Margin bottom="5">
|
||||
<Flex alignCenter>
|
||||
<FlexItem>
|
||||
<ReduxForm form={IC_F} onSubmit={handleSubmit}>
|
||||
{({ handleSubmit, submitting }) => (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Button
|
||||
disabled={disabled || !this.isFormValid()}
|
||||
loading={submitting}
|
||||
>
|
||||
Deploy
|
||||
</Button>
|
||||
</form>
|
||||
)}
|
||||
</ReduxForm>
|
||||
</FlexItem>
|
||||
<FlexItem>
|
||||
<Margin left={3}>
|
||||
<Anchor
|
||||
disabled={disabled || !this.isFormValid()}
|
||||
onClick={() => handleToggleShowCli(!showCli)}
|
||||
>
|
||||
View CLI Details
|
||||
</Anchor>
|
||||
</Margin>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
</Margin>
|
||||
</Padding>
|
||||
{showCli ? (
|
||||
<Fragment>
|
||||
<Footer
|
||||
getData={() => generatePayload(steps)}
|
||||
onCloseCli={() => handleToggleShowCli(!showCli)}
|
||||
/>
|
||||
<Mask onClick={() => handleToggleShowCli(!showCli)} />
|
||||
</Fragment>
|
||||
) : null}
|
||||
</ViewContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
graphql(CreateInstanceMutation, { name: 'createInstance' }),
|
||||
connect(({ form, values = {} }, { match, location }) => {
|
||||
const steps = {
|
||||
name: values[names.name],
|
||||
image: values[names.image],
|
||||
package: values[names.package],
|
||||
networks: values[names.networks],
|
||||
tags: values[names.tags],
|
||||
metadata: values[names.metadata],
|
||||
'user-script': values[names['user-script']],
|
||||
firewall: values[names.firewall],
|
||||
cns: values[names.cns],
|
||||
affinity: values[names.affinity]
|
||||
};
|
||||
|
||||
const error = get(form, `${IC_F}.error`, null);
|
||||
const showCli = Boolean(values[IC_SHOW_CLI]);
|
||||
|
||||
// Maybe re-use saved to only write the rule once
|
||||
const disabled = !(
|
||||
!error &&
|
||||
steps.name &&
|
||||
steps.name.name &&
|
||||
steps.image &&
|
||||
steps.image.id &&
|
||||
steps.package &&
|
||||
steps.package.id &&
|
||||
steps.networks &&
|
||||
Array.isArray(steps.networks)
|
||||
);
|
||||
|
||||
return {
|
||||
disabled,
|
||||
showCli,
|
||||
forms: Object.keys(form), // improve this
|
||||
error,
|
||||
steps
|
||||
};
|
||||
}),
|
||||
connect(null, (dispatch, { steps = {}, forms, history, createInstance }) => {
|
||||
return {
|
||||
handleToggleShowCli: value => {
|
||||
return dispatch(set({ name: IC_SHOW_CLI, value }));
|
||||
},
|
||||
handleDefocus: name => value => {
|
||||
return dispatch(set({ name: names[name], value }));
|
||||
},
|
||||
handleSubmit: async () => {
|
||||
const [err, res] = await intercept(
|
||||
createInstance({
|
||||
variables: generatePayload(steps)
|
||||
})
|
||||
);
|
||||
|
||||
if (err) {
|
||||
throw new SubmissionError({
|
||||
_error: parseError(err)
|
||||
});
|
||||
}
|
||||
|
||||
dispatch([destroyAll(), forms.map(name => destroy(name))]);
|
||||
history.push(`/instances/${res.data.createMachine.id}`);
|
||||
}
|
||||
};
|
||||
})
|
||||
)(CreateInstance);
|
Before Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 115 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 122 KiB |
Before Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 73 KiB |
Before Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 20 KiB |