From dee7d7dfcbec7ef4475896935873f04d4df0d40f Mon Sep 17 00:00:00 2001 From: Emiliano Ciavatta Date: Thu, 7 May 2020 12:25:41 +0200 Subject: Add colors to services dialog --- frontend/.eslintrc.js | 216 ++++++++++++++++++++++++++++++++++ frontend/package.json | 1 + frontend/src/components/Connection.js | 24 ++-- frontend/src/index.scss | 6 +- frontend/src/utils.js | 6 +- frontend/src/views/Services.js | 142 ++++++++++++++++------ frontend/src/views/Services.scss | 20 +++- 7 files changed, 359 insertions(+), 56 deletions(-) create mode 100644 frontend/.eslintrc.js diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js new file mode 100644 index 0000000..97fb33f --- /dev/null +++ b/frontend/.eslintrc.js @@ -0,0 +1,216 @@ +var OFF = 0, WARN = 1, ERROR = 2; + +module.exports = exports = { + "env": { + "es6": true + }, + + "ecmaFeatures": { + // env=es6 doesn't include modules, which we are using + "modules": true + }, + + "extends": "eslint:recommended", + + "rules": { + // Possible Errors (overrides from recommended set) + "no-extra-parens": ERROR, + "no-unexpected-multiline": ERROR, + // All JSDoc comments must be valid + "valid-jsdoc": [ ERROR, { + "requireReturn": false, + "requireReturnDescription": false, + "requireParamDescription": true, + "prefer": { + "return": "returns" + } + }], + + // Best Practices + + // Allowed a getter without setter, but all setters require getters + "accessor-pairs": [ ERROR, { + "getWithoutSet": false, + "setWithoutGet": true + }], + "block-scoped-var": WARN, + "consistent-return": ERROR, + "curly": ERROR, + "default-case": WARN, + // the dot goes with the property when doing multiline + "dot-location": [ WARN, "property" ], + "dot-notation": WARN, + "eqeqeq": [ ERROR, "smart" ], + "guard-for-in": WARN, + "no-alert": ERROR, + "no-caller": ERROR, + "no-case-declarations": WARN, + "no-div-regex": WARN, + "no-else-return": WARN, + "no-empty-label": WARN, + "no-empty-pattern": WARN, + "no-eq-null": WARN, + "no-eval": ERROR, + "no-extend-native": ERROR, + "no-extra-bind": WARN, + "no-floating-decimal": WARN, + "no-implicit-coercion": [ WARN, { + "boolean": true, + "number": true, + "string": true + }], + "no-implied-eval": ERROR, + "no-invalid-this": ERROR, + "no-iterator": ERROR, + "no-labels": WARN, + "no-lone-blocks": WARN, + "no-loop-func": ERROR, + "no-magic-numbers": WARN, + "no-multi-spaces": ERROR, + "no-multi-str": WARN, + "no-native-reassign": ERROR, + "no-new-func": ERROR, + "no-new-wrappers": ERROR, + "no-new": ERROR, + "no-octal-escape": ERROR, + "no-param-reassign": ERROR, + "no-process-env": WARN, + "no-proto": ERROR, + "no-redeclare": ERROR, + "no-return-assign": ERROR, + "no-script-url": ERROR, + "no-self-compare": ERROR, + "no-throw-literal": ERROR, + "no-unused-expressions": ERROR, + "no-useless-call": ERROR, + "no-useless-concat": ERROR, + "no-void": WARN, + // Produce warnings when something is commented as TODO or FIXME + "no-warning-comments": [ WARN, { + "terms": [ "TODO", "FIXME" ], + "location": "start" + }], + "no-with": WARN, + "radix": WARN, + "vars-on-top": ERROR, + // Enforces the style of wrapped functions + "wrap-iife": [ ERROR, "outside" ], + "yoda": ERROR, + + // Strict Mode - for ES6, never use strict. + "strict": [ ERROR, "never" ], + + // Variables + "init-declarations": [ ERROR, "always" ], + "no-catch-shadow": WARN, + "no-delete-var": ERROR, + "no-label-var": ERROR, + "no-shadow-restricted-names": ERROR, + "no-shadow": WARN, + // We require all vars to be initialized (see init-declarations) + // If we NEED a var to be initialized to undefined, it needs to be explicit + "no-undef-init": OFF, + "no-undef": ERROR, + "no-undefined": OFF, + "no-unused-vars": WARN, + // Disallow hoisting - let & const don't allow hoisting anyhow + "no-use-before-define": ERROR, + + // Node.js and CommonJS + "callback-return": [ WARN, [ "callback", "next" ]], + "global-require": ERROR, + "handle-callback-err": WARN, + "no-mixed-requires": WARN, + "no-new-require": ERROR, + // Use path.concat instead + "no-path-concat": ERROR, + "no-process-exit": ERROR, + "no-restricted-modules": OFF, + "no-sync": WARN, + + // ECMAScript 6 support + "arrow-body-style": [ ERROR, "always" ], + "arrow-parens": [ ERROR, "always" ], + "arrow-spacing": [ ERROR, { "before": true, "after": true }], + "constructor-super": ERROR, + "generator-star-spacing": [ ERROR, "before" ], + "no-arrow-condition": ERROR, + "no-class-assign": ERROR, + "no-const-assign": ERROR, + "no-dupe-class-members": ERROR, + "no-this-before-super": ERROR, + "no-var": WARN, + "object-shorthand": [ WARN, "never" ], + "prefer-arrow-callback": WARN, + "prefer-spread": WARN, + "prefer-template": WARN, + "require-yield": ERROR, + + // Stylistic - everything here is a warning because of style. + "array-bracket-spacing": [ WARN, "always" ], + "block-spacing": [ WARN, "always" ], + "brace-style": [ WARN, "1tbs", { "allowSingleLine": false } ], + "camelcase": WARN, + "comma-spacing": [ WARN, { "before": false, "after": true } ], + "comma-style": [ WARN, "last" ], + "computed-property-spacing": [ WARN, "never" ], + "consistent-this": [ WARN, "self" ], + "eol-last": WARN, + "func-names": WARN, + "func-style": [ WARN, "declaration" ], + "id-length": [ WARN, { "min": 2, "max": 32 } ], + "indent": [ WARN, 4 ], + "jsx-quotes": [ WARN, "prefer-double" ], + "linebreak-style": [ WARN, "unix" ], + "lines-around-comment": [ WARN, { "beforeBlockComment": true } ], + "max-depth": [ WARN, 8 ], + "max-len": [ WARN, 132 ], + "max-nested-callbacks": [ WARN, 8 ], + "max-params": [ WARN, 8 ], + "new-cap": WARN, + "new-parens": WARN, + "no-array-constructor": WARN, + "no-bitwise": OFF, + "no-continue": OFF, + "no-inline-comments": OFF, + "no-lonely-if": WARN, + "no-mixed-spaces-and-tabs": WARN, + "no-multiple-empty-lines": WARN, + "no-negated-condition": OFF, + "no-nested-ternary": WARN, + "no-new-object": WARN, + "no-plusplus": OFF, + "no-spaced-func": WARN, + "no-ternary": OFF, + "no-trailing-spaces": WARN, + "no-underscore-dangle": WARN, + "no-unneeded-ternary": WARN, + "object-curly-spacing": [ WARN, "always" ], + "one-var": OFF, + "operator-assignment": [ WARN, "never" ], + "operator-linebreak": [ WARN, "after" ], + "padded-blocks": [ WARN, "never" ], + "quote-props": [ WARN, "consistent-as-needed" ], + "quotes": [ WARN, "single" ], + "require-jsdoc": [ WARN, { + "require": { + "FunctionDeclaration": true, + "MethodDefinition": true, + "ClassDeclaration": false + } + }], + "semi-spacing": [ WARN, { "before": false, "after": true }], + "semi": [ ERROR, "always" ], + "sort-vars": OFF, + "space-after-keywords": [ WARN, "always" ], + "space-before-blocks": [ WARN, "always" ], + "space-before-function-paren": [ WARN, "never" ], + "space-before-keywords": [ WARN, "always" ], + "space-in-parens": [ WARN, "never" ], + "space-infix-ops": [ WARN, { "int32Hint": true } ], + "space-return-throw-case": ERROR, + "space-unary-ops": ERROR, + "spaced-comment": [ WARN, "always" ], + "wrap-regex": WARN + } +}; diff --git a/frontend/package.json b/frontend/package.json index a78cd6c..a91f51d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,7 @@ "@testing-library/user-event": "^7.1.2", "axios": "^0.19.2", "bootstrap": "^4.4.1", + "eslint-config-react-app": "^5.2.1", "node-sass": "^4.14.0", "react": "^16.13.1", "react-bootstrap": "^1.0.1", diff --git a/frontend/src/components/Connection.js b/frontend/src/components/Connection.js index ce2b173..13b394a 100644 --- a/frontend/src/components/Connection.js +++ b/frontend/src/components/Connection.js @@ -4,24 +4,24 @@ import {Button, OverlayTrigger, Tooltip} from "react-bootstrap"; class Connection extends Component { render() { - let conn = this.props.data - let serviceName = "/dev/null" - let serviceColor = "#0F192E" + let conn = this.props.data; + let serviceName = "/dev/null"; + let serviceColor = "#0F192E"; if (conn.service.port !== 0) { - serviceName = conn.service.name - serviceColor = conn.service.color + serviceName = conn.service.name; + serviceColor = conn.service.color; } - let startedAt = new Date(conn.started_at) - let closedAt = new Date(conn.closed_at) - let duration = ((closedAt - startedAt) / 1000).toFixed(3) - let timeInfo = `Started at ${startedAt}\nClosed at ${closedAt}\nProcessed at ${new Date(conn.processed_at)}` + let startedAt = new Date(conn.started_at); + let closedAt = new Date(conn.closed_at); + let duration = ((closedAt - startedAt) / 1000).toFixed(3); + let timeInfo = `Started at ${startedAt}\nClosed at ${closedAt}\nProcessed at ${new Date(conn.processed_at)}`; - let classes = "connection" + let classes = "connection"; if (this.props.selected) { - classes += " connection-selected" + classes += " connection-selected"; } if (conn.marked){ - classes += " connection-marked" + classes += " connection-marked"; } return ( diff --git a/frontend/src/index.scss b/frontend/src/index.scss index def3fdf..044d150 100644 --- a/frontend/src/index.scss +++ b/frontend/src/index.scss @@ -157,4 +157,8 @@ textarea.form-control { .table { color: $color-primary-4; -} \ No newline at end of file +} + +.text-muted { + color: $color-primary-4 !important; +} diff --git a/frontend/src/utils.js b/frontend/src/utils.js index 2487f09..b72ada7 100644 --- a/frontend/src/utils.js +++ b/frontend/src/utils.js @@ -1,5 +1,5 @@ - export function createCurlCommand(subCommand, data) { - return `curl --request PUT \\\n --url ${window.location.hostname}/api${subCommand} \\\n ` + - `--header 'content-type: application/json' \\\n --data '${JSON.stringify(data)}'` + let full = window.location.protocol + '//' + window.location.hostname + (window.location.port ? ':' + window.location.port : ''); + return `curl --request PUT \\\n --url ${full}/api${subCommand} \\\n ` + + `--header 'content-type: application/json' \\\n --data '${JSON.stringify(data)}'`; } diff --git a/frontend/src/views/Services.js b/frontend/src/views/Services.js index 1b3789d..66d99c6 100644 --- a/frontend/src/views/Services.js +++ b/frontend/src/views/Services.js @@ -8,59 +8,131 @@ class Services extends Component { constructor(props) { super(props); + this.alphabet = 'abcdefghijklmnopqrstuvwxyz'; + this.colors = ["#E53935", "#D81B60", "#8E24AA", "#5E35B1", "#3949AB", "#1E88E5", "#039BE5", "#00ACC1", + "#00897B", "#43A047", "#7CB342", "#9E9D24", "#F9A825", "#FB8C00", "#F4511E", "#6D4C41"]; + this.state = { services: {}, - port: "", - portValid: false - } + port: 0, + portValid: false, + name: "", + nameValid: false, + color: this.colors[0], + colorValid: false, + notes: "" + }; this.portChanged = this.portChanged.bind(this); + this.nameChanged = this.nameChanged.bind(this); + this.notesChanged = this.notesChanged.bind(this); + this.newService = this.newService.bind(this); + this.editService = this.editService.bind(this); + this.saveService = this.saveService.bind(this); + this.loadServices = this.loadServices.bind(this); } componentDidMount() { - axios.get("/api/services").then(res => this.setState({services: res.data})) + this.loadServices(); } portChanged(event) { - let value = event.target.value.replace(/[^\d]/gi, '') - let intValue = parseInt(value) - this.setState({port: value, portValid: intValue > 0 && intValue <= 65565}) + let value = event.target.value.replace(/[^\d]/gi, ''); + let port = 0; + if (value !== "") { + port = parseInt(value); + } + this.setState({port: port}); + } + nameChanged(event) { + let value = event.target.value.replace(/[\s]/gi, '_').replace(/[^\w]/gi, '').toLowerCase(); + this.setState({name: value}); + } + notesChanged(event) { + this.setState({notes: event.target.value}); } + newService() { + this.setState({name: "", port: 0, notes: ""}); + } - render() { - let curl = createCurlCommand("/services", { - "port": this.state.port, - "name": "aaaaa", - "color": "#fff", - "notes": "aaa" - }) + editService(service) { + this.setState({name: service.name, port: service.port, color: service.color, notes: service.notes}); + } + + saveService() { + if (this.state.portValid && this.state.nameValid) { + axios.put("/api/services", { + name: this.state.name, + port: this.state.port, + color: this.state.color, + notes: this.state.notes + }); + + this.newService(); + this.loadServices(); + } + } + + loadServices() { + axios.get("/api/services").then(res => this.setState({services: res.data})); + } + + componentDidUpdate(prevProps, prevState, snapshot) { + if (this.state.name != null && prevState.name !== this.state.name) { + this.setState({ + nameValid: this.state.name.length >= 3 + }); + } + if (prevState.port !== this.state.port) { + this.setState({ + portValid: this.state.port > 0 && this.state.port <= 65565 + }); + } + } + render() { + let output = ""; + if (!this.state.portValid) { + output += "assert(1 <= port <= 65565)\n"; + } + if (!this.state.nameValid) { + output += "assert(len(name) >= 3)\n"; + } + if (output === "") { + output = createCurlCommand("/services", { + "port": this.state.port, + "name": this.state.name, + "color": this.state.color, + "notes": this.state.notes + }); + } let rows = Object.values(this.state.services).map(s =>
- | name | +port | +name |
---|