diff --git a/.travis.yml b/.travis.yml index 9f2f128b..0524cb03 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,14 @@ language: node_js node_js: - '8' script: - - echo 0 + - npm run test-ci +# addons: +# chrome: stable +# before_install: +# - # start your web application and listen on `127.0.0.1` +# - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost & +# script: +# - npm run test-ci + # - eslint-gh-status-reporter + # - stylelint-gh-status-reporter + # - lighthouse-gh-status-reporter --chrome-port=9222 --chrome-hostname=localhost diff --git a/package.json b/package.json index a12cff80..aebee4a3 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "eslint-config-prettier": "^2.3.0", "eslint-config-react-app": "^2.0.0", "eslint-config-xo-space": "^0.16.0", + "eslint-gh-status-reporter": "^1.0.7", "eslint-plugin-flowtype": "^2.35.1", "eslint-plugin-import": "^2.7.0", "eslint-plugin-jsx-a11y": "^6.0.2", @@ -61,12 +62,14 @@ "husky": "^0.14.3", "lerna": "^2.1.2", "license-to-fail": "^2.2.0", + "lighthouse-gh-status-reporter": "^1.0.12", "lodash.uniq": "^4.5.0", "prettier": "1.6.1", "quality-docs": "^3.3.0", "read-pkg": "^2.0.0", "redrun": "^5.9.17", "staged-git-files": "0.0.4", + "stylelint-gh-status-reporter": "^1.0.7", "yargs": "^8.0.2" }, "workspaces": [ diff --git a/packages/cloudapi-gql/package.json b/packages/cloudapi-gql/package.json index e1f3e390..1c19e831 100644 --- a/packages/cloudapi-gql/package.json +++ b/packages/cloudapi-gql/package.json @@ -20,6 +20,8 @@ "express-graphql": "^0.6.11", "got": "^7.1.0", "graphql": "^0.11.2", + "graphql-tools": "^1.2.2", + "minimist": "^1.2.0", "smartdc-auth": "^2.5.5", "triton": "^5.3.1" }, diff --git a/packages/cloudapi-gql/src/endpoint.js b/packages/cloudapi-gql/src/endpoint.js index 253379aa..f04af109 100644 --- a/packages/cloudapi-gql/src/endpoint.js +++ b/packages/cloudapi-gql/src/endpoint.js @@ -1,12 +1,11 @@ -const { GraphQLSchema } = require('graphql'); -const graphqlHTTP = require('express-graphql'); +// const argv = require('minimist')(process.argv.slice(2)); +// const { makeExecutableSchema, addMockFunctionsToSchema } = require('graphql-tools'); + const { query, mutation } = require('./schema'); -module.exports = graphqlHTTP(() => ({ - schema: new GraphQLSchema({ - query, - mutation - }), - graphiql: true, - pretty: true -})); +// console.log(new GraphQLSchema({ +// query, +// mutation +// })); +// +// diff --git a/packages/cloudapi-gql/src/index.js b/packages/cloudapi-gql/src/index.js index 7c94809a..1ef92db5 100644 --- a/packages/cloudapi-gql/src/index.js +++ b/packages/cloudapi-gql/src/index.js @@ -1,12 +1,29 @@ const express = require('express'); +const graphqlHTTP = require('express-graphql'); const cors = require('cors'); +const schema = require('./schema'); + const app = express(); app.use(cors()); app.options('*', cors()); -app.use('/graphql', require('./endpoint')); +app.post( + '/graphql', + graphqlHTTP({ + schema, + graphiql: false + }) +); + +app.get( + '/graphql', + graphqlHTTP({ + schema, + graphiql: true + }) +); const server = app.listen(4000, err => { if (err) { diff --git a/packages/cloudapi-gql/src/schema/index.js b/packages/cloudapi-gql/src/schema/index.js index 31162ea9..4aaa5624 100644 --- a/packages/cloudapi-gql/src/schema/index.js +++ b/packages/cloudapi-gql/src/schema/index.js @@ -1,2 +1,9 @@ -exports.query = require('./queries'); -exports.mutation = require('./mutations'); +const { GraphQLSchema } = require('graphql'); + +const query = require('./queries'); +const mutation = require('./mutations'); + +module.exports = new GraphQLSchema({ + query, + mutation +}); diff --git a/packages/cloudapi-gql/src/schema/mutations/machines.js b/packages/cloudapi-gql/src/schema/mutations/machines.js index eea63c7d..f9fbdefb 100644 --- a/packages/cloudapi-gql/src/schema/mutations/machines.js +++ b/packages/cloudapi-gql/src/schema/mutations/machines.js @@ -189,10 +189,10 @@ module.exports.auditMachine = { type: new GraphQLNonNull(GraphQLID), description: 'The machine id' } - }, - resolve: (root, args) => { - return api.machines.destroy(args.id); } + // resolve: (root, args) => { + // return api.machines.destroy(args.id); + // } }; module.exports.setMachineFirewall = { diff --git a/packages/cloudapi-gql/src/schema/queries/firewall-rules.js b/packages/cloudapi-gql/src/schema/queries/firewall-rules.js index a05e4b8e..b82753c3 100644 --- a/packages/cloudapi-gql/src/schema/queries/firewall-rules.js +++ b/packages/cloudapi-gql/src/schema/queries/firewall-rules.js @@ -14,7 +14,6 @@ module.exports = { }, resolve(root, args) { const { list, get } = api.firewallRules; - return args.id ? get(args.id).then(rule => [rule]) : list(); } }; diff --git a/packages/cloudapi-gql/src/schema/types/firewall-rule.js b/packages/cloudapi-gql/src/schema/types/firewall-rule.js index 6ab9ce0c..986079f3 100644 --- a/packages/cloudapi-gql/src/schema/types/firewall-rule.js +++ b/packages/cloudapi-gql/src/schema/types/firewall-rule.js @@ -49,20 +49,22 @@ module.exports = new GraphQLObjectType({ } }, rule: { - type: FirewallRuleSyntaxType, + type: GraphQLString, //FirewallRuleSyntaxType, description: 'Firewall rule', resolve: ({ rule }) => { - const regex = /from (.*?) to (.*?) (allow|deny) (.*?) port (\d*)/i; - const tokens = rule.match(regex); - - return { - from: tokens[1], - to: tokens[2], - action: tokens[3], - protocol: tokens[4], - port: tokens[5], - text: rule - }; + return rule; + // console.log(rule); + // const regex = /from (.*?) to (.*?) (allow|deny) (.*?) port (\d*)/i; + // const tokens = rule.match(regex); + // + // return { + // from: tokens[1], + // to: tokens[2], + // action: tokens[3], + // protocol: tokens[4], + // port: tokens[5], + // text: rule + // }; } }, global: { diff --git a/packages/cloudapi-gql/src/schema/types/machine.js b/packages/cloudapi-gql/src/schema/types/machine.js index f04688e9..4e9366a7 100644 --- a/packages/cloudapi-gql/src/schema/types/machine.js +++ b/packages/cloudapi-gql/src/schema/types/machine.js @@ -92,8 +92,14 @@ module.exports = new GraphQLObjectType({ description: 'The IP addresses this instance has' }, networks: { - type: new GraphQLList(GraphQLString), - description: 'The network UUIDs of the nics this instance has' + type: new GraphQLList(require('./network')), + description: 'The networks of the nics this instance has', + resolve: (root, args) => { + const { networks } = root; + const { get } = api.networks; + + return Promise.all(networks.map(id => get(id))); + } }, primaryIp: { type: GraphQLString, @@ -110,8 +116,8 @@ module.exports = new GraphQLObjectType({ // Circular dependency type: new GraphQLList(require('./firewall-rule')), description: 'List of FirewallRules affecting this machine', - resolve: root => { - return api.firewallRules.listByMachine(root.id); + resolve: ({ id }) => { + return api.firewallRules.listByMachine({ id }); } }, computeNode: { @@ -131,15 +137,10 @@ module.exports = new GraphQLObjectType({ description: 'Filter on the name of the snapshot' } }, - resolve: (root, args) => { - const { snapshot: { list, get } } = api.machines; + resolve: ({ id }, args) => { + const { list, get } = api.machines.snapshots; - return args.id - ? list(root) - : get({ - id: root.id, - name: args.name - }); + return args.name ? get({ name: args.name, id: root.id }) : list({ id }); } } }) diff --git a/packages/joyent-boilerplate/.stylelintrc b/packages/joyent-boilerplate/.stylelintrc index c97c6aac..f82431c9 100644 --- a/packages/joyent-boilerplate/.stylelintrc +++ b/packages/joyent-boilerplate/.stylelintrc @@ -1,8 +1,3 @@ { - "processors": ["stylelint-processor-styled-components"], - "extends": [ - "stylelint-config-standard", - "stylelint-config-styled-components" - ], - "syntax": "scss" + "extends": ["stylelint-config-joyent-portal"] } \ No newline at end of file diff --git a/packages/joyent-boilerplate/package.json b/packages/joyent-boilerplate/package.json index c60e488d..26a4e869 100644 --- a/packages/joyent-boilerplate/package.json +++ b/packages/joyent-boilerplate/package.json @@ -20,7 +20,7 @@ "graphql-tag": "^2.4.2", "jest-cli": "^21.0.1", "joyent-ui-toolkit": "^2.0.0", - "normalized-styled-components": "^1.0.9", + "normalized-styled-components": "^1.0.14", "prop-types": "^15.5.10", "react": "^15.6.1", "react-apollo": "^1.4.15", @@ -33,12 +33,12 @@ "redux-form": "^7.0.3", "remcalc": "^1.0.8", "styled-components": "^2.1.2", - "styled-is": "^1.0.11" + "styled-is": "^1.0.14" }, "devDependencies": { "babel-plugin-inline-react-svg": "^0.4.0", "babel-plugin-styled-components": "^1.2.0", - "babel-preset-joyent-portal": "^2.0.0", + "babel-preset-joyent-portal": "^3.0.1", "eslint": "^4.5.0", "eslint-config-joyent-portal": "3.0.0", "jest": "^21.0.1", @@ -50,13 +50,10 @@ "jest-snapshot": "^21.0.0", "jest-styled-components": "^4.4.1", "jest-transform-graphql": "^2.1.0", - "joyent-react-scripts": "^1.0.2", + "joyent-react-scripts": "^2.0.2", "react-test-renderer": "^15.6.1", "redrun": "^5.9.17", "stylelint": "^8.1.1", - "stylelint-config-primer": "^2.0.1", - "stylelint-config-standard": "^17.0.0", - "stylelint-config-styled-components": "^0.1.1", - "stylelint-processor-styled-components": "^0.4.0" + "stylelint-config-joyent-portal": "^2.0.0" } } diff --git a/packages/joyent-boilerplate/src/app.js b/packages/joyent-boilerplate/src/app.js index d309ccdf..a3eb18b6 100644 --- a/packages/joyent-boilerplate/src/app.js +++ b/packages/joyent-boilerplate/src/app.js @@ -1,26 +1,15 @@ -import React, { Component } from 'react'; -import { ThemeProvider, injectGlobal } from 'styled-components'; -import { theme, global } from 'joyent-ui-toolkit'; +import React from 'react'; +import { ThemeProvider } from 'styled-components'; +import { theme, RootContainer } from 'joyent-ui-toolkit'; import { ApolloProvider } from 'react-apollo'; import { client, store } from '@state/store'; import Router from '@root/router'; -class App extends Component { - componentWillMount() { - // eslint-disable-next-line no-unused-expressions - injectGlobal` - ${global} - `; - } - - render() { - return ( - - {Router} - - ); - } -} - -export default App; +export default () => ( + + + {Router} + + +); diff --git a/packages/joyent-boilerplate/src/components/layout/__tests__/__snapshots__/container.spec.js.snap b/packages/joyent-boilerplate/src/components/layout/__tests__/__snapshots__/container.spec.js.snap deleted file mode 100644 index 8324951c..00000000 --- a/packages/joyent-boilerplate/src/components/layout/__tests__/__snapshots__/container.spec.js.snap +++ /dev/null @@ -1,38 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders without throwing 1`] = ` -.c0 { - margin-right: auto; - margin-left: auto; - padding-top: 1.1875rem; - -webkit-flex: 1 1 auto; - -ms-flex: 1 1 auto; - flex: 1 1 auto; - display: block; - -webkit-flex-flow: column; - -ms-flex-flow: column; - flex-flow: column; -} - -@media only screen and (min-width:48em) { - .c0 { - width: 46rem; - } -} - -@media only screen and (min-width:64em) { - .c0 { - width: 61rem; - } -} - -@media only screen and (min-width:75em) { - .c0 { - width: 76rem; - } -} - -
-`; diff --git a/packages/joyent-boilerplate/src/components/layout/__tests__/container.spec.js b/packages/joyent-boilerplate/src/components/layout/__tests__/container.spec.js deleted file mode 100644 index 406eb413..00000000 --- a/packages/joyent-boilerplate/src/components/layout/__tests__/container.spec.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * @jest-environment jsdom - */ - -import React from 'react'; -import renderer from 'react-test-renderer'; -import 'jest-styled-components'; - -import Container from '../container'; - -it('renders without throwing', () => { - const tree = renderer.create().toJSON(); - expect(tree).toMatchSnapshot(); -}); diff --git a/packages/joyent-boilerplate/src/components/layout/index.js b/packages/joyent-boilerplate/src/components/layout/index.js deleted file mode 100644 index d0d40032..00000000 --- a/packages/joyent-boilerplate/src/components/layout/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default as LayoutContainer } from './container'; diff --git a/packages/joyent-boilerplate/src/components/navigation/not-found.js b/packages/joyent-boilerplate/src/components/navigation/not-found.js index edf96bcc..3caf2142 100644 --- a/packages/joyent-boilerplate/src/components/navigation/not-found.js +++ b/packages/joyent-boilerplate/src/components/navigation/not-found.js @@ -2,8 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import styled from 'styled-components'; import remcalc from 'remcalc'; -import { H1, P, Button } from 'joyent-ui-toolkit'; -import { LayoutContainer } from '@components/layout'; +import { H1, P, Button, ViewContainer } from 'joyent-ui-toolkit'; const StyledContainer = styled.div` margin-top: ${remcalc(60)}; @@ -25,13 +24,13 @@ const NotFound = ({ link = 'Back home', to = '/' }) => ( - + {title} {message} - + ); NotFound.propTypes = { diff --git a/packages/joyent-boilerplate/src/containers/home/index.js b/packages/joyent-boilerplate/src/containers/home/index.js index 93664dc2..5f6adc97 100644 --- a/packages/joyent-boilerplate/src/containers/home/index.js +++ b/packages/joyent-boilerplate/src/containers/home/index.js @@ -1,6 +1,6 @@ import React from 'react'; -import { LayoutContainer } from '@components/layout'; +import { ViewContainer } from 'joyent-ui-toolkit'; -const Home = () => Welcome; +const Home = () => Welcome; export default Home; diff --git a/packages/joyent-boilerplate/src/router.js b/packages/joyent-boilerplate/src/router.js index 856ce803..2d246151 100644 --- a/packages/joyent-boilerplate/src/router.js +++ b/packages/joyent-boilerplate/src/router.js @@ -3,9 +3,7 @@ import { BrowserRouter, Route, Switch } from 'react-router-dom'; import styled from 'styled-components'; import { Header } from '@containers/navigation'; - import Home from '@containers/home'; - import { NotFound } from '@components/navigation'; const Container = styled.div` diff --git a/packages/my-joy-beta/.babelrc b/packages/my-joy-beta/.babelrc new file mode 100644 index 00000000..d57f858e --- /dev/null +++ b/packages/my-joy-beta/.babelrc @@ -0,0 +1,9 @@ +{ + "presets": "joyent-portal", + "plugins": [ + "styled-components", + ["inline-react-svg", { + "ignorePattern": "libre-franklin" + }] + ] +} diff --git a/packages/my-joy-beta/.dockerignore b/packages/my-joy-beta/.dockerignore new file mode 100644 index 00000000..06ad4916 --- /dev/null +++ b/packages/my-joy-beta/.dockerignore @@ -0,0 +1,9 @@ +src/components/base/*.css +node_modules +coverage +.nyc_output +docs/static +!docs/static/index.html +docs/node_modules +dist +package-lock.json diff --git a/packages/my-joy-beta/.eslintignore b/packages/my-joy-beta/.eslintignore new file mode 100644 index 00000000..0321eefc --- /dev/null +++ b/packages/my-joy-beta/.eslintignore @@ -0,0 +1,4 @@ +.nyc_output +coverage +dist +build \ No newline at end of file diff --git a/packages/my-joy-beta/.eslintrc b/packages/my-joy-beta/.eslintrc new file mode 100644 index 00000000..a847796a --- /dev/null +++ b/packages/my-joy-beta/.eslintrc @@ -0,0 +1,11 @@ +{ + "extends": "joyent-portal", + "rules": { + "no-console": 0, + "new-cap": 0, + // temp + "no-undef": 1, + "no-debugger": 1, + "no-negated-condition": 0 + } +} diff --git a/packages/my-joy-beta/.gitignore b/packages/my-joy-beta/.gitignore new file mode 100644 index 00000000..927d17bb --- /dev/null +++ b/packages/my-joy-beta/.gitignore @@ -0,0 +1,18 @@ +# 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* + diff --git a/packages/my-joy-beta/.lighthouserc b/packages/my-joy-beta/.lighthouserc new file mode 100644 index 00000000..f6c943d4 --- /dev/null +++ b/packages/my-joy-beta/.lighthouserc @@ -0,0 +1,8 @@ +{ + "setup": { + "compile": "npm run build", + "start": "serve -s build --port 3069 --single", + "href": "http://0.0.0.0:3069" + }, + "extends": "lighthouse:default" +} \ No newline at end of file diff --git a/packages/my-joy-beta/.stylelintrc b/packages/my-joy-beta/.stylelintrc new file mode 100644 index 00000000..3a875f40 --- /dev/null +++ b/packages/my-joy-beta/.stylelintrc @@ -0,0 +1,4 @@ +{ + "test": ["./src/**/*.js"], + "extends": ["stylelint-config-joyent-portal"] +} diff --git a/packages/my-joy-beta/.tern-project b/packages/my-joy-beta/.tern-project new file mode 100644 index 00000000..8c5745d4 --- /dev/null +++ b/packages/my-joy-beta/.tern-project @@ -0,0 +1,15 @@ +{ + "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" + } + } +} diff --git a/packages/my-joy-beta/README.md b/packages/my-joy-beta/README.md new file mode 100644 index 00000000..114bf321 --- /dev/null +++ b/packages/my-joy-beta/README.md @@ -0,0 +1,20 @@ +# my-joy-beta + +[![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 + +- [Usage](#usage) +- [License](#license) + +## Usage + +``` +npm run start +open http://0.0.0.0:3069 +``` + +## License + +MPL-2.0 diff --git a/packages/my-joy-beta/package.json b/packages/my-joy-beta/package.json new file mode 100644 index 00000000..717376d2 --- /dev/null +++ b/packages/my-joy-beta/package.json @@ -0,0 +1,64 @@ +{ + "name": "my-joy-beta", + "version": "1.0.0", + "license": "MPL-2.0", + "repository": "github:yldio/joyent-portal", + "main": "build/", + "scripts": { + "dev": "REACT_APP_GQL_PORT=4000 PORT=3069 REACT_APP_GQL_PROTOCOL=http joyent-react-scripts start", + "start": "PORT=3069 joyent-react-scripts start", + "build": "NODE_ENV=production joyent-react-scripts build", + "lint:css": "stylelint './src/**/*.js'", + "lint:js": "eslint . --fix", + "lint": "redrun -s lint:*", + "test": "NODE_ENV=test ./test/run --env=jsdom", + "test-ci": "echo 0 `# NODE_ENV=test ./test/run --env=jsdom --coverage`", + "prepublish": "echo 0" + }, + "dependencies": { + "apollo": "^0.2.2", + "joyent-ui-toolkit": "^2.0.0", + "lodash.find": "^4.6.0", + "lodash.get": "^4.4.2", + "lodash.isstring": "^4.0.1", + "lunr": "^2.1.3", + "normalized-styled-components": "^1.0.14", + "param-case": "^2.1.1", + "prop-types": "^15.5.10", + "react": "^15.6.1", + "react-apollo": "^1.4.15", + "react-dom": "^15.6.1", + "react-json-view": "^1.12.4", + "react-redux": "^5.0.6", + "react-router": "^4.1.1", + "react-router-dom": "^4.1.2", + "redux": "^3.7.2", + "redux-actions": "^2.2.1", + "redux-form": "^7.0.4", + "remcalc": "^1.0.8", + "styled-components": "^2.1.2", + "title-case": "^2.1.1" + }, + "devDependencies": { + "babel-plugin-inline-react-svg": "^0.4.0", + "babel-plugin-styled-components": "^1.2.0", + "babel-preset-joyent-portal": "^3.0.1", + "eslint": "^4.7.1", + "eslint-config-joyent-portal": "3.0.0", + "jest": "^21.1.0", + "jest-alias-preprocessor": "^1.1.1", + "jest-cli": "^21.1.0", + "jest-diff": "^21.1.0", + "jest-junit": "^3.0.0", + "jest-matcher-utils": "^21.1.0", + "jest-snapshot": "^21.1.0", + "jest-styled-components": "^4.6.0", + "jest-transform-graphql": "^2.1.0", + "joyent-react-scripts": "^2.0.2", + "react-test-renderer": "^15.6.1", + "redrun": "^5.9.17", + "serve": "^6.1.0", + "stylelint": "^8.1.1", + "stylelint-config-joyent-portal": "^2.0.0" + } +} diff --git a/packages/my-joy-beta/public/index.html b/packages/my-joy-beta/public/index.html new file mode 100644 index 00000000..3a414b9d --- /dev/null +++ b/packages/my-joy-beta/public/index.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + My Joyent β + + + +
+ + diff --git a/packages/my-joy-beta/public/manifest.json b/packages/my-joy-beta/public/manifest.json new file mode 100644 index 00000000..4372986a --- /dev/null +++ b/packages/my-joy-beta/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "Joyent", + "name": "My Joyent β", + "icons": [ + { + "src": "favicon.ico", + "sizes": "192x192", + "type": "image/png" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#1E313B", + "background_color": "#FAFAFA" +} diff --git a/packages/my-joy-beta/src/app.js b/packages/my-joy-beta/src/app.js new file mode 100644 index 00000000..073b14f6 --- /dev/null +++ b/packages/my-joy-beta/src/app.js @@ -0,0 +1,18 @@ +import React from 'react'; +import { ThemeProvider } from 'styled-components'; +import { ApolloProvider } from 'react-apollo'; + +import { theme, RootContainer } from 'joyent-ui-toolkit'; + +import { client, store } from '@state/store'; +import Router from '@root/router'; + +export default () => ( + + + + + + + +); diff --git a/packages/my-joy-beta/src/assets/triton_logo.png b/packages/my-joy-beta/src/assets/triton_logo.png new file mode 100644 index 00000000..a4ec2e75 Binary files /dev/null and b/packages/my-joy-beta/src/assets/triton_logo.png differ diff --git a/packages/my-joy-beta/src/assets/triton_logo_dark.png b/packages/my-joy-beta/src/assets/triton_logo_dark.png new file mode 100644 index 00000000..ccf801cc Binary files /dev/null and b/packages/my-joy-beta/src/assets/triton_logo_dark.png differ diff --git a/packages/my-joy-beta/src/components/instances/index.js b/packages/my-joy-beta/src/components/instances/index.js new file mode 100644 index 00000000..4bd5c1f4 --- /dev/null +++ b/packages/my-joy-beta/src/components/instances/index.js @@ -0,0 +1,3 @@ +export { default as Item } from './item'; +export { default as List } from './list'; +export { default as KeyValue } from './key-value'; diff --git a/packages/my-joy-beta/src/components/instances/item.js b/packages/my-joy-beta/src/components/instances/item.js new file mode 100644 index 00000000..36b8b694 --- /dev/null +++ b/packages/my-joy-beta/src/components/instances/item.js @@ -0,0 +1,54 @@ +import React from 'react'; +import titleCase from 'title-case'; + +import { + Card, + CardMeta, + CardAction, + CardTitle, + CardLabel, + CardView, + Checkbox, + FormGroup, + QueryBreakpoints +} from 'joyent-ui-toolkit'; + +const { SmallOnly, Small } = QueryBreakpoints; + +const stateColor = { + provisioning: 'blue', + ready: 'blue', + active: 'green', + running: 'green', + stopping: 'grey', + stopped: 'grey', + offline: 'red', + destroyed: 'red', + failed: 'red', + deleted: 'secondaryActive', + incomplete: 'secondaryActive', + unknown: 'secondaryActive' +}; + +export default ({ name, state, last, first }) => ( + + + + + + + + + {name} + + + {titleCase(state)} + + + + + + + + +); diff --git a/packages/my-joy-beta/src/components/instances/key-value.js b/packages/my-joy-beta/src/components/instances/key-value.js new file mode 100644 index 00000000..96a44daa --- /dev/null +++ b/packages/my-joy-beta/src/components/instances/key-value.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { Row, Col } from 'react-styled-flexboxgrid'; + +import { + FormGroup, + Input, + Button, + BinIcon, + QueryBreakpoints, + Divider +} from 'joyent-ui-toolkit'; + +const { SmallOnly, Small } = QueryBreakpoints; + +export default ({ name, formName, formValue, handleSubmit, onRemove, textarea }) => ( +
+ + + + + + + + + + + + + + + + + + + + + +
+); diff --git a/packages/my-joy-beta/src/components/instances/list.js b/packages/my-joy-beta/src/components/instances/list.js new file mode 100644 index 00000000..336aa4dc --- /dev/null +++ b/packages/my-joy-beta/src/components/instances/list.js @@ -0,0 +1,28 @@ +import React from 'react'; +import forceArray from 'force-array'; + +import { FormGroup, Input, FormLabel } from 'joyent-ui-toolkit'; +import Item from './item'; + +export default ({ instances, handleChange = () => null, handleSubmit }) => { + const _instances = forceArray(instances); + + const items = _instances.map((instance, i, all) => ( + + )); + + return ( +
handleSubmit(ctx => handleChange(ctx))}> + + Filter instances + + + {items} +
+ ); +}; diff --git a/packages/my-joy-beta/src/components/navigation/header.js b/packages/my-joy-beta/src/components/navigation/header.js new file mode 100644 index 00000000..a7d23053 --- /dev/null +++ b/packages/my-joy-beta/src/components/navigation/header.js @@ -0,0 +1,24 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { Img } from 'normalized-styled-components'; +import remcalc from 'remcalc'; + +import Logo from '@assets/triton_logo.png'; +import { Header, HeaderBrand, ViewContainer } from 'joyent-ui-toolkit'; + +const StyledLogo = Img.extend` + width: ${remcalc(87)}; + height: ${remcalc(25)}; +`; + +export default () => ( +
+ + + + + + + +
+); diff --git a/packages/my-joy-beta/src/components/navigation/index.js b/packages/my-joy-beta/src/components/navigation/index.js new file mode 100644 index 00000000..b6d90c0d --- /dev/null +++ b/packages/my-joy-beta/src/components/navigation/index.js @@ -0,0 +1,2 @@ +export { default as Header } from './header'; +export { default as Menu } from './menu'; diff --git a/packages/my-joy-beta/src/components/navigation/menu.js b/packages/my-joy-beta/src/components/navigation/menu.js new file mode 100644 index 00000000..204f891f --- /dev/null +++ b/packages/my-joy-beta/src/components/navigation/menu.js @@ -0,0 +1,44 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import forceArray from 'force-array'; + +import { + SectionList, + SectionListItem, + SectionListNavLink, + ViewContainer +} from 'joyent-ui-toolkit'; + +const getMenuItems = (links = []) => + links.map(({ pathname, name }) => ( + + + {name} + + + )); + +const Menu = ({ links = [] }) => { + const _links = forceArray(links); + + if (!_links.length) { + return null; + } + + return ( + + {getMenuItems(_links)} + + ); +}; + +Menu.propTypes = { + links: PropTypes.arrayOf( + PropTypes.shape({ + name: PropTypes.string, + pathname: PropTypes.string + }) + ) +}; + +export default Menu; diff --git a/packages/my-joy-beta/src/containers/instances/firewall.js b/packages/my-joy-beta/src/containers/instances/firewall.js new file mode 100644 index 00000000..62f21ce8 --- /dev/null +++ b/packages/my-joy-beta/src/containers/instances/firewall.js @@ -0,0 +1,66 @@ +import React from 'react'; +import ReactJson from 'react-json-view'; +import PropTypes from 'prop-types'; +import forceArray from 'force-array'; +import { compose, graphql } from 'react-apollo'; +import find from 'lodash.find'; +import get from 'lodash.get'; + +import { ViewContainer, Title, StatusLoader, Message } from 'joyent-ui-toolkit'; + +import GetFirewallRules from '@graphql/list-firewall-rules.gql'; + +const Firewall = ({ + firewallEnabled = false, + firewallRules = [], + loading, + error +}) => { + const _title = Firewall; + const _loading = !(loading && !forceArray(firewallRules).length) ? null : ( + + ); + + const _rules = !_loading && ; + const _enabled = !_loading && ; + + const _error = !(error && !_loading) ? null : ( + + ); + + return ( + + {_title} + {_loading} + {_error} + {_enabled} + {_rules} + + ); +}; + +Firewall.propTypes = { + loading: PropTypes.bool +}; + +export default compose( + graphql(GetFirewallRules, { + options: ({ match }) => ({ + pollInterval: 1000, + variables: { + name: get(match, 'params.instance') + } + }), + props: ({ data: { loading, error, variables, ...rest } }) => { + const machine = find(get(rest, 'machines', []), ['name', variables.name]); + const firewallEnabled = get(machine, 'firewallEnabled', false); + const firewallRules = get(machine, 'firewallRules', []); + + return { firewallEnabled, firewallRules, loading, error }; + } + }) +)(Firewall); diff --git a/packages/my-joy-beta/src/containers/instances/index.js b/packages/my-joy-beta/src/containers/instances/index.js new file mode 100644 index 00000000..0f040718 --- /dev/null +++ b/packages/my-joy-beta/src/containers/instances/index.js @@ -0,0 +1,7 @@ +export { default as List } from './list'; +export { default as Summary } from './summary'; +export { default as Tags } from './tags'; +export { default as Metadata } from './metadata'; +export { default as Networks } from './networks'; +export { default as Firewall } from './firewall'; +export { default as Snapshots } from './snapshots'; diff --git a/packages/my-joy-beta/src/containers/instances/list.js b/packages/my-joy-beta/src/containers/instances/list.js new file mode 100644 index 00000000..a6f3158a --- /dev/null +++ b/packages/my-joy-beta/src/containers/instances/list.js @@ -0,0 +1,81 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { compose, graphql } from 'react-apollo'; +import { connect } from 'react-redux'; +import { reduxForm } from 'redux-form'; +import forceArray from 'force-array'; +import get from 'lodash.get'; +import find from 'lodash.find'; + +import { ViewContainer, Title, StatusLoader, Message } from 'joyent-ui-toolkit'; + +import GetInstances from '@graphql/list-instances.gql'; +import { List as InstanceList } from '@components/instances'; +import GenIndex from '@state/gen-index'; + +const InstanceListForm = reduxForm({ + form: `instance-list` +})(InstanceList); + +const List = ({ instances, loading, error }) => { + const _title = Instances; + const _instances = forceArray(instances); + const _loading = !(loading && !_instances.length) ? null : ; + const _list = _loading ? null : ; + + const _error = !error ? null : ( + + ); + + return ( + + {_title} + {!_loading && _error} + {_loading} + {_list} + + ); +}; + +List.propTypes = { + loading: PropTypes.bool, + instances: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string, + name: PropTypes.string + }) + ) +}; + +export default compose( + graphql(GetInstances, { + options: () => ({ + pollInterval: 1000 + }), + props: ({ ownProps, data: { machines, loading, error } }) => { + const _instances = forceArray(machines); + + return { + instances: _instances, + loading, + error, + index: GenIndex(_instances) + }; + } + }), + connect((state, ownProps) => { + const filter = get(state, 'form.instance-list.values.filter'); + const { index, instances = [], ...rest } = ownProps; + + return { + ...rest, + instances: !filter + ? instances + : index.search(filter).map(({ ref }) => find(instances, ['id', ref])) + }; + }) +)(List); diff --git a/packages/my-joy-beta/src/containers/instances/metadata.js b/packages/my-joy-beta/src/containers/instances/metadata.js new file mode 100644 index 00000000..6a80391e --- /dev/null +++ b/packages/my-joy-beta/src/containers/instances/metadata.js @@ -0,0 +1,82 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import paramCase from 'param-case'; +import forceArray from 'force-array'; +import { compose, graphql } from 'react-apollo'; +import { reduxForm } from 'redux-form'; +import find from 'lodash.find'; +import get from 'lodash.get'; + +import { ViewContainer, Title, StatusLoader, Message } from 'joyent-ui-toolkit'; + +import GetMetadata from '@graphql/list-metadata.gql'; +import { KeyValue } from '@components/instances'; + +const Metadata = ({ metadata = [], loading, error }) => { + const _title = Metadata; + const _loading = !(loading && !forceArray(metadata).length) ? null : ( + + ); + + const metadataNames = Object.keys(metadata).map(name => ({ + key: paramCase(name), + name + })); + + const InstanceMetadataForm = reduxForm({ + form: `instance-tags`, + initialValues: metadataNames.reduce( + (all, { key, name }) => ({ + ...all, + [`${key}-name`]: name, + [`${key}-value`]: metadata[name] + }), + {} + ) + })(KeyValue); + + const _tags = !_loading && ( + key)} /> + ); + + const _error = !(error && !_loading) ? null : ( + + ); + + return ( + + {_title} + {_loading} + {_error} + {_tags} + + ); +}; + +Metadata.propTypes = { + loading: PropTypes.bool +}; + +export default compose( + graphql(GetMetadata, { + options: ({ match }) => ({ + pollInterval: 1000, + variables: { + name: get(match, 'params.instance') + } + }), + props: ({ data: { loading, error, variables, ...rest } }) => ({ + metadata: get( + find(get(rest, 'machines', []), ['name', variables.name]), + 'metadata', + [] + ), + loading, + error + }) + }) +)(Metadata); diff --git a/packages/my-joy-beta/src/containers/instances/networks.js b/packages/my-joy-beta/src/containers/instances/networks.js new file mode 100644 index 00000000..21b7b281 --- /dev/null +++ b/packages/my-joy-beta/src/containers/instances/networks.js @@ -0,0 +1,61 @@ +import React from 'react'; +import ReactJson from 'react-json-view'; +import PropTypes from 'prop-types'; +import forceArray from 'force-array'; +import { compose, graphql } from 'react-apollo'; +import find from 'lodash.find'; +import get from 'lodash.get'; + +import { ViewContainer, Title, StatusLoader, Message } from 'joyent-ui-toolkit'; + +import GetNetworks from '@graphql/list-networks.gql'; + +const Networks = ({ networks = [], loading, error }) => { + const _title = Networks; + const _loading = !(loading && !forceArray(networks).length) ? null : ( + + ); + + const _summary = !_loading && ; + + const _error = !(error && !_loading) ? null : ( + + ); + + return ( + + {_title} + {_loading} + {_error} + {_summary} + + ); +}; + +Networks.propTypes = { + loading: PropTypes.bool +}; + +export default compose( + graphql(GetNetworks, { + options: ({ match }) => ({ + pollInterval: 1000, + variables: { + name: get(match, 'params.instance') + } + }), + props: ({ data: { loading, error, variables, ...rest } }) => ({ + networks: get( + find(get(rest, 'machines', []), ['name', variables.name]), + 'networks', + [] + ), + loading, + error + }) + }) +)(Networks); diff --git a/packages/my-joy-beta/src/containers/instances/snapshots.js b/packages/my-joy-beta/src/containers/instances/snapshots.js new file mode 100644 index 00000000..84438457 --- /dev/null +++ b/packages/my-joy-beta/src/containers/instances/snapshots.js @@ -0,0 +1,61 @@ +import React from 'react'; +import ReactJson from 'react-json-view'; +import PropTypes from 'prop-types'; +import forceArray from 'force-array'; +import { compose, graphql } from 'react-apollo'; +import find from 'lodash.find'; +import get from 'lodash.get'; + +import { ViewContainer, Title, StatusLoader, Message } from 'joyent-ui-toolkit'; + +import GetSnapshots from '@graphql/list-snapshots.gql'; + +const Snapshots = ({ snapshots = [], loading, error }) => { + const _title = Snapshots; + const _loading = !(loading && !forceArray(snapshots).length) ? null : ( + + ); + + const _summary = !_loading && ; + + const _error = !(error && !_loading) ? null : ( + + ); + + return ( + + {_title} + {_loading} + {_error} + {_summary} + + ); +}; + +Snapshots.propTypes = { + loading: PropTypes.bool +}; + +export default compose( + graphql(GetSnapshots, { + options: ({ match }) => ({ + pollInterval: 1000, + variables: { + name: get(match, 'params.instance') + } + }), + props: ({ data: { loading, error, variables, ...rest } }) => ({ + snapshots: get( + find(get(rest, 'machines', []), ['name', variables.name]), + 'snapshots', + [] + ), + loading, + error + }) + }) +)(Snapshots); diff --git a/packages/my-joy-beta/src/containers/instances/summary.js b/packages/my-joy-beta/src/containers/instances/summary.js new file mode 100644 index 00000000..61d6ffea --- /dev/null +++ b/packages/my-joy-beta/src/containers/instances/summary.js @@ -0,0 +1,55 @@ +import React from 'react'; +import ReactJson from 'react-json-view'; +import PropTypes from 'prop-types'; +import { compose, graphql } from 'react-apollo'; +import find from 'lodash.find'; +import get from 'lodash.get'; + +import { ViewContainer, Title, StatusLoader, Message } from 'joyent-ui-toolkit'; + +import GetInstance from '@graphql/get-instance.gql'; + +const Summary = ({ instance = {}, loading, error }) => { + const { name } = instance; + + const _title = Summary; + const _loading = !(loading && !name) ? null : ; + const _summary = !_loading && ; + + const _error = !(error && !_loading) ? null : ( + + ); + + return ( + + {_title} + {_loading} + {_error} + {_summary} + + ); +}; + +Summary.propTypes = { + loading: PropTypes.bool +}; + +export default compose( + graphql(GetInstance, { + options: ({ match }) => ({ + pollInterval: 1000, + variables: { + name: get(match, 'params.instance') + } + }), + props: ({ data: { loading, error, variables, ...rest } }) => ({ + instance: find(get(rest, 'machines', []), ['name', variables.name]), + loading, + error + }) + }) +)(Summary); diff --git a/packages/my-joy-beta/src/containers/instances/tags.js b/packages/my-joy-beta/src/containers/instances/tags.js new file mode 100644 index 00000000..c82e0e96 --- /dev/null +++ b/packages/my-joy-beta/src/containers/instances/tags.js @@ -0,0 +1,109 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import paramCase from 'param-case'; +import forceArray from 'force-array'; +import { compose, graphql } from 'react-apollo'; +import { reduxForm } from 'redux-form'; +import find from 'lodash.find'; +import get from 'lodash.get'; + +import { ViewContainer, Title, StatusLoader, Message } from 'joyent-ui-toolkit'; + +import { KeyValue } from '@components/instances'; +import GetTags from '@graphql/list-tags.gql'; +import PutTags from '@graphql/add-tags.gql'; + +const TagForms = (tags = []) => tags.map(({ key, formName, formValue, value, name }) => { + const TagForm = reduxForm({ + form: `instance-tags-${key}`, + initialValues: { + [formName]: name, + [formValue]: value + } + })(KeyValue); + + return ( + console.log(key, val)} + onRemove={key => console.log('remove', key)} + /> + ); +}); + +const Tags = ({ tags = [], loading, error }) => { + const _title = Tags; + const _loading = !(loading && !forceArray(tags).length) ? null : ( + + ); + + const _tags = !_loading && TagForms(Object.values(tags)); + + const _error = !(error && !_loading) ? null : ( + + ); + + return ( + + {_title} + {_loading} + {_error} + {_tags} + + ); +}; + +Tags.propTypes = { + loading: PropTypes.bool +}; + +export default compose( + graphql(GetTags, { + options: ({ match }) => ({ + pollInterval: 1000, + variables: { + name: get(match, 'params.instance') + } + }), + props: ({ data: { loading, error, variables, ...rest } }) => { + const values = get( + find(get(rest, 'machines', []), ['name', variables.name]), + 'tags', + [] + ); + + + const tags = Object.keys(values).reduce((all, name) => { + const key = paramCase(name); + + return { + ...all, + [key]: { + key, + formName: `${key}-name`, + formValue: `${key}-value`, + value: values[name], + name + } + }; + }, {}); + + return { tags, loading, error }; + } + }), + graphql(PutTags, { + props: ({ mutate, ownProps }) => ({ + updateTag: (name = '', value = '') => + mutate({ + variables: { name, value } + }) + }) + }) +)(Tags); diff --git a/packages/my-joy-beta/src/containers/navigation/breadcrumb.js b/packages/my-joy-beta/src/containers/navigation/breadcrumb.js new file mode 100644 index 00000000..eaaaf478 --- /dev/null +++ b/packages/my-joy-beta/src/containers/navigation/breadcrumb.js @@ -0,0 +1,25 @@ +import React from 'react'; +import paramCase from 'param-case'; +import get from 'lodash.get'; + +import { Breadcrumb } from 'joyent-ui-toolkit'; + +export default ({ match }) => { + const instance = get(match, 'params.instance'); + + const links = [ + { + name: '/', + pathname: '/instances' + } + ].concat( + instance && [ + { + name: paramCase(instance), + pathname: `/instances/${instance}` + } + ] + ); + + return ; +}; diff --git a/packages/my-joy-beta/src/containers/navigation/index.js b/packages/my-joy-beta/src/containers/navigation/index.js new file mode 100644 index 00000000..4e5c76e1 --- /dev/null +++ b/packages/my-joy-beta/src/containers/navigation/index.js @@ -0,0 +1,2 @@ +export { default as Breadcrumb } from './breadcrumb'; +export { default as Menu } from './menu'; diff --git a/packages/my-joy-beta/src/containers/navigation/menu.js b/packages/my-joy-beta/src/containers/navigation/menu.js new file mode 100644 index 00000000..c57bb466 --- /dev/null +++ b/packages/my-joy-beta/src/containers/navigation/menu.js @@ -0,0 +1,32 @@ +import { connect } from 'react-redux'; +import paramCase from 'param-case'; +import titleCase from 'title-case'; +import isString from 'lodash.isstring'; +import get from 'lodash.get'; + +import { Menu } from '@components/navigation'; + +export default connect((state, { match }) => { + const instanceSlug = get(match, 'params.instance'); + const allSections = get(state, 'ui.sections'); + const sections = instanceSlug ? allSections.instances : []; + + const links = sections + .map( + section => + !isString(section) + ? section + : { + pathname: paramCase(section), + name: titleCase(section) + } + ) + .map(({ name, pathname }) => ({ + name, + pathname: `/instances/${instanceSlug}/${pathname}` + })); + + return { + links + }; +})(Menu); diff --git a/packages/my-joy-beta/src/graphql/add-tags.gql b/packages/my-joy-beta/src/graphql/add-tags.gql new file mode 100644 index 00000000..cd886a23 --- /dev/null +++ b/packages/my-joy-beta/src/graphql/add-tags.gql @@ -0,0 +1,3 @@ +mutation addMachineTags($id: ID!, $tags: DynamicObjectType!) { + addMachineTags(id: $id, tags: $tags) +} diff --git a/packages/my-joy-beta/src/graphql/get-instance.gql b/packages/my-joy-beta/src/graphql/get-instance.gql new file mode 100644 index 00000000..c4d737ad --- /dev/null +++ b/packages/my-joy-beta/src/graphql/get-instance.gql @@ -0,0 +1,15 @@ +query Instance($name: String!) { + machines(name: $name) { + id + name + brand + state + image + memory + disk + created + updated + firewallEnabled + package + } +} diff --git a/packages/my-joy-beta/src/graphql/list-firewall-rules.gql b/packages/my-joy-beta/src/graphql/list-firewall-rules.gql new file mode 100644 index 00000000..67d3021b --- /dev/null +++ b/packages/my-joy-beta/src/graphql/list-firewall-rules.gql @@ -0,0 +1,14 @@ +query Instance($name: String!) { + machines(name: $name) { + id + name + firewallEnabled + firewallRules { + id + enabled + global + description + rule + } + } +} diff --git a/packages/my-joy-beta/src/graphql/list-instances.gql b/packages/my-joy-beta/src/graphql/list-instances.gql new file mode 100644 index 00000000..2be72335 --- /dev/null +++ b/packages/my-joy-beta/src/graphql/list-instances.gql @@ -0,0 +1,13 @@ +query Instances { + machines { + id + name + brand + state + image + metadata + tags + firewallEnabled + docker + } +} diff --git a/packages/my-joy-beta/src/graphql/list-metadata.gql b/packages/my-joy-beta/src/graphql/list-metadata.gql new file mode 100644 index 00000000..58d1960b --- /dev/null +++ b/packages/my-joy-beta/src/graphql/list-metadata.gql @@ -0,0 +1,7 @@ +query Instance($name: String!) { + machines(name: $name) { + id + name + metadata + } +} diff --git a/packages/my-joy-beta/src/graphql/list-networks.gql b/packages/my-joy-beta/src/graphql/list-networks.gql new file mode 100644 index 00000000..502b6e98 --- /dev/null +++ b/packages/my-joy-beta/src/graphql/list-networks.gql @@ -0,0 +1,20 @@ +query Instance($name: String!) { + machines(name: $name) { + id + name + networks { + id + name + public + fabric + description + subnet + provisionStartIp + provisionEndIp + gateway + resolvers + routes + internetNat + } + } +} diff --git a/packages/my-joy-beta/src/graphql/list-snapshots.gql b/packages/my-joy-beta/src/graphql/list-snapshots.gql new file mode 100644 index 00000000..fd5e0551 --- /dev/null +++ b/packages/my-joy-beta/src/graphql/list-snapshots.gql @@ -0,0 +1,10 @@ +query Instance($name: String!) { + machines(name: $name) { + id + name + snapshots { + name + state + } + } +} diff --git a/packages/my-joy-beta/src/graphql/list-tags.gql b/packages/my-joy-beta/src/graphql/list-tags.gql new file mode 100644 index 00000000..09a638e6 --- /dev/null +++ b/packages/my-joy-beta/src/graphql/list-tags.gql @@ -0,0 +1,7 @@ +query Instance($name: String!) { + machines(name: $name) { + id + name + tags + } +} diff --git a/packages/my-joy-beta/src/index.js b/packages/my-joy-beta/src/index.js new file mode 100644 index 00000000..a1b69bbd --- /dev/null +++ b/packages/my-joy-beta/src/index.js @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import { register } from './sw'; +import App from './app'; + +ReactDOM.render(, document.getElementById('root')); + +register(); diff --git a/packages/my-joy-beta/src/router.js b/packages/my-joy-beta/src/router.js new file mode 100644 index 00000000..d322d3cb --- /dev/null +++ b/packages/my-joy-beta/src/router.js @@ -0,0 +1,76 @@ +import React from 'react'; +import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'; +import get from 'lodash.get'; + +import { PageContainer } from 'joyent-ui-toolkit'; + +import { Breadcrumb, Menu } from '@containers/navigation'; +import { Header } from '@components/navigation'; + +import { + List as Instances, + Summary as InstanceSummary, + Tags as InstanceTags, + Metadata as InstanceMetadata, + Networks as InstanceNetworks, + Firewall as InstanceFirewall, + Snapshots as InstanceSnapshots +} from '@containers/instances'; + +export default () => ( + + + + + + + + + + + + + + + + + + + + + + + ( + + )} + /> + + } /> + + +); diff --git a/packages/my-joy-beta/src/state/gen-index.js b/packages/my-joy-beta/src/state/gen-index.js new file mode 100644 index 00000000..327982f0 --- /dev/null +++ b/packages/my-joy-beta/src/state/gen-index.js @@ -0,0 +1,16 @@ +import Lunr from 'lunr'; + +Lunr.tokenizer.separator = /[\s\-|_]+/; + +export default items => + Lunr(function() { + const fields = items + .map(item => Object.keys(item)) + .reduce((all, keys) => all.concat(keys), []) + // eslint-disable-next-line no-implicit-coercion + .reduce((all, key) => (~all.indexOf(key) ? all : all.concat(key)), []) + .filter(key => !key.match(/^__/)); + + fields.forEach(field => this.field(field)); + items.forEach(item => this.add(item)); + }); diff --git a/packages/my-joy-beta/src/state/reducers/index.js b/packages/my-joy-beta/src/state/reducers/index.js new file mode 100644 index 00000000..570fa5d0 --- /dev/null +++ b/packages/my-joy-beta/src/state/reducers/index.js @@ -0,0 +1 @@ +export { default as ui } from './ui'; diff --git a/packages/my-joy-beta/src/state/reducers/ui.js b/packages/my-joy-beta/src/state/reducers/ui.js new file mode 100644 index 00000000..ac57b4f1 --- /dev/null +++ b/packages/my-joy-beta/src/state/reducers/ui.js @@ -0,0 +1,3 @@ +import { handleActions } from 'redux-actions'; + +export default handleActions({}, {}); diff --git a/packages/my-joy-beta/src/state/state.js b/packages/my-joy-beta/src/state/state.js new file mode 100644 index 00000000..3b662bde --- /dev/null +++ b/packages/my-joy-beta/src/state/state.js @@ -0,0 +1,15 @@ +export default { + ui: { + sections: { + instances: [ + 'summary', + 'tags', + 'metadata', + 'networks', + 'firewall', + 'snapshots', + 'affinity' + ] + } + } +}; diff --git a/packages/my-joy-beta/src/state/store.js b/packages/my-joy-beta/src/state/store.js new file mode 100644 index 00000000..add14fc7 --- /dev/null +++ b/packages/my-joy-beta/src/state/store.js @@ -0,0 +1,62 @@ +import { createStore, combineReducers, applyMiddleware, compose } from 'redux'; +import { reducer as formReducer } from 'redux-form'; +import { ApolloClient, createNetworkInterface } from 'react-apollo'; + +import { ui } from './reducers'; +import state from './state'; + +const GLOBAL = + typeof window === 'object' + ? window + : { + location: { + hostname: '0.0.0.0' + } + }; + +const GQL_PORT = process.env.REACT_APP_GQL_PORT || 443; +const GQL_PROTOCOL = process.env.REACT_APP_GQL_PROTOCOL || 'https'; +const GQL_HOSTNAME = + process.env.REACT_APP_GQL_HOSTNAME || GLOBAL.location.hostname; + +export const client = new ApolloClient({ + dataIdFromObject: o => { + const id = o.id + ? o.id + : o.slug + ? o.slug + : o.uuid + ? o.uuid + : o.timestamp + ? o.timestamp + : o.name && o.instance + ? `${o.name}-${o.instance}` + : o.name + ? o.name + : o.time && o.value + ? `${o.time}-${o.value}` + : 'apollo-cache-key-not-defined'; + + return `${o.__typename}:${id}`; + }, + networkInterface: createNetworkInterface({ + uri: `${GQL_PROTOCOL}://${GQL_HOSTNAME}:${GQL_PORT}/graphql` + }) +}); + +export const store = createStore( + combineReducers({ + apollo: client.reducer(), + form: formReducer, + ui + }), + state, // Initial state + compose( + applyMiddleware(client.middleware()), + // 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 + ) +); diff --git a/packages/my-joy-beta/src/sw.js b/packages/my-joy-beta/src/sw.js new file mode 100644 index 00000000..eb7a28cf --- /dev/null +++ b/packages/my-joy-beta/src/sw.js @@ -0,0 +1,108 @@ +// In production, we register a service worker to serve assets from local cache. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on the "N+1" visit to a page, since previously +// cached resources are updated in the background. + +// To learn more about the benefits of this model, read https://goo.gl/KwvDNy. +// This link also includes instructions on opting out of this behavior. + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export function register() { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (!isLocalhost) { + // Is not local host. Just register service worker + registerValidSW(swUrl); + } else { + // This is running on localhost. Lets check if a service worker still exists or not. + checkValidServiceWorker(swUrl); + } + }); + } +} + +function registerValidSW(swUrl) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the old content will have been purged and + // the fresh content will have been added to the cache. + // It's the perfect time to display a "New content is + // available; please refresh." message in your web app. + console.log('New content is available; please refresh.'); + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + if ( + response.status === 404 || + response.headers.get('content-type').indexOf('javascript') === -1 + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +} diff --git a/packages/my-joy-beta/test/file-mock.js b/packages/my-joy-beta/test/file-mock.js new file mode 100644 index 00000000..d906d5b4 --- /dev/null +++ b/packages/my-joy-beta/test/file-mock.js @@ -0,0 +1 @@ +module.exports = 'test-file-mock'; diff --git a/packages/my-joy-beta/test/mocks/index.js b/packages/my-joy-beta/test/mocks/index.js new file mode 100644 index 00000000..cf9bab85 --- /dev/null +++ b/packages/my-joy-beta/test/mocks/index.js @@ -0,0 +1,3 @@ +export { default as Router } from './router'; +export { default as Store } from './store'; +export { default as Theme } from './theme'; diff --git a/packages/my-joy-beta/test/mocks/router.js b/packages/my-joy-beta/test/mocks/router.js new file mode 100644 index 00000000..47dbba36 --- /dev/null +++ b/packages/my-joy-beta/test/mocks/router.js @@ -0,0 +1,4 @@ +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; + +export default ({ children }) => {children}; diff --git a/packages/my-joy-beta/test/mocks/store.js b/packages/my-joy-beta/test/mocks/store.js new file mode 100644 index 00000000..6026ecbc --- /dev/null +++ b/packages/my-joy-beta/test/mocks/store.js @@ -0,0 +1,9 @@ +import React from 'react'; +import { client, store } from '@state/store'; +import { ApolloProvider } from 'react-apollo'; + +export default ({ children }) => ( + + {children} + +); diff --git a/packages/my-joy-beta/test/mocks/theme.js b/packages/my-joy-beta/test/mocks/theme.js new file mode 100644 index 00000000..5b1a2a45 --- /dev/null +++ b/packages/my-joy-beta/test/mocks/theme.js @@ -0,0 +1,7 @@ +import React from 'react'; +import { ThemeProvider } from 'styled-components'; +import { theme } from 'joyent-ui-toolkit'; + +export default ({ children }) => ( + {children} +); diff --git a/packages/my-joy-beta/test/run b/packages/my-joy-beta/test/run new file mode 100755 index 00000000..4563667b --- /dev/null +++ b/packages/my-joy-beta/test/run @@ -0,0 +1,66 @@ +#!/usr/bin/env node + +// Do this as the first thing so that any code reading it knows the right env. +process.env.BABEL_ENV = 'test'; +process.env.NODE_ENV = 'test'; +process.env.PUBLIC_URL = ''; + +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + +// Ensure environment variables are read. +require('react-scripts/config/env'); + +const jest = require('jest'); +const argv = process.argv.slice(2); + +// This is not necessary after eject because we embed config into package.json. +const createJestConfig = require('react-scripts/scripts/utils/createJestConfig'); +const path = require('path'); +const paths = require('react-scripts/config/paths'); + +const config = createJestConfig( + relativePath => + path.resolve( + __dirname, + '../../../node_modules/react-scripts', + relativePath + ), + path.resolve(__dirname, '../../../'), + false +); + +// patch +config.testEnvironment = 'node'; +config.transform = Object.assign( + {}, + { + '\\.(gql|graphql)$': 'jest-transform-graphql' + }, + config.transform +); +config.testMatch = [ + '/packages/joyent-boilerplate/src/**/**/__tests__/**/*.js', + '/packages/joyent-boilerplate/src/**/**/**/?(*.)(spec|test).js' +]; +config.moduleNameMapper = Object.assign({}, config.moduleNameMapper, { + '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': + '/packages/joyent-boilerplate/test/file-mock.js', + '^@root/(.*)$': '/packages/joyent-boilerplate/src/$1', + '^@mocks/(.*)$': '/packages/joyent-boilerplate/test/mocks$1', + '^@components/(.*)$': + '/packages/joyent-boilerplate/src/components/$1', + '^@containers/(.*)$': + '/packages/joyent-boilerplate/src/containers/$1', + '^@graphql/(.*)$': '/packages/joyent-boilerplate/src/graphql/$1', + '^@assets/(.*)$': '/packages/joyent-boilerplate/src/assets/$1', + '^@state/(.*)$': '/packages/joyent-boilerplate/src/state/$1' +}); + +argv.push('--config', JSON.stringify(config)); + +jest.run(argv); diff --git a/packages/my-joyent/.stylelintrc b/packages/my-joyent/.stylelintrc index c97c6aac..f82431c9 100644 --- a/packages/my-joyent/.stylelintrc +++ b/packages/my-joyent/.stylelintrc @@ -1,8 +1,3 @@ { - "processors": ["stylelint-processor-styled-components"], - "extends": [ - "stylelint-config-standard", - "stylelint-config-styled-components" - ], - "syntax": "scss" + "extends": ["stylelint-config-joyent-portal"] } \ No newline at end of file diff --git a/packages/my-joyent/Dockerfile b/packages/my-joyent/Dockerfile deleted file mode 100644 index 2a8813c9..00000000 --- a/packages/my-joyent/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM quay.io/yldio/alpine-node-containerpilot:latest - -RUN apk add --update nginx - -ENV CONTAINERPILOT /etc/containerpilot.json5 - -RUN npm install -g npm@^4 -RUN npm config set loglevel info \ - && yarn add lerna@^2.0.0 - -RUN ./node_modules/.bin/lerna clean --yes --scope my-joyent --include-filtered-dependencies \ - && ./node_modules/.bin/lerna bootstrap --scope my-joyent --include-filtered-dependencies - -COPY packages/my-joyent/etc/containerpilot.json5 ${CONTAINERPILOT} -COPY packages/my-joyent/etc/nginx.conf.tmpl /etc/nginx/nginx.conf.tmpl - -WORKDIR /opt/app/packages/my-joyent - -CMD ["/bin/containerpilot"] diff --git a/packages/my-joyent/package.json b/packages/my-joyent/package.json index 32e0d16c..0cfbf9f8 100644 --- a/packages/my-joyent/package.json +++ b/packages/my-joyent/package.json @@ -21,7 +21,7 @@ "jest-cli": "^21.0.1", "joyent-ui-toolkit": "^2.0.0", "lodash.isempty": "^4.4.0", - "normalized-styled-components": "^1.0.9", + "normalized-styled-components": "^1.0.14", "prop-types": "^15.5.10", "react": "^15.6.1", "react-apollo": "^1.4.15", @@ -34,8 +34,8 @@ "redux-form": "^7.0.3", "remcalc": "^1.0.8", "styled-components": "^2.1.2", - "styled-is": "^1.0.11", - "unitcalc": "^1.0.8" + "styled-is": "^1.0.14", + "unitcalc": "^1.1.0" }, "devDependencies": { "apr-for-each": "^1.0.6", @@ -43,7 +43,7 @@ "babel-minify-webpack-plugin": "^0.2.0", "babel-plugin-inline-react-svg": "^0.4.0", "babel-plugin-styled-components": "^1.2.0", - "babel-preset-joyent-portal": "^2.0.0", + "babel-preset-joyent-portal": "^3.0.1", "commitizen": "^2.9.6", "cross-env": "^5.0.5", "eslint": "^4.5.0", @@ -63,9 +63,6 @@ "react-test-renderer": "^15.6.1", "redrun": "^5.9.17", "stylelint": "^8.1.1", - "stylelint-config-primer": "^2.0.1", - "stylelint-config-standard": "^17.0.0", - "stylelint-config-styled-components": "^0.1.1", - "stylelint-processor-styled-components": "^0.4.0" + "stylelint-config-joyent-portal": "^2.0.0" } } diff --git a/packages/my-joyent/src/app.js b/packages/my-joyent/src/app.js index 5afcb520..0ea12cf2 100644 --- a/packages/my-joyent/src/app.js +++ b/packages/my-joyent/src/app.js @@ -1,28 +1,20 @@ import React, { Component } from 'react'; import { ThemeProvider, injectGlobal } from 'styled-components'; -import { theme, global } from 'joyent-ui-toolkit'; +import { theme, RootContainer } from 'joyent-ui-toolkit'; import { ApolloProvider } from 'react-apollo'; import { client, store } from '@state/store'; import Router from '@root/router'; import { register } from './sw'; -class App extends Component { - componentWillMount() { - injectGlobal` - ${global} - `; - } - - render() { - return ( - - {Router} - - ); - } -} - -export default App; +export default () => ( + + + + + + + +); register(); diff --git a/packages/my-joyent/src/components/diskTypeForm/__tests__/__snapshots__/index.spec.js.snap b/packages/my-joyent/src/components/diskTypeForm/__tests__/__snapshots__/index.spec.js.snap index da210bf7..6fa13ba4 100644 --- a/packages/my-joyent/src/components/diskTypeForm/__tests__/__snapshots__/index.spec.js.snap +++ b/packages/my-joyent/src/components/diskTypeForm/__tests__/__snapshots__/index.spec.js.snap @@ -65,6 +65,7 @@ exports[`renders without throwing 1`] = ` height: 1.125rem; top: 0; box-sizing: border-box; + cursor: pointer; background-color: rgb(255,255,255); box-shadow: none; border: 1px solid; @@ -98,11 +99,11 @@ exports[`renders without throwing 1`] = ` width: 1.125rem; height: 1.125rem; position: relative; + cursor: pointer; } .c5 { list-style-type: none; - font-family: "Libre Franklin",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica,sans-serif; font-weight: 400; } @@ -135,7 +136,6 @@ exports[`renders without throwing 1`] = ` max-width: 100%; padding: 0; white-space: normal; - font-family: "Libre Franklin",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica,sans-serif; font-weight: 600; } @@ -197,7 +197,7 @@ exports[`renders without throwing 1`] = ` without throwing 1`] = ` />
Magnetic @@ -239,7 +239,7 @@ exports[`renders without throwing 1`] = ` without throwing 1`] = ` />