aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmiliano Ciavatta2020-05-07 10:25:41 +0000
committerEmiliano Ciavatta2020-05-07 10:25:41 +0000
commitdee7d7dfcbec7ef4475896935873f04d4df0d40f (patch)
tree78b77cd829b77e1efc9bd27e5d79941d8c14404d
parent8db3b3b43bec6170a7f1db21772c48c5e270c97b (diff)
Add colors to services dialog
-rw-r--r--frontend/.eslintrc.js216
-rw-r--r--frontend/package.json1
-rw-r--r--frontend/src/components/Connection.js24
-rw-r--r--frontend/src/index.scss6
-rw-r--r--frontend/src/utils.js6
-rw-r--r--frontend/src/views/Services.js142
-rw-r--r--frontend/src/views/Services.scss20
7 files changed, 359 insertions, 56 deletions
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 =>
<tr>
- <td><Button size="sm" style={{
- "backgroundColor": s.color
- }}>edit</Button></td>
+ <td><Button variant="btn-edit" size="sm"
+ onClick={() => this.editService(s)} style={{ "backgroundColor": s.color }}>edit</Button></td>
<td>{s.port}</td>
<td>{s.name}</td>
</tr>
- )
-
-
+ );
+ let colorButtons = this.colors.map((color, i) =>
+ <Button size="sm" className="btn-color" key={"button" + this.alphabet[i]}
+ style={{"backgroundColor": color, "borderColor": this.state.color === color ? "#fff" : color}}
+ onClick={() => this.setState({color: color})}>{this.alphabet[i]}</Button>);
return (
<Modal
{...this.props}
show="true"
size="lg"
- aria-labelledby="contained-modal-title-vcenter"
+ aria-labelledby="services-dialog"
centered
>
<Modal.Header>
- <Modal.Title id="contained-modal-title-vcenter">
+ <Modal.Title id="services-dialog">
~/services
</Modal.Title>
</Modal.Header>
@@ -71,9 +143,9 @@ class Services extends Component {
<Table borderless size="sm" className="services-list">
<thead>
<tr>
- <th><Button size="sm">new</Button></th>
- <th>name</th>
+ <th><Button size="sm" onClick={this.newService}>new</Button></th>
<th>port</th>
+ <th>name</th>
</tr>
</thead>
<tbody>
@@ -86,31 +158,26 @@ class Services extends Component {
<Form.Group controlId="servicePort">
<Form.Label>port:</Form.Label>
<Form.Control type="text" onChange={this.portChanged} value={this.state.port} />
- <Form.Text className="text-muted">
- {!this.state.portValid ? "assert(1 <= port <= 65565)" : ""}
- </Form.Text>
</Form.Group>
<Form.Group controlId="serviceName">
<Form.Label>name:</Form.Label>
- <Form.Control type="text" required min={3} max={16} />
- <Form.Text className="text-muted">
- {"assert(len(name) >= 3)"}
- </Form.Text>
+ <Form.Control type="text" onChange={this.nameChanged} value={this.state.name}/>
</Form.Group>
<Form.Group controlId="serviceColor">
<Form.Label>color:</Form.Label>
<ButtonGroup aria-label="Basic example">
- <Button variant="secondary">Left</Button>
- <Button variant="secondary">Middle</Button>
- <Button variant="secondary">Right</Button>
+ {colorButtons.slice(0,8)}
+ </ButtonGroup>
+ <ButtonGroup aria-label="Basic example">
+ {colorButtons.slice(8,18)}
</ButtonGroup>
</Form.Group>
<Form.Group controlId="serviceNotes">
<Form.Label>notes:</Form.Label>
- <Form.Control type="textarea" />
+ <Form.Control as="textarea" rows={3} onChange={this.notesChanged} value={this.state.notes} />
</Form.Group>
</Form>
@@ -122,8 +189,7 @@ class Services extends Component {
<Row>
<Col md={12}>
<InputGroup>
- <FormControl as="textarea" rows={9} className="curl-output" readOnly={true}>{curl}
- </FormControl>
+ <FormControl as="textarea" rows={4} className="curl-output" readOnly={true} value={output}/>
</InputGroup>
</Col>
@@ -131,7 +197,7 @@ class Services extends Component {
</Container>
</Modal.Body>
<Modal.Footer className="dialog-footer">
- <Button variant="green" onClick={this.props.onHide}>save</Button>
+ <Button variant="green" onClick={this.saveService}>save</Button>
<Button variant="red" onClick={this.props.onHide}>close</Button>
</Modal.Footer>
</Modal>
diff --git a/frontend/src/views/Services.scss b/frontend/src/views/Services.scss
index fd65beb..2abb55e 100644
--- a/frontend/src/views/Services.scss
+++ b/frontend/src/views/Services.scss
@@ -1,3 +1,5 @@
+@import '../colors.scss';
+
.curl-output {
width: 100%;
font-size: 13px;
@@ -5,12 +7,26 @@
.services-list {
.btn {
- width: 150px;
+ width: 70px;
+ }
+
+ td {
+ background-color: $color-primary-2;
+ border-top: 2px solid $color-primary-0;
+ vertical-align: middle;
+ }
+
+ th {
+ background-color: $color-primary-1;
}
}
+.btn-color {
+ border: 3px solid #fff;
+}
+
.dialog-footer {
.btn {
width: 80px;
}
-} \ No newline at end of file
+}