├── server-worker-graph ├── README.md ├── .npmignore ├── src │ ├── model │ │ ├── viewer │ │ │ └── index.js │ │ ├── service │ │ │ └── index.js │ │ └── ports │ │ │ └── index.js │ ├── schema │ │ ├── viewer │ │ │ ├── ports │ │ │ │ └── index.js │ │ │ └── index.js │ │ └── index.js │ ├── api │ │ └── port │ │ │ └── index.js │ └── index.js ├── .babelrc ├── pkg │ ├── linker-dev │ │ └── index.js │ ├── linker-token │ │ └── index.js │ ├── linker-json │ │ └── index.js │ ├── linker-folder │ │ └── index.js │ ├── linker-cache │ │ └── index.js │ ├── linker-kubectl │ │ └── index.js │ ├── linker-tunnel │ │ └── index.js │ └── linker-operation │ │ └── index.js ├── webpack.config.js └── package.json ├── local-boot-graph ├── .npmignore ├── src │ ├── model │ │ ├── viewer │ │ │ ├── index.js │ │ │ ├── proxy │ │ │ │ └── index.js │ │ │ ├── workers │ │ │ │ └── index.js │ │ │ └── hosts │ │ │ │ └── index.js │ │ └── service │ │ │ ├── handler │ │ │ ├── index.js │ │ │ └── template.js │ │ │ └── index.js │ ├── schema │ │ ├── viewer │ │ │ ├── service │ │ │ │ └── index.js │ │ │ ├── proxy │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ ├── workers │ │ │ │ └── index.js │ │ │ └── hosts │ │ │ │ └── index.js │ │ └── index.js │ └── api │ │ ├── server │ │ └── index.js │ │ ├── proxy │ │ └── index.js │ │ └── workers │ │ └── index.js ├── .babelrc ├── pkg │ ├── linker-validation │ │ └── index.js │ ├── linker-json │ │ └── index.js │ ├── linker-dev │ │ └── index.js │ ├── linker-request │ │ └── index.js │ ├── linker-folder │ │ └── index.js │ ├── linker-compose │ │ └── index.js │ ├── linker-tunnel │ │ └── index.js │ └── linker-operation │ │ └── index.js ├── webpack.config.js └── package.json ├── local-worker-graph ├── .npmignore ├── src │ ├── model │ │ ├── viewer │ │ │ └── index.js │ │ ├── worker │ │ │ └── index.js │ │ └── service │ │ │ └── index.js │ ├── schema │ │ ├── viewer │ │ │ ├── index.js │ │ │ └── worker │ │ │ │ └── index.js │ │ └── index.js │ ├── api │ │ ├── graph │ │ │ └── index.js │ │ └── server │ │ │ └── index.js │ └── index.js ├── .babelrc ├── pkg │ ├── linker-dev │ │ └── index.js │ ├── linker-token │ │ └── index.js │ ├── linker-json │ │ └── index.js │ ├── linker-folder │ │ └── index.js │ ├── linker-request │ │ └── index.js │ ├── linker-cache │ │ └── index.js │ ├── linker-kubectl │ │ └── index.js │ ├── linker-tunnel │ │ └── index.js │ └── linker-operation │ │ └── index.js ├── webpack.config.js └── package.json ├── server-boot-graph ├── .npmignore ├── src │ ├── model │ │ ├── viewer │ │ │ ├── index.js │ │ │ └── workers │ │ │ │ └── index.js │ │ └── service │ │ │ ├── handler │ │ │ ├── index.js │ │ │ └── template.js │ │ │ └── index.js │ ├── schema │ │ ├── viewer │ │ │ ├── service │ │ │ │ └── index.js │ │ │ ├── index.js │ │ │ └── workers │ │ │ │ └── index.js │ │ └── index.js │ ├── api │ │ └── workers │ │ │ └── index.js │ └── index.js ├── .babelrc ├── pkg │ ├── linker-json │ │ └── index.js │ ├── linker-dev │ │ └── index.js │ ├── linker-request │ │ └── index.js │ ├── linker-folder │ │ └── index.js │ ├── linker-compose │ │ └── index.js │ ├── linker-tunnel │ │ └── index.js │ └── linker-operation │ │ └── index.js ├── webpack.config.js └── package.json ├── local-web ├── .npmignore ├── .babelrc ├── pkg │ ├── linker-notify │ │ └── index.js │ ├── linker-validation │ │ └── index.js │ └── linker-network │ │ └── index.js ├── src │ ├── common │ │ ├── state │ │ │ └── index.js │ │ ├── queries │ │ │ ├── viewer.js │ │ │ ├── service │ │ │ │ └── index.js │ │ │ └── device │ │ │ │ └── index.js │ │ ├── ui │ │ │ ├── utils │ │ │ │ ├── loading.js │ │ │ │ ├── link.js │ │ │ │ ├── field.js │ │ │ │ ├── mutation.js │ │ │ │ ├── layout.js │ │ │ │ ├── list.js │ │ │ │ ├── confirm.js │ │ │ │ ├── query.js │ │ │ │ └── info.js │ │ │ └── label │ │ │ │ └── index.js │ │ ├── fragments │ │ │ ├── service │ │ │ │ └── index.js │ │ │ └── device │ │ │ │ ├── outlet │ │ │ │ └── index.js │ │ │ │ ├── inlet │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ ├── root │ │ │ ├── index.js │ │ │ └── home │ │ │ │ ├── inlets │ │ │ │ └── index.js │ │ │ │ ├── outlets │ │ │ │ └── index.js │ │ │ │ └── index.js │ │ ├── App.js │ │ ├── actions │ │ │ ├── service │ │ │ │ └── update.js │ │ │ └── device │ │ │ │ ├── service │ │ │ │ └── update.js │ │ │ │ ├── inlet │ │ │ │ ├── state │ │ │ │ │ ├── stop.js │ │ │ │ │ └── start.js │ │ │ │ └── remove.js │ │ │ │ └── outlet │ │ │ │ ├── state │ │ │ │ ├── stop.js │ │ │ │ └── start.js │ │ │ │ └── remove.js │ │ └── comps │ │ │ └── device │ │ │ └── index.js │ ├── web │ │ ├── index.js │ │ └── render.js │ └── server │ │ ├── template.js │ │ ├── render.js │ │ └── index.js ├── webpack.config.common.js ├── webpack.config.web.js ├── webpack.config.server.js └── package.json ├── server-web-container ├── container.json └── Dockerfile ├── local-web-container ├── container.json └── Dockerfile ├── local-graph-container ├── container.json └── Dockerfile ├── server-graph-container ├── container.json └── Dockerfile ├── worker-graph-container ├── container.json └── Dockerfile ├── local-worker-graph-container ├── container.json └── Dockerfile └── LICENSE /server-worker-graph/README.md: -------------------------------------------------------------------------------- 1 | # tunnel-server-worker-graph -------------------------------------------------------------------------------- /local-boot-graph/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | pkg 3 | static 4 | webpack.* 5 | .babelrc 6 | .env 7 | -------------------------------------------------------------------------------- /local-worker-graph/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | static 3 | webpack.* 4 | pkg 5 | .babelrc 6 | .env 7 | -------------------------------------------------------------------------------- /server-boot-graph/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | pkg 3 | static 4 | webpack.* 5 | .babelrc 6 | .env 7 | -------------------------------------------------------------------------------- /server-worker-graph/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | static 3 | webpack.* 4 | pkg 5 | .babelrc 6 | .env 7 | -------------------------------------------------------------------------------- /local-web/.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | static 3 | service 4 | webpack.config.js 5 | .babelrc 6 | .env 7 | -------------------------------------------------------------------------------- /local-web/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/react" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /local-boot-graph/src/model/viewer/index.js: -------------------------------------------------------------------------------- 1 | export const get = cxt => { 2 | return { 3 | id: null, 4 | username: null 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /server-boot-graph/src/model/viewer/index.js: -------------------------------------------------------------------------------- 1 | export const get = cxt => { 2 | return { 3 | id: null, 4 | username: null 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /local-web/pkg/linker-notify/index.js: -------------------------------------------------------------------------------- 1 | import { toast } from "react-toastify"; 2 | 3 | export const error = ({ message }) => { 4 | toast.error(message); 5 | }; 6 | -------------------------------------------------------------------------------- /local-web/src/common/state/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | combineReducers 3 | } from 'redux'; 4 | 5 | export const watchers = []; 6 | export const reducers = { 7 | }; 8 | -------------------------------------------------------------------------------- /local-worker-graph/src/model/viewer/index.js: -------------------------------------------------------------------------------- 1 | 2 | export const get = async ({}, cxt) => { 3 | 4 | return { 5 | id: null, 6 | username: null 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /server-worker-graph/src/model/viewer/index.js: -------------------------------------------------------------------------------- 1 | 2 | export const get = async ({}, cxt) => { 3 | 4 | return { 5 | id: null, 6 | username: null 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /local-web/src/common/queries/viewer.js: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-boost"; 2 | 3 | export const Get = gql` 4 | query { 5 | viewer { 6 | id 7 | } 8 | } 9 | `; 10 | -------------------------------------------------------------------------------- /local-web/src/common/ui/utils/loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default () => ( 4 |
7 | 8 |
9 | ); 10 | -------------------------------------------------------------------------------- /local-boot-graph/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "@babel/plugin-transform-runtime", 5 | { 6 | "regenerator": true, 7 | "corejs": 3 8 | } 9 | ] 10 | ], 11 | "presets": [ 12 | "@babel/preset-env" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /local-worker-graph/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "@babel/plugin-transform-runtime", 5 | { 6 | "regenerator": true, 7 | "corejs": 3 8 | } 9 | ] 10 | ], 11 | "presets": [ 12 | "@babel/preset-env" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /server-boot-graph/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "@babel/plugin-transform-runtime", 5 | { 6 | "regenerator": true, 7 | "corejs": 3 8 | } 9 | ] 10 | ], 11 | "presets": [ 12 | "@babel/preset-env" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /server-web-container/container.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repoflow/tunnel-server-web-container", 3 | "version": "1.70.4-master", 4 | "source": { 5 | "type": "npm", 6 | "fullname": "@nebulario/tunnel-server-web", 7 | "version": "1.70.3-master" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /server-worker-graph/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "@babel/plugin-transform-runtime", 5 | { 6 | "regenerator": true, 7 | "corejs": 3 8 | } 9 | ] 10 | ], 11 | "presets": [ 12 | "@babel/preset-env" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /local-web-container/container.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repoflow/tunnel-local-web-container", 3 | "version": "1.70.8-initial-client-prod", 4 | "source": { 5 | "type": "npm", 6 | "fullname": "@nebulario/tunnel-local-web", 7 | "version": "1.70.8-initial-client-prod" 8 | } 9 | } -------------------------------------------------------------------------------- /local-graph-container/container.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repoflow/tunnel-local-graph-container", 3 | "version": "1.70.11-initial-client-prod", 4 | "source": { 5 | "type": "npm", 6 | "fullname": "@nebulario/tunnel-local-graph", 7 | "version": "1.70.11-initial-client-prod" 8 | } 9 | } -------------------------------------------------------------------------------- /server-graph-container/container.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repoflow/tunnel-server-graph-container", 3 | "version": "1.70.17-initial-server-prod", 4 | "source": { 5 | "type": "npm", 6 | "fullname": "@nebulario/tunnel-server-graph", 7 | "version": "1.70.17-initial-server-prod" 8 | } 9 | } -------------------------------------------------------------------------------- /local-web/src/common/ui/utils/link.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import _ from "lodash"; 3 | import { Link } from "react-router-dom"; 4 | 5 | export default ({ to, children }) => ( 6 | 7 | 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /local-web/src/common/fragments/service/index.js: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-boost"; 2 | 3 | 4 | export const ServiceFragment = gql` 5 | fragment ServiceFragment on Service { 6 | upgradable 7 | needRestart 8 | messages { 9 | type 10 | message 11 | } 12 | } 13 | `; 14 | -------------------------------------------------------------------------------- /worker-graph-container/container.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repoflow/tunnel-server-worker-graph-container", 3 | "version": "1.70.4-initial-server-prod", 4 | "source": { 5 | "type": "npm", 6 | "fullname": "@nebulario/tunnel-server-worker-graph", 7 | "version": "1.70.3-initial-server-prod" 8 | } 9 | } -------------------------------------------------------------------------------- /local-worker-graph-container/container.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "repoflow/tunnel-local-worker-graph-container", 3 | "version": "1.70.4-initial-client-prod", 4 | "source": { 5 | "type": "npm", 6 | "fullname": "@nebulario/tunnel-local-worker-graph", 7 | "version": "1.70.4-initial-client-prod" 8 | } 9 | } -------------------------------------------------------------------------------- /local-web/src/common/ui/utils/field.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | export default ({ label, children }) => ( 4 |
5 | 6 | {label} 7 | 8 | {children} 9 |
10 | ); 11 | -------------------------------------------------------------------------------- /local-web/src/common/queries/service/index.js: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-boost"; 2 | import { ServiceFragment } from "Fragments/service"; 3 | 4 | export const Get = gql` 5 | query Service { 6 | viewer { 7 | id 8 | service { 9 | ...ServiceFragment 10 | } 11 | } 12 | } 13 | ${ServiceFragment} 14 | `; 15 | -------------------------------------------------------------------------------- /local-web/src/common/ui/label/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Badge } from "reactstrap"; 3 | 4 | export const Icon = () => ; 5 | 6 | export const Label = ({ color = "info", label: { name, value } }) => ( 7 | 8 | 9 | {name}:{value} 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /local-web/src/common/root/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Route, NavLink, Switch } from "react-router-dom"; 3 | import Home from "./home"; 4 | 5 | export default ({ viewer }) => ( 6 |
7 | 8 | } 11 | /> 12 | 13 |
14 | ); 15 | -------------------------------------------------------------------------------- /local-web/src/common/ui/utils/mutation.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import * as Notify from "PKG/linker-notify"; 3 | 4 | export const onError = error => { 5 | 6 | const errors = error.graphQLErrors 7 | ? error.graphQLErrors.map(error => { 8 | return error.message; 9 | }) 10 | : [error.toString()]; 11 | 12 | Notify.error({ message: errors.join(",") }); 13 | }; 14 | -------------------------------------------------------------------------------- /server-worker-graph/src/model/service/index.js: -------------------------------------------------------------------------------- 1 | import { spawn } from "child-process-promise"; 2 | import fs from "fs"; 3 | 4 | export const start = async cxt => { 5 | cxt.services.sshd.process = spawn("/usr/sbin/sshd", [ 6 | "-p", 7 | "2200", 8 | "-D" 9 | ]).catch(e => cxt.logger.error("sshd.error", { error: e.toString() })); 10 | cxt.logger.debug("sshd.started", { port: 2200 }); 11 | }; 12 | -------------------------------------------------------------------------------- /server-worker-graph/src/schema/viewer/ports/index.js: -------------------------------------------------------------------------------- 1 | import * as PortModel from 'Model/ports' 2 | 3 | const schema = [ 4 | ` 5 | 6 | type PortMutations { 7 | stop(port: Int!): Boolean 8 | } 9 | 10 | ` 11 | ]; 12 | 13 | const resolvers = { 14 | PortMutations: { 15 | stop: async (viewer, { port }, cxt) => await PortModel.stop(port, cxt) 16 | } 17 | }; 18 | 19 | export { schema, resolvers }; 20 | -------------------------------------------------------------------------------- /local-web/src/web/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "../common/App.js"; 4 | import { render } from "./render.js"; 5 | import { reducers, watchers } from "../common/state"; 6 | 7 | const { 8 | urls: { graphql, events } 9 | } = window.__CONFIG__; 10 | 11 | render({ 12 | App, 13 | watchers, 14 | reducers, 15 | urls: { 16 | graphql, 17 | events 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /local-boot-graph/src/model/viewer/proxy/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import * as ProxyApi from "Api/proxy"; 5 | 6 | export const start = async cxt => { 7 | cxt.logger.debug("proxy.start", {}); 8 | await ProxyApi.start(cxt); 9 | }; 10 | 11 | export const stop = async cxt => { 12 | cxt.logger.debug("proxy.stop", {}); 13 | await ProxyApi.stop(cxt); 14 | }; 15 | -------------------------------------------------------------------------------- /local-web/src/common/fragments/device/outlet/index.js: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-boost"; 2 | 3 | export const DeviceOutletFragment = gql` 4 | fragment DeviceOutletFragment on DeviceOutlet { 5 | id 6 | outletid 7 | src { 8 | host 9 | port 10 | } 11 | state { 12 | active 13 | status 14 | worker { 15 | workerid 16 | ip 17 | port 18 | } 19 | } 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /local-web/pkg/linker-validation/index.js: -------------------------------------------------------------------------------- 1 | import validator from "validator"; 2 | 3 | export const isEntityName = name => { 4 | const rx = new RegExp(/[a-z0-9](?:[-a-z0-9]*[a-z0-9])/g); 5 | const match = rx.exec(name); 6 | return match !== null && match[0] === name; 7 | }; 8 | 9 | export const isPort = value => 10 | value 11 | ? validator.isInt(value.toString(), { 12 | min: 1, 13 | max: 65535 14 | }) 15 | : false; 16 | -------------------------------------------------------------------------------- /local-boot-graph/pkg/linker-validation/index.js: -------------------------------------------------------------------------------- 1 | import validator from "validator"; 2 | 3 | export const isEntityName = name => { 4 | const rx = new RegExp(/[a-z0-9](?:[-a-z0-9]*[a-z0-9])/g); 5 | const match = rx.exec(name); 6 | return match !== null && match[0] === name; 7 | }; 8 | 9 | export const isPort = value => 10 | value 11 | ? validator.isInt(value.toString(), { 12 | min: 1, 13 | max: 65535 14 | }) 15 | : false; 16 | -------------------------------------------------------------------------------- /server-boot-graph/src/schema/viewer/service/index.js: -------------------------------------------------------------------------------- 1 | import * as ServiceModel from "Model/service"; 2 | 3 | const schema = [ 4 | ` 5 | type ServiceMutations { 6 | update(boot: String!, graph: String!, worker: String!): Boolean! 7 | } 8 | 9 | ` 10 | ]; 11 | 12 | const resolvers = { 13 | ServiceMutations: { 14 | update: async (viewer, input, cxt) => await ServiceModel.update(input, cxt) 15 | } 16 | }; 17 | 18 | export { schema, resolvers }; 19 | -------------------------------------------------------------------------------- /local-boot-graph/src/schema/viewer/service/index.js: -------------------------------------------------------------------------------- 1 | import * as ServiceModel from "Model/service"; 2 | 3 | const schema = [ 4 | ` 5 | type ServiceMutations { 6 | update(boot: String!, graph: String!, web: String!, worker: String!): Boolean! 7 | } 8 | 9 | ` 10 | ]; 11 | 12 | const resolvers = { 13 | ServiceMutations: { 14 | update: async (viewer, input, cxt) => await ServiceModel.update(input, cxt) 15 | } 16 | }; 17 | 18 | export { schema, resolvers }; 19 | -------------------------------------------------------------------------------- /local-boot-graph/src/schema/viewer/proxy/index.js: -------------------------------------------------------------------------------- 1 | import * as ProxyModel from "Model/viewer/proxy"; 2 | 3 | const schema = [ 4 | ` 5 | type LocalProxyMutations { 6 | start: String 7 | stop: String 8 | } 9 | 10 | 11 | ` 12 | ]; 13 | 14 | const resolvers = { 15 | LocalProxyMutations: { 16 | start: async (viewer, {}, cxt) => await ProxyModel.start(cxt), 17 | stop: async (viewer, {}, cxt) => await ProxyModel.stop(cxt) 18 | } 19 | }; 20 | 21 | export { schema, resolvers }; 22 | -------------------------------------------------------------------------------- /local-web/src/common/ui/utils/layout.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const Title = ({ icon, children }) => ( 4 |
5 |
6 | {icon} {children} 7 |
8 |
9 |
10 | ); 11 | 12 | export const Field = ({ label, control, children }) => ( 13 |
14 | 15 | {label} {control} 16 | 17 | {children} 18 |
19 | ); 20 | -------------------------------------------------------------------------------- /server-worker-graph/src/schema/viewer/index.js: -------------------------------------------------------------------------------- 1 | import * as PortSchema from "Schema/viewer/ports"; 2 | 3 | const schema = [ 4 | ...PortSchema.schema, 5 | ` 6 | type Viewer { 7 | id: ID 8 | } 9 | 10 | type ViewerMutations { 11 | id: ID 12 | ports: PortMutations 13 | } 14 | 15 | ` 16 | ]; 17 | 18 | const resolvers = { 19 | ...PortSchema.resolvers, 20 | Viewer: {}, 21 | ViewerMutations: { 22 | ports: viewer => viewer 23 | } 24 | }; 25 | 26 | export { schema, resolvers }; 27 | -------------------------------------------------------------------------------- /local-worker-graph/pkg/linker-dev/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | 3 | const config = () => { 4 | const configEnv = process.env["DEV_CONFIG"]; 5 | 6 | let config = {}; 7 | try { 8 | config = JSON.parse(configEnv); 9 | console.log("DEV_CONFIG"); 10 | console.log(JSON.stringify(config, null, 2)); 11 | } catch (e) {} 12 | return config; 13 | }; 14 | 15 | export const init = cxt => { 16 | cxt.dev = config(); 17 | }; 18 | 19 | export const get = (ptk, cxt) => { 20 | return _.get(cxt.dev, ptk); 21 | }; 22 | -------------------------------------------------------------------------------- /server-worker-graph/pkg/linker-dev/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | 3 | const config = () => { 4 | const configEnv = process.env["DEV_CONFIG"]; 5 | 6 | let config = {}; 7 | try { 8 | config = JSON.parse(configEnv); 9 | console.log("DEV_CONFIG"); 10 | console.log(JSON.stringify(config, null, 2)); 11 | } catch (e) {} 12 | return config; 13 | }; 14 | 15 | export const init = cxt => { 16 | cxt.dev = config(); 17 | }; 18 | 19 | export const get = (ptk, cxt) => { 20 | return _.get(cxt.dev, ptk); 21 | }; 22 | -------------------------------------------------------------------------------- /local-worker-graph/pkg/linker-token/index.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | 3 | export const create = async (payload, { key: privateKey }, cxt) => { 4 | return jwt.sign(payload, privateKey, { 5 | algorithm: "RS256" 6 | }); 7 | }; 8 | 9 | export const decrypt = async (token, { key: publicKey }, cxt) => { 10 | try { 11 | return jwt.verify(token, publicKey, { 12 | algorithm: "RS256" 13 | }); 14 | } catch (e) { 15 | cxt.logger.error("token.error", { error: e.toString(), token }); 16 | return null; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /server-worker-graph/pkg/linker-token/index.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | 3 | export const create = async (payload, { key: privateKey }, cxt) => { 4 | return jwt.sign(payload, privateKey, { 5 | algorithm: "RS256" 6 | }); 7 | }; 8 | 9 | export const decrypt = async (token, { key: publicKey }, cxt) => { 10 | try { 11 | return jwt.verify(token, publicKey, { 12 | algorithm: "RS256" 13 | }); 14 | } catch (e) { 15 | cxt.logger.error("token.error", { error: e.toString(), token }); 16 | return null; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /local-boot-graph/pkg/linker-json/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import YAML from "yamljs"; 5 | 6 | export const load = (filename, isYaml = false) => { 7 | const content = fs.readFileSync(filename, "utf8"); 8 | return isYaml ? YAML.parse(content) : JSON.parse(content); 9 | }; 10 | 11 | export const save = (filename, content, isYaml = false) => { 12 | fs.writeFileSync( 13 | filename, 14 | isYaml ? YAML.stringify(content, 10, 2) : JSON.stringify(content, null, 2), 15 | "utf8" 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /server-boot-graph/pkg/linker-json/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import YAML from "yamljs"; 5 | 6 | export const load = (filename, isYaml = false) => { 7 | const content = fs.readFileSync(filename, "utf8"); 8 | return isYaml ? YAML.parse(content) : JSON.parse(content); 9 | }; 10 | 11 | export const save = (filename, content, isYaml = false) => { 12 | fs.writeFileSync( 13 | filename, 14 | isYaml ? YAML.stringify(content, 10, 2) : JSON.stringify(content, null, 2), 15 | "utf8" 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /server-graph-container/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.13.0-alpine 2 | 3 | ENV ENV_ROOT=/env 4 | RUN mkdir ${ENV_ROOT} 5 | RUN chown node ${ENV_ROOT} 6 | 7 | ENV CONTAINER=tunnel-server-graph-container 8 | ENV SOURCE=tunnel-server-graph 9 | 10 | ENV APP_ROOT=/env/${CONTAINER}/dist 11 | ENV APP_HOME=${APP_ROOT}/node_modules/@nebulario/${SOURCE} 12 | 13 | ARG CACHEBUST=1 14 | RUN echo "CACHE $CACHEBUST" 15 | 16 | RUN mkdir -p ${APP_HOME} 17 | COPY --chown=node:node ./dist ${APP_ROOT} 18 | RUN chown -R node ${APP_HOME} 19 | 20 | USER node 21 | 22 | WORKDIR ${APP_HOME} 23 | ENTRYPOINT ["node"] 24 | CMD ["dist/index.js"] 25 | -------------------------------------------------------------------------------- /local-web/src/common/fragments/device/inlet/index.js: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-boost"; 2 | 3 | export const DeviceInletFragment = gql` 4 | fragment DeviceInletFragment on DeviceInlet { 5 | id 6 | inletid 7 | target { 8 | deviceid 9 | outletid 10 | } 11 | dest { 12 | host 13 | port 14 | } 15 | state { 16 | active 17 | status 18 | target { 19 | id 20 | state { 21 | active 22 | } 23 | } 24 | worker { 25 | ip 26 | } 27 | hosts { 28 | ip 29 | } 30 | } 31 | } 32 | `; 33 | -------------------------------------------------------------------------------- /local-web/webpack.config.common.js: -------------------------------------------------------------------------------- 1 | class WatchRunPlugin { 2 | 3 | constructor(target) { 4 | this.target = target; 5 | } 6 | 7 | apply(compiler) { 8 | compiler.hooks.watchRun.tap("WatchRun", comp => { 9 | const changedTimes = comp.watchFileSystem.watcher.mtimes; 10 | const changedFiles = Object.keys(changedTimes) 11 | .map(file => `\n ${file}`) 12 | .join(""); 13 | if (changedFiles.length) { 14 | console.log(`[target:${this.target}:file:${changedFiles.trim()}]`, ); 15 | } 16 | }); 17 | } 18 | } 19 | 20 | module.exports = { 21 | WatchRunPlugin 22 | }; 23 | -------------------------------------------------------------------------------- /local-worker-graph/src/model/worker/index.js: -------------------------------------------------------------------------------- 1 | import { spawn } from "child-process-promise"; 2 | import { execSync } from "child_process"; 3 | import fs from "fs"; 4 | import _ from "lodash"; 5 | 6 | import * as WorkerApi from "Api/worker"; 7 | 8 | export const get = async cxt => { 9 | const worker = await WorkerApi.get(cxt); 10 | return worker; 11 | }; 12 | 13 | export const set = async (worker, { inlets, outlets }, cxt) => { 14 | cxt.logger.debug("worker.set", { inlets, outlets }); 15 | await WorkerApi.set(inlets, outlets, cxt); 16 | await WorkerApi.update(cxt); 17 | return await WorkerApi.get(cxt); 18 | }; 19 | -------------------------------------------------------------------------------- /local-web/src/common/ui/utils/list.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Query from "UI/utils/query"; 3 | 4 | export default ({ query, variables, list: getList, children, noitems }) => ( 5 | 6 | {props => { 7 | const { data } = props; 8 | 9 | const list = getList(data); 10 | return list.length === 0 ? ( 11 | noitems ? ( 12 | noitems 13 | ) : ( 14 |
No items found...
15 | ) 16 | ) : ( 17 | children({ ...props, list }) 18 | ); 19 | }} 20 |
21 | ); 22 | -------------------------------------------------------------------------------- /local-web/src/common/queries/device/index.js: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-boost"; 2 | import { DeviceFragment } from "Fragments/device"; 3 | 4 | export const List = gql` 5 | query Devices { 6 | viewer { 7 | id 8 | devices { 9 | list { 10 | ...DeviceFragment 11 | } 12 | } 13 | } 14 | } 15 | ${DeviceFragment} 16 | `; 17 | 18 | export const Get = gql` 19 | query Device($deviceid: String!) { 20 | viewer { 21 | id 22 | devices { 23 | device(deviceid: $deviceid) { 24 | ...DeviceFragment 25 | } 26 | } 27 | } 28 | } 29 | ${DeviceFragment} 30 | `; 31 | -------------------------------------------------------------------------------- /local-worker-graph/pkg/linker-json/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import YAML from "yamljs"; 5 | 6 | export const load = (filename, isYaml = false) => { 7 | const content = fs.readFileSync(filename, "utf8"); 8 | return parse(content, isYaml); 9 | }; 10 | 11 | export const save = (filename, content, isYaml = false) => { 12 | fs.writeFileSync( 13 | filename, 14 | isYaml ? YAML.stringify(content, 10, 2) : JSON.stringify(content, null, 2), 15 | "utf8" 16 | ); 17 | }; 18 | 19 | export const parse = (content, isYaml = false) => 20 | isYaml ? YAML.parse(content) : JSON.parse(content); 21 | -------------------------------------------------------------------------------- /server-worker-graph/pkg/linker-json/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import YAML from "yamljs"; 5 | 6 | export const load = (filename, isYaml = false) => { 7 | const content = fs.readFileSync(filename, "utf8"); 8 | return parse(content, isYaml); 9 | }; 10 | 11 | export const save = (filename, content, isYaml = false) => { 12 | fs.writeFileSync( 13 | filename, 14 | isYaml ? YAML.stringify(content, 10, 2) : JSON.stringify(content, null, 2), 15 | "utf8" 16 | ); 17 | }; 18 | 19 | export const parse = (content, isYaml = false) => 20 | isYaml ? YAML.parse(content) : JSON.parse(content); 21 | -------------------------------------------------------------------------------- /server-worker-graph/src/model/ports/index.js: -------------------------------------------------------------------------------- 1 | import * as PortApi from "Api/port"; 2 | import kill from "tree-kill"; 3 | 4 | export const stop = async (port, cxt) => { 5 | const pid = await PortApi.getProcId(port, cxt); 6 | 7 | if (pid) { 8 | cxt.logger.debug("port.id", { port, pid }); 9 | 10 | kill(pid, err => { 11 | if (err) { 12 | cxt.logger.error("port.stop.error", { 13 | error: err.toString(), 14 | pid 15 | }); 16 | } 17 | 18 | cxt.logger.debug("port.stopped", { 19 | port, 20 | pid 21 | }); 22 | }); 23 | return true; 24 | } 25 | 26 | return false; 27 | }; 28 | -------------------------------------------------------------------------------- /local-boot-graph/src/api/server/index.js: -------------------------------------------------------------------------------- 1 | import { request } from "PKG/linker-request"; 2 | 3 | const SERVER_INFO = `mutation ServerInfo { 4 | viewer { 5 | id 6 | } 7 | }`; 8 | 9 | export const info = async cxt => { 10 | const { 11 | instance: { 12 | network: { 13 | graph: { ip: host } 14 | } 15 | } 16 | } = cxt; 17 | 18 | const url = `http://${host}:9000/graphql`; 19 | cxt.logger.debug("server.info", { 20 | url 21 | }); 22 | const res = await request(url, SERVER_INFO, {}, {}, cxt); 23 | 24 | const { viewer } = res; 25 | 26 | cxt.logger.debug("server.info.response", viewer); 27 | 28 | return viewer; 29 | }; 30 | -------------------------------------------------------------------------------- /local-graph-container/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.13.0-alpine 2 | 3 | RUN apk --update add --no-cache openssh-client curl 4 | 5 | ENV ENV_ROOT=/env 6 | RUN mkdir ${ENV_ROOT} 7 | RUN chown node ${ENV_ROOT} 8 | 9 | ENV CONTAINER=tunnel-local-graph-container 10 | ENV SOURCE=tunnel-local-graph 11 | 12 | ENV APP_ROOT=/env/${CONTAINER}/dist 13 | ENV APP_HOME=${APP_ROOT}/node_modules/@nebulario/${SOURCE} 14 | 15 | ARG CACHEBUST=1 16 | RUN echo "CACHE $CACHEBUST" 17 | 18 | RUN mkdir -p ${APP_HOME} 19 | COPY --chown=node:node ./dist ${APP_ROOT} 20 | RUN chown -R node ${APP_HOME} 21 | 22 | USER node 23 | 24 | WORKDIR ${APP_HOME} 25 | ENTRYPOINT ["node"] 26 | CMD ["dist/index.js"] 27 | -------------------------------------------------------------------------------- /local-web/src/common/fragments/device/index.js: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-boost"; 2 | import { DeviceOutletFragment } from "./outlet"; 3 | import { DeviceInletFragment } from "./inlet"; 4 | 5 | export const DeviceFragment = gql` 6 | fragment DeviceFragment on Device { 7 | id 8 | deviceid 9 | service { 10 | upgradable 11 | needRestart 12 | } 13 | state { 14 | online 15 | } 16 | outlets { 17 | list { 18 | ...DeviceOutletFragment 19 | } 20 | } 21 | inlets { 22 | list { 23 | ...DeviceInletFragment 24 | } 25 | } 26 | } 27 | ${DeviceOutletFragment} 28 | ${DeviceInletFragment} 29 | `; 30 | -------------------------------------------------------------------------------- /local-worker-graph-container/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.13.0-alpine 2 | 3 | RUN apk --update add --no-cache openssh-client 4 | 5 | ENV ENV_ROOT=/env 6 | RUN mkdir ${ENV_ROOT} 7 | RUN chown node ${ENV_ROOT} 8 | 9 | ENV CONTAINER=tunnel-local-worker-graph-container 10 | ENV SOURCE=tunnel-local-worker-graph 11 | 12 | ENV APP_ROOT=/env/${CONTAINER}/dist 13 | ENV APP_HOME=${APP_ROOT}/node_modules/@nebulario/${SOURCE} 14 | 15 | ARG CACHEBUST=1 16 | RUN echo "CACHE $CACHEBUST" 17 | 18 | RUN mkdir -p ${APP_HOME} 19 | COPY --chown=node:node ./dist ${APP_ROOT} 20 | RUN chown -R node ${APP_HOME} 21 | 22 | USER node 23 | 24 | WORKDIR ${APP_HOME} 25 | ENTRYPOINT ["node"] 26 | CMD ["dist/index.js"] 27 | -------------------------------------------------------------------------------- /server-worker-graph/src/api/port/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | 3 | export const getProcId = async (port, cxt) => { 4 | const infoLines = execSync(`netstat -tuplen`) 5 | .toString() 6 | .trim(); 7 | 8 | const info = infoLines.match(/[^\r\n]+/g); 9 | 10 | for (const ln of info) { 11 | const portExp = /0\.0\.0\.0:(\d+)/; 12 | const procIdExp = /(\d+)\/sshd/; 13 | 14 | const resPort = ln.match(portExp); 15 | const resId = ln.match(procIdExp); 16 | 17 | if (resPort && resId) { 18 | if (parseInt(resPort[1]) === port) { 19 | return parseInt(resId[1]); 20 | break; 21 | } 22 | } 23 | } 24 | 25 | return null; 26 | }; 27 | -------------------------------------------------------------------------------- /local-worker-graph/src/schema/viewer/index.js: -------------------------------------------------------------------------------- 1 | import * as WorkerSchema from "Schema/viewer/worker"; 2 | import * as WorkerModel from "Model/worker"; 3 | 4 | const schema = [ 5 | ...WorkerSchema.schema, 6 | ` 7 | type Viewer { 8 | id: ID 9 | worker: Worker 10 | } 11 | 12 | type ViewerMutations { 13 | id: ID 14 | worker: WorkerMutations 15 | } 16 | 17 | ` 18 | ]; 19 | 20 | const resolvers = { 21 | ...WorkerSchema.resolvers, 22 | Viewer: { 23 | worker: async (viewer, params, cxt) => await WorkerModel.get(cxt) 24 | }, 25 | ViewerMutations: { 26 | worker: async (viewer, params, cxt) => await WorkerModel.get(cxt) 27 | } 28 | }; 29 | 30 | export { schema, resolvers }; 31 | -------------------------------------------------------------------------------- /local-web-container/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.13.0-alpine 2 | 3 | ENV ENV_ROOT=/env 4 | RUN mkdir ${ENV_ROOT} 5 | RUN chown node ${ENV_ROOT} 6 | 7 | ENV CONTAINER=tunnel-local-web-container 8 | ENV SOURCE=tunnel-local-web 9 | 10 | ENV APP_ROOT=/env/${CONTAINER}/dist 11 | ENV APP_ROOT_SOURCE=/env/${SOURCE} 12 | ENV APP_HOME=${APP_ROOT}/node_modules/@nebulario/${SOURCE} 13 | 14 | ARG CACHEBUST=1 15 | RUN echo "CACHE $CACHEBUST" 16 | 17 | RUN mkdir -p ${APP_HOME} 18 | RUN mkdir -p ${APP_ROOT_SOURCE} 19 | COPY --chown=node:node ./dist ${APP_ROOT} 20 | RUN chown -R node ${APP_HOME} 21 | 22 | RUN ln -s ${APP_ROOT}/node_modules ${APP_HOME}/node_modules 23 | 24 | USER node 25 | 26 | WORKDIR ${APP_HOME} 27 | ENTRYPOINT ["node"] 28 | CMD ["dist/index.js"] 29 | -------------------------------------------------------------------------------- /server-web-container/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.13.0-alpine 2 | 3 | ENV ENV_ROOT=/env 4 | RUN mkdir ${ENV_ROOT} 5 | RUN chown node ${ENV_ROOT} 6 | 7 | ENV CONTAINER=tunnel-server-web-container 8 | ENV SOURCE=tunnel-server-web 9 | 10 | ENV APP_ROOT=/env/${CONTAINER}/dist 11 | ENV APP_ROOT_SOURCE=/env/${SOURCE} 12 | ENV APP_HOME=${APP_ROOT}/node_modules/@nebulario/${SOURCE} 13 | 14 | ARG CACHEBUST=1 15 | RUN echo "CACHE $CACHEBUST" 16 | 17 | RUN mkdir -p ${APP_HOME} 18 | RUN mkdir -p ${APP_ROOT_SOURCE} 19 | COPY --chown=node:node ./dist ${APP_ROOT} 20 | RUN chown -R node ${APP_HOME} 21 | 22 | RUN ln -s ${APP_ROOT}/node_modules ${APP_HOME}/node_modules 23 | 24 | USER node 25 | 26 | WORKDIR ${APP_HOME} 27 | ENTRYPOINT ["node"] 28 | CMD ["dist/index.js"] 29 | -------------------------------------------------------------------------------- /local-boot-graph/src/api/proxy/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import path from "path"; 3 | import _ from "lodash"; 4 | import fs from "fs"; 5 | import * as ComposeApi from "PKG/linker-compose"; 6 | 7 | export const start = async cxt => { 8 | const { 9 | instance: { instanceid }, 10 | paths: { proxy } 11 | } = cxt; 12 | 13 | await ComposeApi.start( 14 | { instanceid: `${instanceid}-proxy`, folder: proxy.folder }, 15 | cxt 16 | ); 17 | }; 18 | 19 | export const stop = async cxt => { 20 | const { 21 | instance: { instanceid }, 22 | paths: { proxy } 23 | } = cxt; 24 | 25 | await ComposeApi.stop( 26 | { instanceid: `${instanceid}-proxy`, folder: proxy.folder }, 27 | cxt 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /local-web/pkg/linker-network/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | 3 | export const getNameserver = () => { 4 | const hostsRegex = new RegExp(/nameserver (.+)/, "gm"); 5 | const hosts = execSync(`cat /etc/resolv.conf`).toString(); 6 | 7 | const match = hostsRegex.exec(hosts); 8 | if (!match) { 9 | return null; 10 | } 11 | 12 | return match[1]; 13 | }; 14 | 15 | export const getHostname = (host, nameserver) => { 16 | const lupRegex = new RegExp(/Address 1:\s+([^\s]+)/, "g"); 17 | const lup = execSync(`nslookup ${host} ${nameserver}`).toString(); 18 | 19 | const lups = lup.substr(lup.indexOf(host)) 20 | 21 | const match = lupRegex.exec(lups); 22 | if (!match) { 23 | return null; 24 | } 25 | 26 | return match[1]; 27 | }; 28 | -------------------------------------------------------------------------------- /server-boot-graph/src/schema/viewer/index.js: -------------------------------------------------------------------------------- 1 | import * as WorkersSchema from "Schema/viewer/workers"; 2 | import * as ServiceSchema from "Schema/viewer/service"; 3 | 4 | const schema = [ 5 | ...WorkersSchema.schema, 6 | ...ServiceSchema.schema, 7 | ` 8 | type Viewer { 9 | id: ID 10 | workers: LocalWorkerQueries 11 | } 12 | 13 | type ViewerMutations { 14 | id: ID 15 | workers: LocalWorkerMutations 16 | service: ServiceMutations 17 | } 18 | 19 | ` 20 | ]; 21 | 22 | const resolvers = { 23 | ...WorkersSchema.resolvers, 24 | ...ServiceSchema.resolvers, 25 | Viewer: { 26 | workers: viewer => viewer 27 | }, 28 | ViewerMutations: { 29 | workers: viewer => viewer, 30 | service: viewer => viewer 31 | } 32 | }; 33 | 34 | export { schema, resolvers }; 35 | -------------------------------------------------------------------------------- /server-worker-graph/src/schema/index.js: -------------------------------------------------------------------------------- 1 | import * as ViewerSchema from "Schema/viewer"; 2 | const { GraphQLDate, GraphQLDateTime } = require("graphql-iso-date"); 3 | 4 | const schema = [ 5 | ...ViewerSchema.schema, 6 | ` 7 | scalar DateTime 8 | scalar Date 9 | 10 | type Label { 11 | name: String! 12 | value: String! 13 | } 14 | 15 | type Query { 16 | viewer: Viewer 17 | } 18 | 19 | type Mutation { 20 | viewer: ViewerMutations 21 | } 22 | ` 23 | ]; 24 | 25 | const resolvers = { 26 | ...ViewerSchema.resolvers, 27 | Date: GraphQLDate, 28 | DateTime: GraphQLDateTime, 29 | Query: { 30 | viewer: async (parent, args, cxt) => ({}) 31 | }, 32 | Mutation: { 33 | viewer: async (parent, args, cxt) => ({}) 34 | } 35 | }; 36 | 37 | export { schema, resolvers }; 38 | -------------------------------------------------------------------------------- /local-worker-graph/src/schema/index.js: -------------------------------------------------------------------------------- 1 | import * as ViewerSchema from "Schema/viewer"; 2 | const { GraphQLDate, GraphQLDateTime } = require("graphql-iso-date"); 3 | 4 | const schema = [ 5 | ...ViewerSchema.schema, 6 | ` 7 | scalar DateTime 8 | scalar Date 9 | 10 | type Label { 11 | name: String! 12 | value: String! 13 | } 14 | 15 | type Query { 16 | viewer: Viewer 17 | } 18 | 19 | type Mutation { 20 | viewer: ViewerMutations 21 | } 22 | ` 23 | ]; 24 | 25 | const resolvers = { 26 | ...ViewerSchema.resolvers, 27 | Date: GraphQLDate, 28 | DateTime: GraphQLDateTime, 29 | Query: { 30 | viewer: async (parent, { token }, cxt) => ({}) 31 | }, 32 | Mutation: { 33 | viewer: async (parent, { token }, cxt) => ({}) 34 | } 35 | }; 36 | 37 | export { schema, resolvers }; 38 | -------------------------------------------------------------------------------- /local-boot-graph/src/schema/index.js: -------------------------------------------------------------------------------- 1 | import * as ViewerSchema from "Schema/viewer"; 2 | const { GraphQLDate, GraphQLDateTime } = require("graphql-iso-date"); 3 | import GraphQLToolsTypes from "graphql-tools-types"; 4 | 5 | const schema = [ 6 | ...ViewerSchema.schema, 7 | ` 8 | scalar JSON 9 | scalar DateTime 10 | scalar Date 11 | 12 | type Query { 13 | viewer: Viewer 14 | } 15 | 16 | type Mutation { 17 | viewer: ViewerMutations 18 | } 19 | ` 20 | ]; 21 | 22 | const resolvers = { 23 | ...ViewerSchema.resolvers, 24 | JSON: GraphQLToolsTypes.JSON({ name: "JSON" }), 25 | Date: GraphQLDate, 26 | DateTime: GraphQLDateTime, 27 | Query: { 28 | viewer: (parent, args, cxt) => ({}) 29 | }, 30 | Mutation: { 31 | viewer: (parent, args, cxt) => ({}) 32 | } 33 | }; 34 | 35 | export { schema, resolvers }; 36 | -------------------------------------------------------------------------------- /server-boot-graph/src/schema/index.js: -------------------------------------------------------------------------------- 1 | import * as ViewerSchema from "Schema/viewer"; 2 | const { GraphQLDate, GraphQLDateTime } = require("graphql-iso-date"); 3 | import GraphQLToolsTypes from "graphql-tools-types"; 4 | 5 | const schema = [ 6 | ...ViewerSchema.schema, 7 | ` 8 | scalar JSON 9 | scalar DateTime 10 | scalar Date 11 | 12 | type Query { 13 | viewer: Viewer 14 | } 15 | 16 | type Mutation { 17 | viewer: ViewerMutations 18 | } 19 | ` 20 | ]; 21 | 22 | const resolvers = { 23 | ...ViewerSchema.resolvers, 24 | JSON: GraphQLToolsTypes.JSON({ name: "JSON" }), 25 | Date: GraphQLDate, 26 | DateTime: GraphQLDateTime, 27 | Query: { 28 | viewer: (parent, args, cxt) => ({}) 29 | }, 30 | Mutation: { 31 | viewer: (parent, args, cxt) => ({}) 32 | } 33 | }; 34 | 35 | export { schema, resolvers }; 36 | -------------------------------------------------------------------------------- /local-worker-graph/src/model/service/index.js: -------------------------------------------------------------------------------- 1 | import { spawn } from "child-process-promise"; 2 | import { execSync } from "child_process"; 3 | import fs from "fs"; 4 | import _ from "lodash"; 5 | import * as WorkerApi from "Api/worker"; 6 | import * as Utils from "@nebulario/tunnel-utils"; 7 | 8 | export const start = async cxt => { 9 | const { 10 | services: { 11 | config: { workerid } 12 | } 13 | } = cxt; 14 | 15 | const ip = execSync("hostname -i") 16 | .toString() 17 | .trim(); 18 | 19 | cxt.logger.debug("worker.started", { workerid, ip }); 20 | await WorkerApi.init({ workerid, ip }, cxt); 21 | 22 | //await WorkerApi.update(cxt); 23 | /*(async () => { 24 | while (true) { 25 | await WorkerApi.update(cxt); 26 | await Utils.Process.wait(500); 27 | } 28 | })().catch(e => 29 | cxt.logger.error("service.loop.fatal", { error: e.toString() }) 30 | );*/ 31 | }; 32 | -------------------------------------------------------------------------------- /local-web/src/common/ui/utils/confirm.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap"; 3 | 4 | export default ({ trigger: TriggerButton, confirm: ConfirmButton, body }) => { 5 | const [isOpen, setModalState] = useState(false); 6 | const close = () => setModalState(false); 7 | const open = () => setModalState(true); 8 | 9 | return ( 10 | 11 | 12 | 13 | Confirmation... 14 | {body} 15 | 16 | {" "} 17 | 20 | 21 | 22 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /local-worker-graph/pkg/linker-folder/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import { execSync } from "child_process"; 5 | 6 | export const isDirectory = source => fs.lstatSync(source).isDirectory(); 7 | export const isFile = source => fs.lstatSync(source).isFile(); 8 | export const getDirectories = source => 9 | fs 10 | .readdirSync(source) 11 | .map(name => path.join(source, name)) 12 | .filter(isDirectory); 13 | export const getFiles = (source, ext) => { 14 | if (!fs.existsSync(source)) { 15 | return []; 16 | } 17 | 18 | return fs 19 | .readdirSync(source) 20 | .map(name => path.join(source, name)) 21 | .filter(isFile) 22 | .filter(source => source.endsWith(ext)); 23 | }; 24 | 25 | export const makePath = (pathid, cxt) => { 26 | if (fs.existsSync(pathid)) { 27 | return; 28 | } 29 | 30 | execSync(`mkdir -p ${pathid}`); 31 | }; 32 | -------------------------------------------------------------------------------- /server-worker-graph/pkg/linker-folder/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import { execSync } from "child_process"; 5 | 6 | export const isDirectory = source => fs.lstatSync(source).isDirectory(); 7 | export const isFile = source => fs.lstatSync(source).isFile(); 8 | export const getDirectories = source => 9 | fs 10 | .readdirSync(source) 11 | .map(name => path.join(source, name)) 12 | .filter(isDirectory); 13 | export const getFiles = (source, ext) => { 14 | if (!fs.existsSync(source)) { 15 | return []; 16 | } 17 | 18 | return fs 19 | .readdirSync(source) 20 | .map(name => path.join(source, name)) 21 | .filter(isFile) 22 | .filter(source => source.endsWith(ext)); 23 | }; 24 | 25 | export const makePath = (pathid, cxt) => { 26 | if (fs.existsSync(pathid)) { 27 | return; 28 | } 29 | 30 | execSync(`mkdir -p ${pathid}`); 31 | }; 32 | -------------------------------------------------------------------------------- /local-worker-graph/src/api/graph/index.js: -------------------------------------------------------------------------------- 1 | import { request } from "PKG/linker-request"; 2 | 3 | const WORKER_UPDATE = `mutation GraphWorkerUpdate ($workerid: String!) { 4 | viewer { 5 | workers { 6 | worker (workerid: $workerid) { 7 | update 8 | } 9 | } 10 | } 11 | }`; 12 | 13 | export const update = async (workerid, cxt) => { 14 | const { 15 | services: { 16 | graph: { port } 17 | } 18 | } = cxt; 19 | 20 | const url = `http://graph:${port}/graphql`; 21 | cxt.logger.debug("graph.worker.update", { 22 | url, 23 | workerid 24 | }); 25 | const res = await request( 26 | url, 27 | WORKER_UPDATE, 28 | { 29 | workerid 30 | }, 31 | {}, 32 | cxt 33 | ); 34 | 35 | const { 36 | viewer: { 37 | workers: { worker } 38 | } 39 | } = res; 40 | 41 | if (!worker) { 42 | return; 43 | } 44 | 45 | cxt.logger.debug("graph.worker.update.response", { result: worker.update }); 46 | 47 | return worker.update; 48 | }; 49 | -------------------------------------------------------------------------------- /local-boot-graph/src/model/viewer/workers/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | 5 | import * as WorkerApi from "Api/workers"; 6 | 7 | export const list = async ({}, cxt) => { 8 | return await WorkerApi.list({}, cxt); 9 | }; 10 | 11 | export const get = async (workerid, cxt) => { 12 | return await WorkerApi.get(workerid, cxt); 13 | }; 14 | 15 | export const start = async (worker, cxt) => { 16 | const { workerid } = worker; 17 | cxt.logger.debug("worker.start", { workerid }); 18 | await WorkerApi.start(worker, cxt); 19 | return worker; 20 | }; 21 | 22 | export const restart = async (worker, cxt) => { 23 | const { workerid } = worker; 24 | cxt.logger.debug("worker.restart", { workerid }); 25 | await WorkerApi.restart(worker, cxt); 26 | return worker; 27 | }; 28 | 29 | export const stop = async (worker, cxt) => { 30 | const { workerid } = worker; 31 | cxt.logger.debug("worker.stop", { workerid }); 32 | await WorkerApi.stop(worker, cxt); 33 | return worker; 34 | }; 35 | -------------------------------------------------------------------------------- /local-boot-graph/pkg/linker-dev/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import fs from "fs"; 3 | import * as JsonUtils from "PKG/linker-json"; 4 | 5 | const DEV_CONFIG_FILE = "dev.config.json"; 6 | 7 | const config = cxt => { 8 | let config = null; 9 | 10 | if (fs.existsSync(DEV_CONFIG_FILE)) { 11 | config = JsonUtils.load(DEV_CONFIG_FILE); 12 | } else { 13 | try { 14 | const configEnv = process.env["DEV_CONFIG"]; 15 | if (configEnv) { 16 | config = JSON.parse(configEnv); 17 | } 18 | } catch (e) { 19 | console.log("dev.config.error:" + e.toString()); 20 | } 21 | } 22 | 23 | if (config) { 24 | console.log("DEV_CONFIG"); 25 | console.log(JSON.stringify(config, null, 2)); 26 | } 27 | 28 | return config; 29 | }; 30 | 31 | export const init = cxt => { 32 | cxt.dev = config(cxt); 33 | }; 34 | 35 | export const get = (ptk, cxt) => { 36 | return _.get(cxt.dev, ptk, null); 37 | }; 38 | 39 | export const serialize = cxt => { 40 | if (cxt.dev) { 41 | return JSON.stringify(cxt.dev); 42 | } else { 43 | return null; 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /server-boot-graph/pkg/linker-dev/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import fs from "fs"; 3 | import * as JsonUtils from "PKG/linker-json"; 4 | 5 | const DEV_CONFIG_FILE = "dev.config.json"; 6 | 7 | const config = cxt => { 8 | let config = null; 9 | 10 | if (fs.existsSync(DEV_CONFIG_FILE)) { 11 | config = JsonUtils.load(DEV_CONFIG_FILE); 12 | } else { 13 | try { 14 | const configEnv = process.env["DEV_CONFIG"]; 15 | if (configEnv) { 16 | config = JSON.parse(configEnv); 17 | } 18 | } catch (e) { 19 | console.log("dev.config.error:" + e.toString()); 20 | } 21 | } 22 | 23 | if (config) { 24 | console.log("DEV_CONFIG"); 25 | console.log(JSON.stringify(config, null, 2)); 26 | } 27 | 28 | return config; 29 | }; 30 | 31 | export const init = cxt => { 32 | cxt.dev = config(cxt); 33 | }; 34 | 35 | export const get = (ptk, cxt) => { 36 | return _.get(cxt.dev, ptk, null); 37 | }; 38 | 39 | export const serialize = cxt => { 40 | if (cxt.dev) { 41 | return JSON.stringify(cxt.dev); 42 | } else { 43 | return null; 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /local-worker-graph/src/schema/viewer/worker/index.js: -------------------------------------------------------------------------------- 1 | import * as WorkerModel from "Model/worker"; 2 | 3 | const schema = [ 4 | ` 5 | 6 | input EndpointInput { 7 | host: String! 8 | port: Int! 9 | } 10 | 11 | input TunnelInput { 12 | tunnelid: String! 13 | src: EndpointInput! 14 | dest: EndpointInput! 15 | } 16 | 17 | type Endpoint { 18 | host: String! 19 | port: Int! 20 | } 21 | 22 | type TunnelState { 23 | status: String! 24 | } 25 | 26 | type Tunnel { 27 | tunnelid: String! 28 | src: Endpoint! 29 | dest: Endpoint! 30 | state: TunnelState! 31 | } 32 | 33 | type Worker { 34 | id: ID 35 | workerid: String! 36 | ip: String! 37 | inlets: [Tunnel]! 38 | outlets: [Tunnel]! 39 | } 40 | 41 | type WorkerMutations { 42 | id: ID 43 | set (inlets: [TunnelInput], outlets: [TunnelInput]): Worker 44 | } 45 | 46 | ` 47 | ]; 48 | 49 | const resolvers = { 50 | Worker: {}, 51 | WorkerMutations: { 52 | set: async (worker, input, cxt) => await WorkerModel.set(worker, input, cxt) 53 | } 54 | }; 55 | 56 | export { schema, resolvers }; 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Victor Jimenez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /local-web/src/common/ui/utils/query.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Query } from "react-apollo"; 3 | import LoadingIcon from "UI/utils/loading"; 4 | import { Alert } from "reactstrap"; 5 | 6 | export default ({ query, variables, children }) => ( 7 | 8 | {props => { 9 | const { loading, error, data } = props; 10 | 11 | if (loading) 12 | return ( 13 |
14 | 15 |
16 | ); 17 | if (error) { 18 | const errors = error.graphQLErrors 19 | ? error.graphQLErrors.map(error => { 20 | return error.message; 21 | }) 22 | : [error.toString()]; 23 | 24 | return ( 25 |
26 |

27 | Query error: {errors.join(",")} 28 |

29 | 30 | Please check the pivot and cluster handler troubleshooting section 31 | 32 |
33 | ); 34 | } 35 | 36 | return children(props); 37 | }} 38 |
39 | ); 40 | -------------------------------------------------------------------------------- /local-boot-graph/src/schema/viewer/index.js: -------------------------------------------------------------------------------- 1 | import * as HostsSchema from "Schema/viewer/hosts"; 2 | import * as ProxySchema from "Schema/viewer/proxy"; 3 | import * as WorkersSchema from "Schema/viewer/workers"; 4 | import * as ServiceSchema from "Schema/viewer/service"; 5 | 6 | const schema = [ 7 | ...HostsSchema.schema, 8 | ...WorkersSchema.schema, 9 | ...ProxySchema.schema, 10 | ...ServiceSchema.schema, 11 | ` 12 | type Viewer { 13 | id: ID 14 | hosts: LocalHostsQueries 15 | workers: LocalWorkerQueries 16 | } 17 | 18 | type ViewerMutations { 19 | id: ID 20 | hosts: LocalHostsMutations 21 | workers: LocalWorkerMutations 22 | proxy: LocalProxyMutations 23 | service: ServiceMutations 24 | } 25 | 26 | ` 27 | ]; 28 | 29 | const resolvers = { 30 | ...HostsSchema.resolvers, 31 | ...WorkersSchema.resolvers, 32 | ...ProxySchema.resolvers, 33 | ...ServiceSchema.resolvers, 34 | Viewer: { 35 | hosts: viewer => viewer, 36 | workers: viewer => viewer 37 | }, 38 | ViewerMutations: { 39 | hosts: viewer => viewer, 40 | workers: viewer => viewer, 41 | proxy: viewer => viewer, 42 | service: viewer => viewer 43 | } 44 | }; 45 | 46 | export { schema, resolvers }; 47 | -------------------------------------------------------------------------------- /local-boot-graph/webpack.config.js: -------------------------------------------------------------------------------- 1 | const UglifyJSPlugin = require("uglifyjs-webpack-plugin"); 2 | const path = require("path"); 3 | const nodeExternals = require("webpack-node-externals"); 4 | 5 | module.exports = (env = {}) => { 6 | 7 | const __DEV__ = env.development; 8 | const __PROD__ = env.production; 9 | const plugins = []; 10 | 11 | if (__PROD__) { 12 | plugins.push(new UglifyJSPlugin()); 13 | } 14 | 15 | return { 16 | entry: "./src/index.js", 17 | target: "node", 18 | 19 | externals: [nodeExternals()], 20 | 21 | output: { 22 | path: path.join(__dirname, "/dist"), 23 | filename: "index.js" 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.js$/, 29 | use: "babel-loader" 30 | } 31 | ] 32 | }, 33 | plugins, 34 | resolve: { 35 | alias: { 36 | Api: path.resolve(__dirname, "src/api"), 37 | Model: path.resolve(__dirname, "src/model"), 38 | Schema: path.resolve(__dirname, "src/schema"), 39 | PKG: path.resolve(__dirname, "pkg") 40 | }, 41 | modules: [path.resolve(__dirname, "src"), "node_modules"], 42 | extensions: [".js", ".jsx", ".json"] 43 | } 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /server-boot-graph/src/model/viewer/workers/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | 5 | import * as WorkerApi from "Api/workers"; 6 | 7 | export const list = async ({}, cxt) => { 8 | return await WorkerApi.list({}, cxt); 9 | }; 10 | 11 | export const get = async (workerid, cxt) => { 12 | return await WorkerApi.get(workerid, cxt); 13 | }; 14 | 15 | export const start = async (worker, cxt) => { 16 | const { workerid } = worker; 17 | cxt.logger.debug("worker.start", { workerid }); 18 | await WorkerApi.start(worker, cxt); 19 | 20 | const { ip } = await WorkerApi.info(worker, cxt); 21 | worker.ip = ip; 22 | return worker; 23 | }; 24 | 25 | export const restart = async (worker, cxt) => { 26 | const { workerid } = worker; 27 | cxt.logger.debug("worker.restart", { workerid }); 28 | await WorkerApi.restart(worker, cxt); 29 | return worker; 30 | }; 31 | 32 | export const stop = async (worker, cxt) => { 33 | const { workerid } = worker; 34 | cxt.logger.debug("worker.stop", { workerid }); 35 | await WorkerApi.stop(worker, cxt); 36 | 37 | const { ip } = await WorkerApi.info(worker, cxt); 38 | worker.ip = ip; 39 | 40 | return worker; 41 | }; 42 | -------------------------------------------------------------------------------- /local-worker-graph/webpack.config.js: -------------------------------------------------------------------------------- 1 | const UglifyJSPlugin = require("uglifyjs-webpack-plugin"); 2 | const path = require("path"); 3 | const nodeExternals = require("webpack-node-externals"); 4 | 5 | module.exports = (env = {}) => { 6 | 7 | const __DEV__ = env.development; 8 | const __PROD__ = env.production; 9 | const plugins = []; 10 | 11 | if (__PROD__) { 12 | plugins.push(new UglifyJSPlugin()); 13 | } 14 | 15 | return { 16 | entry: "./src/index.js", 17 | target: "node", 18 | 19 | externals: [nodeExternals()], 20 | 21 | output: { 22 | path: path.join(__dirname, "/dist"), 23 | filename: "index.js" 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.js$/, 29 | use: "babel-loader" 30 | } 31 | ] 32 | }, 33 | plugins, 34 | resolve: { 35 | alias: { 36 | Api: path.resolve(__dirname, "src/api"), 37 | Model: path.resolve(__dirname, "src/model"), 38 | Schema: path.resolve(__dirname, "src/schema"), 39 | PKG: path.resolve(__dirname, "pkg") 40 | }, 41 | modules: [path.resolve(__dirname, "src"), "node_modules"], 42 | extensions: [".js", ".jsx", ".json"] 43 | } 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /server-boot-graph/webpack.config.js: -------------------------------------------------------------------------------- 1 | const UglifyJSPlugin = require("uglifyjs-webpack-plugin"); 2 | const path = require("path"); 3 | const nodeExternals = require("webpack-node-externals"); 4 | 5 | module.exports = (env = {}) => { 6 | 7 | const __DEV__ = env.development; 8 | const __PROD__ = env.production; 9 | const plugins = []; 10 | 11 | if (__PROD__) { 12 | plugins.push(new UglifyJSPlugin()); 13 | } 14 | 15 | return { 16 | entry: "./src/index.js", 17 | target: "node", 18 | 19 | externals: [nodeExternals()], 20 | 21 | output: { 22 | path: path.join(__dirname, "/dist"), 23 | filename: "index.js" 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.js$/, 29 | use: "babel-loader" 30 | } 31 | ] 32 | }, 33 | plugins, 34 | resolve: { 35 | alias: { 36 | Api: path.resolve(__dirname, "src/api"), 37 | Model: path.resolve(__dirname, "src/model"), 38 | Schema: path.resolve(__dirname, "src/schema"), 39 | PKG: path.resolve(__dirname, "pkg") 40 | }, 41 | modules: [path.resolve(__dirname, "src"), "node_modules"], 42 | extensions: [".js", ".jsx", ".json"] 43 | } 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /server-worker-graph/webpack.config.js: -------------------------------------------------------------------------------- 1 | const UglifyJSPlugin = require("uglifyjs-webpack-plugin"); 2 | const path = require("path"); 3 | const nodeExternals = require("webpack-node-externals"); 4 | 5 | module.exports = (env = {}) => { 6 | 7 | const __DEV__ = env.development; 8 | const __PROD__ = env.production; 9 | const plugins = []; 10 | 11 | if (__PROD__) { 12 | plugins.push(new UglifyJSPlugin()); 13 | } 14 | 15 | return { 16 | entry: "./src/index.js", 17 | target: "node", 18 | 19 | externals: [nodeExternals()], 20 | 21 | output: { 22 | path: path.join(__dirname, "/dist"), 23 | filename: "index.js" 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.js$/, 29 | use: "babel-loader" 30 | } 31 | ] 32 | }, 33 | plugins, 34 | resolve: { 35 | alias: { 36 | Api: path.resolve(__dirname, "src/api"), 37 | Model: path.resolve(__dirname, "src/model"), 38 | Schema: path.resolve(__dirname, "src/schema"), 39 | PKG: path.resolve(__dirname, "pkg") 40 | }, 41 | modules: [path.resolve(__dirname, "src"), "node_modules"], 42 | extensions: [".js", ".jsx", ".json"] 43 | } 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /local-boot-graph/src/schema/viewer/workers/index.js: -------------------------------------------------------------------------------- 1 | import * as WorkerModel from "Model/viewer/workers"; 2 | 3 | const schema = [ 4 | ` 5 | type LocalWorker { 6 | id: ID! 7 | workerid: String! 8 | } 9 | 10 | type LocalWorkerQueries { 11 | worker(workerid: ID!): LocalWorker 12 | } 13 | 14 | type LocalWorkerMutations { 15 | worker(workerid: ID!): LocalWorkerEntityMutations! 16 | } 17 | 18 | type LocalWorkerEntityMutations { 19 | start: LocalWorker! 20 | restart: LocalWorker! 21 | stop: LocalWorker! 22 | } 23 | 24 | ` 25 | ]; 26 | 27 | const resolvers = { 28 | LocalWorker: {}, 29 | LocalWorkerQueries: { 30 | worker: async (viewer, { workerid }, cxt) => 31 | await WorkerModel.get(workerid, cxt) 32 | }, 33 | LocalWorkerMutations: { 34 | worker: async (viewer, { workerid }, cxt) => 35 | await WorkerModel.get(workerid, cxt) 36 | }, 37 | LocalWorkerEntityMutations: { 38 | start: async (worker, args, cxt) => await WorkerModel.start(worker, cxt), 39 | restart: async (worker, args, cxt) => 40 | await WorkerModel.restart(worker, cxt), 41 | stop: async (worker, args, cxt) => await WorkerModel.stop(worker, cxt) 42 | } 43 | }; 44 | 45 | export { schema, resolvers }; 46 | -------------------------------------------------------------------------------- /worker-graph-container/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.13.0-alpine 2 | 3 | RUN apk --update add --no-cache openssh curl 4 | RUN /usr/bin/ssh-keygen -A 5 | 6 | RUN chown -R node /etc/ssh/ 7 | RUN sed -i -e 's/AllowTcpForwarding.*/AllowTcpForwarding yes/' /etc/ssh/sshd_config 8 | RUN sed -i -e 's/GatewayPorts.*/GatewayPorts yes/' /etc/ssh/sshd_config 9 | RUN sed -i -e 's/#ChallengeResponseAuthentication.*/ChallengeResponseAuthentication no/' /etc/ssh/sshd_config 10 | RUN sed -i -e 's/#PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config 11 | RUN sed -i -e 's/#UsePAM.*/UsePAM no/' /etc/ssh/sshd_config 12 | RUN sed -i -e 's/#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config 13 | 14 | ENV ENV_ROOT=/env 15 | RUN mkdir ${ENV_ROOT} 16 | RUN chown node ${ENV_ROOT} 17 | 18 | ENV CONTAINER=tunnel-server-worker-graph-container 19 | ENV SOURCE=tunnel-server-worker-graph 20 | 21 | ENV APP_ROOT=/env/${CONTAINER}/dist 22 | ENV APP_HOME=${APP_ROOT}/node_modules/@nebulario/${SOURCE} 23 | 24 | ARG CACHEBUST=1 25 | RUN echo "CACHE $CACHEBUST" 26 | 27 | RUN mkdir -p ${APP_HOME} 28 | COPY --chown=node:node ./dist ${APP_ROOT} 29 | RUN chown -R node ${APP_HOME} 30 | 31 | USER node 32 | 33 | WORKDIR ${APP_HOME} 34 | ENTRYPOINT ["node"] 35 | CMD ["dist/index.js"] 36 | -------------------------------------------------------------------------------- /server-boot-graph/src/schema/viewer/workers/index.js: -------------------------------------------------------------------------------- 1 | import * as WorkerModel from "Model/viewer/workers"; 2 | 3 | const schema = [ 4 | ` 5 | type LocalWorker { 6 | id: ID! 7 | workerid: String! 8 | ip: String! 9 | } 10 | 11 | type LocalWorkerQueries { 12 | worker(workerid: ID!): LocalWorker 13 | } 14 | 15 | type LocalWorkerMutations { 16 | worker(workerid: ID!): LocalWorkerEntityMutations! 17 | } 18 | 19 | type LocalWorkerEntityMutations { 20 | start: LocalWorker! 21 | restart: LocalWorker! 22 | stop: LocalWorker! 23 | } 24 | 25 | ` 26 | ]; 27 | 28 | const resolvers = { 29 | LocalWorker: {}, 30 | LocalWorkerQueries: { 31 | worker: async (viewer, { workerid }, cxt) => 32 | await WorkerModel.get(workerid, cxt) 33 | }, 34 | LocalWorkerMutations: { 35 | worker: async (viewer, { workerid }, cxt) => 36 | await WorkerModel.get(workerid, cxt) 37 | }, 38 | LocalWorkerEntityMutations: { 39 | start: async (worker, args, cxt) => await WorkerModel.start(worker, cxt), 40 | restart: async (worker, args, cxt) => 41 | await WorkerModel.restart(worker, cxt), 42 | stop: async (worker, args, cxt) => await WorkerModel.stop(worker, cxt) 43 | } 44 | }; 45 | 46 | export { schema, resolvers }; 47 | -------------------------------------------------------------------------------- /local-web/src/common/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Root from "Root"; 3 | import { NavItem, NavLink } from "reactstrap"; 4 | import { Query } from "react-apollo"; 5 | import * as Viewer from "Queries/viewer"; 6 | import { Link } from "react-router-dom"; 7 | 8 | import { Layout } from "@nebulario/tunnel-layout"; 9 | 10 | export const HomeLink = ({ viewer }) => ( 11 | 12 | 13 | Home 14 | 15 | 16 | ); 17 | 18 | const App = () => { 19 | return ( 20 | 21 | {({ loading, error, data }) => { 22 | if (loading) return

Loading...

; 23 | if (error) return

Error: {error}

; 24 | 25 | const { viewer } = data; 26 | 27 | return ( 28 | 31 | Tunnels 32 | 33 | } 34 | viewer={viewer} 35 | left={[]} 36 | right={[]} 37 | > 38 | 39 | 40 | ); 41 | }} 42 |
43 | ); 44 | }; 45 | 46 | export default App; 47 | -------------------------------------------------------------------------------- /local-boot-graph/pkg/linker-request/index.js: -------------------------------------------------------------------------------- 1 | import { request as graphRequest } from "graphql-request"; 2 | import * as Utils from "@nebulario/tunnel-utils"; 3 | //mport uuidv4 from 'uuid/v4' 4 | 5 | export const request = async (url, query, variables, opts, cxt) => { 6 | //message: "request to http://localhost:9000/backend/graphql failed, reason: connect ECONNREFUSED 127.0.0.1:9000" 7 | //const requestid = uuidv4(); 8 | let i = 0; 9 | let latestError = null; 10 | while (i++ < 3) { 11 | try { 12 | cxt.logger.debug("graphql.request", { 13 | url, 14 | query, 15 | variables 16 | }); 17 | 18 | const res = await graphRequest(url, query, variables); 19 | return res; 20 | } catch (e) { 21 | latestError = e; 22 | const error = e.toString(); 23 | cxt.logger.error("graphql.request.error", { 24 | url, 25 | query, 26 | variables, 27 | error 28 | }); 29 | 30 | const retryError = error.includes("ECONNREFUSED"); 31 | 32 | if (retryError) { 33 | cxt.logger.error("graphql.request.retry", { 34 | query, 35 | attempt: i 36 | }); 37 | await Utils.Process.wait(2500); 38 | } else { 39 | throw e; 40 | } 41 | } 42 | } 43 | 44 | throw latestError; 45 | }; 46 | -------------------------------------------------------------------------------- /server-boot-graph/pkg/linker-request/index.js: -------------------------------------------------------------------------------- 1 | import { request as graphRequest } from "graphql-request"; 2 | import * as Utils from "@nebulario/tunnel-utils"; 3 | //mport uuidv4 from 'uuid/v4' 4 | 5 | export const request = async (url, query, variables, opts, cxt) => { 6 | //message: "request to http://localhost:9000/backend/graphql failed, reason: connect ECONNREFUSED 127.0.0.1:9000" 7 | //const requestid = uuidv4(); 8 | let i = 0; 9 | let latestError = null; 10 | while (i++ < 3) { 11 | try { 12 | cxt.logger.debug("graphql.request", { 13 | url, 14 | query, 15 | variables 16 | }); 17 | 18 | const res = await graphRequest(url, query, variables); 19 | return res; 20 | } catch (e) { 21 | latestError = e; 22 | const error = e.toString(); 23 | cxt.logger.error("graphql.request.error", { 24 | url, 25 | query, 26 | variables, 27 | error 28 | }); 29 | 30 | const retryError = error.includes("ECONNREFUSED"); 31 | 32 | if (retryError) { 33 | cxt.logger.error("graphql.request.retry", { 34 | query, 35 | attempt: i 36 | }); 37 | await Utils.Process.wait(500); 38 | } else { 39 | throw e; 40 | } 41 | } 42 | } 43 | 44 | throw latestError; 45 | }; 46 | -------------------------------------------------------------------------------- /local-worker-graph/pkg/linker-request/index.js: -------------------------------------------------------------------------------- 1 | import { request as graphRequest } from "graphql-request"; 2 | import * as Utils from "@nebulario/tunnel-utils"; 3 | //mport uuidv4 from 'uuid/v4' 4 | 5 | export const request = async (url, query, variables, opts, cxt) => { 6 | //message: "request to http://localhost:9000/backend/graphql failed, reason: connect ECONNREFUSED 127.0.0.1:9000" 7 | //const requestid = uuidv4(); 8 | let i = 0; 9 | let latestError = null; 10 | while (i++ < 5) { 11 | try { 12 | cxt.logger.debug("graphql.request", { 13 | url, 14 | query, 15 | variables 16 | }); 17 | 18 | const res = await graphRequest(url, query, variables); 19 | return res; 20 | } catch (e) { 21 | latestError = e; 22 | const error = e.toString(); 23 | cxt.logger.error("graphql.request.error", { 24 | url, 25 | query, 26 | variables, 27 | error 28 | }); 29 | 30 | const retryError = error.includes("ECONNREFUSED"); 31 | 32 | if (retryError) { 33 | cxt.logger.error("graphql.request.retry", { 34 | query, 35 | attempt: i 36 | }); 37 | await Utils.Process.wait(2500); 38 | } else { 39 | throw e; 40 | } 41 | } 42 | } 43 | 44 | throw latestError; 45 | }; 46 | -------------------------------------------------------------------------------- /local-worker-graph/src/api/server/index.js: -------------------------------------------------------------------------------- 1 | 2 | import { request } from "PKG/linker-request"; 3 | 4 | const DEVICE_OUTLET_RESET = `mutation ServerOutletReset ($deviceid: String!, $outletid: String! ) { 5 | viewer { 6 | devices { 7 | device(deviceid: $deviceid) { 8 | outlets { 9 | outlet(outletid: $outletid) { 10 | state { 11 | reset { 12 | id 13 | deviceid 14 | } 15 | } 16 | } 17 | } 18 | } 19 | } 20 | } 21 | } 22 | `; 23 | 24 | export const reset = async ({ deviceid, outletid }, cxt) => { 25 | const port = 9000; 26 | 27 | const url = `http://graph:${port}/graphql`; 28 | cxt.logger.debug("graph.device.reset", { 29 | url, 30 | deviceid, 31 | outletid 32 | }); 33 | const res = await request( 34 | url, 35 | DEVICE_OUTLET_RESET, 36 | { 37 | deviceid, 38 | outletid 39 | }, 40 | {}, 41 | cxt 42 | ); 43 | 44 | const { 45 | viewer: { 46 | devices: { 47 | device: { 48 | outlets: { 49 | outlet: { 50 | state: { reset } 51 | } 52 | } 53 | } 54 | } 55 | } 56 | } = res; 57 | 58 | cxt.logger.debug("graph.device.reset", { 59 | deviceid: reset.deviceid, 60 | outletid 61 | }); 62 | 63 | return reset; 64 | }; 65 | -------------------------------------------------------------------------------- /local-web/webpack.config.web.js: -------------------------------------------------------------------------------- 1 | const UglifyJSPlugin = require("uglifyjs-webpack-plugin"); 2 | const path = require("path"); 3 | const { WatchRunPlugin } = require("./webpack.config.common"); 4 | 5 | module.exports = (env = {}) => { 6 | const __DEV__ = env.development; 7 | const __PROD__ = env.production; 8 | const plugins = [new WatchRunPlugin("web")]; 9 | 10 | if (__PROD__) { 11 | plugins.push(new UglifyJSPlugin()); 12 | } 13 | 14 | return { 15 | entry: "./src/web/index.js", 16 | output: { 17 | path: path.join(__dirname, "/dist/web"), 18 | filename: "index.js" 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.js$/, 24 | use: "babel-loader" 25 | } 26 | ] 27 | }, 28 | plugins, 29 | resolve: { 30 | alias: { 31 | Comps: path.resolve(__dirname, "src/common/comps"), 32 | Root: path.resolve(__dirname, "src/common/root"), 33 | Actions: path.resolve(__dirname, "src/common/actions"), 34 | UI: path.resolve(__dirname, "src/common/ui"), 35 | PKG: path.resolve(__dirname, "pkg"), 36 | Queries: path.resolve(__dirname, "src/common/queries"), 37 | Fragments: path.resolve(__dirname, "src/common/fragments") 38 | }, 39 | modules: [path.resolve(__dirname, "src"), "node_modules"], 40 | extensions: [".js", ".jsx", ".json"] 41 | } 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /local-web/src/common/actions/service/update.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import _ from "lodash"; 3 | import { Button } from "reactstrap"; 4 | import { useMutation } from "@apollo/react-hooks"; 5 | import { gql } from "apollo-boost"; 6 | import Loading from "UI/utils/loading"; 7 | import { ServiceFragment } from "Fragments/service"; 8 | import * as Mutation from "UI/utils/mutation"; 9 | 10 | const mutation = gql` 11 | mutation ServiceUpdate { 12 | viewer { 13 | service { 14 | update { 15 | id 16 | service { 17 | ...ServiceFragment 18 | } 19 | } 20 | } 21 | } 22 | } 23 | ${ServiceFragment} 24 | `; 25 | 26 | export default () => { 27 | const [ 28 | upgrade, 29 | { loading: mutationLoading, error: mutationError } 30 | ] = useMutation(mutation, { 31 | refetchQueries: () => [], 32 | onCompleted: ({}) => {}, 33 | onError: Mutation.onError 34 | }); 35 | 36 | const busy = mutationLoading; 37 | 38 | return ( 39 | 56 | ); 57 | }; 58 | -------------------------------------------------------------------------------- /local-boot-graph/src/schema/viewer/hosts/index.js: -------------------------------------------------------------------------------- 1 | import * as HostsModel from "Model/viewer/hosts"; 2 | 3 | const schema = [ 4 | ` 5 | 6 | type LocalHostsEntry { 7 | id: ID! 8 | host: String! 9 | ip: String! 10 | managed: Boolean! 11 | } 12 | 13 | type LocalHostsStatus { 14 | readable: Boolean 15 | writable: Boolean 16 | } 17 | 18 | 19 | type LocalHostsQueries { 20 | status: LocalHostsStatus! 21 | entry(host: String!): LocalHostsEntry 22 | list: [LocalHostsEntry] 23 | } 24 | 25 | type LocalHostsMutations { 26 | add(host: String!, ip: String!): LocalHostsEntry 27 | entry(host: String!): LocalHostsEntryMutations 28 | } 29 | 30 | type LocalHostsEntryMutations { 31 | remove: Boolean! 32 | } 33 | 34 | ` 35 | ]; 36 | 37 | const resolvers = { 38 | LocalHostsQueries: { 39 | status: async (viewer, args, cxt) => await HostsModel.status(cxt), 40 | entry: async (viewer, { host }, cxt) => await HostsModel.get(host, cxt), 41 | list: async (viewer, args, cxt) => await HostsModel.list(args, cxt) 42 | }, 43 | LocalHostsMutations: { 44 | add: async (viewer, args, cxt) => await HostsModel.add(args, cxt), 45 | entry: async (viewer, { host }, cxt) => await HostsModel.get(host, cxt) 46 | }, 47 | LocalHostsEntryMutations: { 48 | remove: async (entry, args, cxt) => await HostsModel.remove(entry, cxt) 49 | } 50 | }; 51 | 52 | export { schema, resolvers }; 53 | -------------------------------------------------------------------------------- /local-web/webpack.config.server.js: -------------------------------------------------------------------------------- 1 | const UglifyJSPlugin = require("uglifyjs-webpack-plugin"); 2 | const path = require("path"); 3 | const { WatchRunPlugin } = require("./webpack.config.common"); 4 | const nodeExternals = require("webpack-node-externals"); 5 | 6 | module.exports = (env = {}) => { 7 | const __DEV__ = env.development; 8 | const __PROD__ = env.production; 9 | const plugins = [new WatchRunPlugin("server")]; 10 | 11 | if (__PROD__) { 12 | plugins.push(new UglifyJSPlugin()); 13 | } 14 | 15 | return { 16 | entry: "./src/server/index.js", 17 | target: "node", 18 | externals: [nodeExternals()], 19 | 20 | output: { 21 | path: path.join(__dirname, "/dist"), 22 | filename: "index.js" 23 | }, 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.js$/, 28 | use: "babel-loader" 29 | } 30 | ] 31 | }, 32 | plugins, 33 | resolve: { 34 | alias: { 35 | Comps: path.resolve(__dirname, "src/common/comps"), 36 | Root: path.resolve(__dirname, "src/common/root"), 37 | Actions: path.resolve(__dirname, "src/common/actions"), 38 | UI: path.resolve(__dirname, "src/common/ui"), 39 | PKG: path.resolve(__dirname, "pkg"), 40 | Queries: path.resolve(__dirname, "src/common/queries"), 41 | Fragments: path.resolve(__dirname, "src/common/fragments") 42 | }, 43 | modules: [path.resolve(__dirname, "src"), "node_modules"], 44 | extensions: [".js", ".jsx", ".json"] 45 | } 46 | }; 47 | }; 48 | -------------------------------------------------------------------------------- /local-web/src/common/actions/device/service/update.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import _ from "lodash"; 3 | import { Button } from "reactstrap"; 4 | import { useMutation } from "@apollo/react-hooks"; 5 | import { gql } from "apollo-boost"; 6 | import Loading from "UI/utils/loading"; 7 | import { ServiceFragment } from "Fragments/service"; 8 | import * as Mutation from "UI/utils/mutation"; 9 | import { DeviceFragment } from "Fragments/device"; 10 | 11 | const mutation = gql` 12 | mutation DeviceServiceUpdate($deviceid: String!) { 13 | viewer { 14 | devices { 15 | device(deviceid: $deviceid) { 16 | service { 17 | update { 18 | ...DeviceFragment 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | ${DeviceFragment} 26 | `; 27 | 28 | export default ({ device: { deviceid } }) => { 29 | const [ 30 | upgrade, 31 | { loading: mutationLoading, error: mutationError } 32 | ] = useMutation(mutation, { 33 | refetchQueries: () => [], 34 | onCompleted: ({}) => {}, 35 | onError: Mutation.onError 36 | }); 37 | 38 | const busy = mutationLoading; 39 | 40 | return ( 41 | 58 | ); 59 | }; 60 | -------------------------------------------------------------------------------- /local-boot-graph/pkg/linker-folder/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import { execSync } from "child_process"; 5 | import os from "os"; 6 | 7 | export const isDirectory = source => fs.lstatSync(source).isDirectory(); 8 | export const isFile = source => fs.lstatSync(source).isFile(); 9 | export const getDirectories = source => 10 | fs 11 | .readdirSync(source) 12 | .map(name => path.join(source, name)) 13 | .filter(isDirectory); 14 | export const getFiles = (source, ext) => { 15 | if (!fs.existsSync(source)) { 16 | return []; 17 | } 18 | 19 | return fs 20 | .readdirSync(source) 21 | .map(name => path.join(source, name)) 22 | .filter(isFile) 23 | .filter(source => source.endsWith(ext)); 24 | }; 25 | 26 | export const makePath = (pathid, cxt) => { 27 | if (fs.existsSync(pathid)) { 28 | return; 29 | } 30 | 31 | execSync(`mkdir -p ${pathid}`); 32 | }; 33 | 34 | export function resolveTilde(filePath) { 35 | if (!filePath || typeof filePath !== "string") { 36 | return ""; 37 | } 38 | // '~/folder/path' or '~' 39 | if (filePath[0] === "~" && (filePath[1] === "/" || filePath.length === 1)) { 40 | return filePath.replace("~", os.homedir()); 41 | } 42 | return filePath; 43 | } 44 | 45 | export function resolveCurrent(filePath) { 46 | if (!filePath || typeof filePath !== "string") { 47 | return ""; 48 | } 49 | // '~/folder/path' or '~' 50 | if (filePath[0] === "." && (filePath[1] === "/" || filePath.length === 1)) { 51 | return filePath.replace(".", process.cwd()); 52 | } 53 | return filePath; 54 | } 55 | -------------------------------------------------------------------------------- /server-boot-graph/pkg/linker-folder/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import { execSync } from "child_process"; 5 | import os from "os"; 6 | 7 | export const isDirectory = source => fs.lstatSync(source).isDirectory(); 8 | export const isFile = source => fs.lstatSync(source).isFile(); 9 | export const getDirectories = source => 10 | fs 11 | .readdirSync(source) 12 | .map(name => path.join(source, name)) 13 | .filter(isDirectory); 14 | export const getFiles = (source, ext) => { 15 | if (!fs.existsSync(source)) { 16 | return []; 17 | } 18 | 19 | return fs 20 | .readdirSync(source) 21 | .map(name => path.join(source, name)) 22 | .filter(isFile) 23 | .filter(source => source.endsWith(ext)); 24 | }; 25 | 26 | export const makePath = (pathid, cxt) => { 27 | if (fs.existsSync(pathid)) { 28 | return; 29 | } 30 | 31 | execSync(`mkdir -p ${pathid}`); 32 | }; 33 | 34 | export function resolveTilde(filePath) { 35 | if (!filePath || typeof filePath !== "string") { 36 | return ""; 37 | } 38 | // '~/folder/path' or '~' 39 | if (filePath[0] === "~" && (filePath[1] === "/" || filePath.length === 1)) { 40 | return filePath.replace("~", os.homedir()); 41 | } 42 | return filePath; 43 | } 44 | 45 | export function resolveCurrent(filePath) { 46 | if (!filePath || typeof filePath !== "string") { 47 | return ""; 48 | } 49 | // '~/folder/path' or '~' 50 | if (filePath[0] === "." && (filePath[1] === "/" || filePath.length === 1)) { 51 | return filePath.replace(".", process.cwd()); 52 | } 53 | return filePath; 54 | } 55 | -------------------------------------------------------------------------------- /local-boot-graph/src/api/workers/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import path from "path"; 3 | import _ from "lodash"; 4 | import fs from "fs"; 5 | import * as FolderUtils from "PKG/linker-folder"; 6 | import * as ComposeApi from "PKG/linker-compose"; 7 | 8 | export const list = async ({}, cxt) => { 9 | const { 10 | paths: { 11 | workers: { folder } 12 | } 13 | } = cxt; 14 | 15 | const workersList = FolderUtils.getDirectories(folder); 16 | 17 | return _.map(workersList, folder => { 18 | const workerid = path.basename(folder); 19 | return { 20 | id: workerid, 21 | workerid, 22 | folder 23 | }; 24 | }); 25 | }; 26 | 27 | export const get = async (workerid, cxt) => { 28 | const workers = await list({}, cxt); 29 | const worker = _.find(workers, { workerid }) || null; 30 | return worker; 31 | }; 32 | 33 | export const start = async ({ workerid, folder }, cxt) => { 34 | const { 35 | instance: { instanceid } 36 | } = cxt; 37 | 38 | await ComposeApi.start( 39 | { instanceid: `${instanceid}-${workerid}`, folder }, 40 | cxt 41 | ); 42 | }; 43 | 44 | export const restart = async ({ workerid, folder }, cxt) => { 45 | const { 46 | instance: { instanceid } 47 | } = cxt; 48 | 49 | await ComposeApi.restart( 50 | { instanceid: `${instanceid}-${workerid}`, folder }, 51 | cxt 52 | ); 53 | }; 54 | 55 | export const stop = async ({ workerid, folder }, cxt) => { 56 | const { 57 | instance: { instanceid } 58 | } = cxt; 59 | 60 | await ComposeApi.stop( 61 | { instanceid: `${instanceid}-${workerid}`, folder }, 62 | cxt 63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /local-web/src/common/ui/utils/info.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Route, NavLink, Switch, Link } from "react-router-dom"; 3 | import { 4 | Alert as ReactAlert, 5 | Card as ReactCard, 6 | ButtonGroup, 7 | Button, 8 | CardHeader, 9 | CardFooter, 10 | CardBody, 11 | CardTitle, 12 | CardText 13 | } from "reactstrap"; 14 | import { 15 | Modal as ReactModal, 16 | ModalHeader, 17 | ModalBody, 18 | ModalFooter 19 | } from "reactstrap"; 20 | 21 | export const Icon = () => ; 22 | 23 | export const Modal = ({ text, title, children, icon, iconColor="link" }) => { 24 | const [isOpen, setModalState] = useState(false); 25 | const close = () => setModalState(false); 26 | const open = () => setModalState(true); 27 | 28 | return ( 29 | 30 | {text} 31 | 32 | 41 | 42 | 43 | {title} 44 | {children} 45 | 46 | 54 | 55 | 56 | 57 | ); 58 | }; 59 | 60 | export const Alert = ({ children, color="info" }) => ( 61 | 62 | {children} 63 | 64 | ); 65 | -------------------------------------------------------------------------------- /local-web/src/common/actions/device/inlet/state/stop.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import _ from "lodash"; 3 | import { Button } from "reactstrap"; 4 | import { useMutation } from "@apollo/react-hooks"; 5 | import { gql } from "apollo-boost"; 6 | import Loading from "UI/utils/loading"; 7 | import { DeviceFragment } from "Fragments/device"; 8 | import * as Mutation from "UI/utils/mutation"; 9 | 10 | const mutation = gql` 11 | mutation DeviceOutletStop($deviceid: String!, $inletid: String!) { 12 | viewer { 13 | devices { 14 | device(deviceid: $deviceid) { 15 | inlets { 16 | inlet(inletid: $inletid) { 17 | state { 18 | stop { 19 | ...DeviceFragment 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | } 28 | ${DeviceFragment} 29 | `; 30 | 31 | export default ({ device, inlet }) => { 32 | const [ 33 | stop, 34 | { loading: mutationLoading, error: mutationError } 35 | ] = useMutation(mutation, { 36 | refetchQueries: () => [], 37 | onCompleted: ({}) => {}, 38 | onError: Mutation.onError 39 | }); 40 | 41 | const busy = mutationLoading || inlet.state.status !== "active"; 42 | 43 | return ( 44 | 63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /local-web/src/common/actions/device/outlet/state/stop.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import _ from "lodash"; 3 | import { Button } from "reactstrap"; 4 | import { useMutation } from "@apollo/react-hooks"; 5 | import { gql } from "apollo-boost"; 6 | import Loading from "UI/utils/loading"; 7 | import { DeviceFragment } from "Fragments/device"; 8 | import * as Mutation from "UI/utils/mutation"; 9 | 10 | const mutation = gql` 11 | mutation DeviceOutletStop($deviceid: String!, $outletid: String!) { 12 | viewer { 13 | devices { 14 | device(deviceid: $deviceid) { 15 | outlets { 16 | outlet(outletid: $outletid) { 17 | state { 18 | stop { 19 | ...DeviceFragment 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | } 28 | ${DeviceFragment} 29 | `; 30 | 31 | export default ({ device, outlet }) => { 32 | const [ 33 | stop, 34 | { loading: mutationLoading, error: mutationError } 35 | ] = useMutation(mutation, { 36 | refetchQueries: () => [], 37 | onCompleted: ({}) => {}, 38 | onError: Mutation.onError 39 | }); 40 | 41 | const busy = mutationLoading || outlet.state.status !== "active"; 42 | return ( 43 | 62 | ); 63 | }; 64 | -------------------------------------------------------------------------------- /local-web/src/common/actions/device/inlet/state/start.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import _ from "lodash"; 3 | import { Button } from "reactstrap"; 4 | import { useMutation } from "@apollo/react-hooks"; 5 | import { gql } from "apollo-boost"; 6 | import Loading from "UI/utils/loading"; 7 | import { DeviceFragment } from "Fragments/device"; 8 | import * as Mutation from "UI/utils/mutation"; 9 | 10 | const mutation = gql` 11 | mutation DeviceOutletStart($deviceid: String!, $inletid: String!) { 12 | viewer { 13 | devices { 14 | device(deviceid: $deviceid) { 15 | inlets { 16 | inlet(inletid: $inletid) { 17 | state { 18 | start { 19 | ...DeviceFragment 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | } 28 | ${DeviceFragment} 29 | `; 30 | 31 | export default ({ device, inlet }) => { 32 | const [ 33 | start, 34 | { loading: mutationLoading, error: mutationError } 35 | ] = useMutation(mutation, { 36 | refetchQueries: () => [], 37 | onCompleted: ({}) => {}, 38 | onError: Mutation.onError 39 | }); 40 | 41 | const busy = mutationLoading || inlet.state.status !== "inactive"; 42 | 43 | return ( 44 | 63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /local-worker-graph/pkg/linker-cache/index.js: -------------------------------------------------------------------------------- 1 | import * as Utils from "@nebulario/tunnel-utils"; 2 | 3 | const CACHE_INDEX = {}; 4 | 5 | export const get = async (cacheid, valueFn, opts, cxt) => { 6 | const timestamp = Math.floor(Date.now() / 1000); 7 | const current = CACHE_INDEX[cacheid]; 8 | 9 | if (current) { 10 | if (current.opts.expire) { 11 | if (timestamp > current.timestamp + current.opts.expire) { 12 | await clear(cacheid, cxt); 13 | } 14 | } 15 | } 16 | 17 | if (!CACHE_INDEX[cacheid]) { 18 | CACHE_INDEX[cacheid] = { 19 | processing: true, 20 | timestamp, 21 | opts 22 | }; 23 | 24 | try { 25 | CACHE_INDEX[cacheid].value = await valueFn(cxt); 26 | CACHE_INDEX[cacheid].processing = false; 27 | } catch (e) { 28 | delete CACHE_INDEX[cacheid]; 29 | cxt.logger.error("cache.process.error", { cacheid }); 30 | throw e; 31 | } 32 | } else { 33 | await wait(cacheid, cxt); 34 | } 35 | 36 | return CACHE_INDEX[cacheid].value; 37 | }; 38 | 39 | export const clear = async (cacheid, cxt) => { 40 | delete CACHE_INDEX[cacheid]; 41 | }; 42 | 43 | export const set = async (cacheid, value, opts, cxt) => { 44 | const timestamp = Math.floor(Date.now() / 1000); 45 | 46 | if (CACHE_INDEX[cacheid] && CACHE_INDEX[cacheid].processing) { 47 | await wait(cacheid, cxt); 48 | } else { 49 | CACHE_INDEX[cacheid] = { value, timestamp, opts }; 50 | } 51 | }; 52 | 53 | const wait = async (cacheid, cxt) => { 54 | while (CACHE_INDEX[cacheid] && CACHE_INDEX[cacheid].processing) { 55 | await Utils.Process.wait(10); 56 | } 57 | if (!CACHE_INDEX[cacheid]) { 58 | cxt.logger.error("cache.wait.error", { cacheid }); 59 | throw new Error("cache.wait.error"); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /server-worker-graph/pkg/linker-cache/index.js: -------------------------------------------------------------------------------- 1 | import * as Utils from "@nebulario/tunnel-utils"; 2 | 3 | const CACHE_INDEX = {}; 4 | 5 | export const get = async (cacheid, valueFn, opts, cxt) => { 6 | const timestamp = Math.floor(Date.now() / 1000); 7 | const current = CACHE_INDEX[cacheid]; 8 | 9 | if (current) { 10 | if (current.opts.expire) { 11 | if (timestamp > current.timestamp + current.opts.expire) { 12 | await clear(cacheid, cxt); 13 | } 14 | } 15 | } 16 | 17 | if (!CACHE_INDEX[cacheid]) { 18 | CACHE_INDEX[cacheid] = { 19 | processing: true, 20 | timestamp, 21 | opts 22 | }; 23 | 24 | try { 25 | CACHE_INDEX[cacheid].value = await valueFn(cxt); 26 | CACHE_INDEX[cacheid].processing = false; 27 | } catch (e) { 28 | delete CACHE_INDEX[cacheid]; 29 | cxt.logger.error("cache.process.error", { cacheid }); 30 | throw e; 31 | } 32 | } else { 33 | await wait(cacheid, cxt); 34 | } 35 | 36 | return CACHE_INDEX[cacheid].value; 37 | }; 38 | 39 | export const clear = async (cacheid, cxt) => { 40 | delete CACHE_INDEX[cacheid]; 41 | }; 42 | 43 | export const set = async (cacheid, value, opts, cxt) => { 44 | const timestamp = Math.floor(Date.now() / 1000); 45 | 46 | if (CACHE_INDEX[cacheid] && CACHE_INDEX[cacheid].processing) { 47 | await wait(cacheid, cxt); 48 | } else { 49 | CACHE_INDEX[cacheid] = { value, timestamp, opts }; 50 | } 51 | }; 52 | 53 | const wait = async (cacheid, cxt) => { 54 | while (CACHE_INDEX[cacheid] && CACHE_INDEX[cacheid].processing) { 55 | await Utils.Process.wait(10); 56 | } 57 | if (!CACHE_INDEX[cacheid]) { 58 | cxt.logger.error("cache.wait.error", { cacheid }); 59 | throw new Error("cache.wait.error"); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /local-web/src/common/actions/device/outlet/state/start.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import _ from "lodash"; 3 | import { Button } from "reactstrap"; 4 | import { useMutation } from "@apollo/react-hooks"; 5 | import { gql } from "apollo-boost"; 6 | import Loading from "UI/utils/loading"; 7 | import { DeviceFragment } from "Fragments/device"; 8 | import * as Mutation from "UI/utils/mutation"; 9 | 10 | const mutation = gql` 11 | mutation DeviceOutletStart($deviceid: String!, $outletid: String!) { 12 | viewer { 13 | devices { 14 | device(deviceid: $deviceid) { 15 | outlets { 16 | outlet(outletid: $outletid) { 17 | state { 18 | start { 19 | ...DeviceFragment 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | } 28 | ${DeviceFragment} 29 | `; 30 | 31 | export default ({ device, outlet }) => { 32 | const [ 33 | start, 34 | { loading: mutationLoading, error: mutationError } 35 | ] = useMutation(mutation, { 36 | refetchQueries: () => [], 37 | onCompleted: ({}) => {}, 38 | onError: Mutation.onError 39 | }); 40 | 41 | const busy = mutationLoading || outlet.state.status !== "inactive"; 42 | 43 | return ( 44 | 63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /server-worker-graph/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nebulario/tunnel-server-worker-graph", 3 | "version": "1.70.3-initial-server-prod", 4 | "description": "GraphQL for cluster handler", 5 | "main": "dist/index.js", 6 | "repository": "github.com:vicjicaman/tunnel-server-worker-graph.git", 7 | "author": "Victor Jimenez ", 8 | "license": "SEE LICENSE IN LICENSE", 9 | "dependencies": { 10 | "@babel/runtime": "^7.6.0", 11 | "@babel/runtime-corejs3": "^7.6.0", 12 | "@nebulario/tunnel-logger": "1.70.0-master", 13 | "@nebulario/tunnel-utils": "1.70.0-master", 14 | "axios": "^0.19.0", 15 | "child-process-promise": "^2.2.1", 16 | "express": "^4.16.4", 17 | "express-graphql": "^0.7.1", 18 | "graphql": "^14.2.0", 19 | "graphql-iso-date": "^3.6.1", 20 | "graphql-tools": "^4.0.4", 21 | "graphql-tools-types": "^1.2.1", 22 | "jsonwebtoken": "^8.5.1", 23 | "lodash": "^4.17.15", 24 | "tree-kill": "^1.2.1", 25 | "yamljs": "^0.3.0" 26 | }, 27 | "scripts": { 28 | "clean": "rm -Rf ./dist*", 29 | "start:prod": "NODE_ENV=\"production\" node ./dist/index.js", 30 | "start:dev": "NODE_ENV=\"development\" nodemon ./dist/index.js", 31 | "build:prod": "yarn clean && webpack --config webpack.config.js --mode=production ", 32 | "build:dev": "yarn clean && webpack --config webpack.config.js --mode=development ", 33 | "build:watch:dev": "yarn build:dev --watch", 34 | "build:watch:prod": "yarn build:prod --watch" 35 | }, 36 | "devDependencies": { 37 | "@babel/core": "^7.6.0", 38 | "@babel/plugin-transform-runtime": "^7.6.0", 39 | "@babel/preset-env": "^7.6.0", 40 | "babel-loader": "^8.0.6", 41 | "nodemon": "^1.19.2", 42 | "uglifyjs-webpack-plugin": "^1.1.2", 43 | "webpack": "^4.39.3", 44 | "webpack-cli": "^3.3.8", 45 | "webpack-node-externals": "^1.7.2" 46 | } 47 | } -------------------------------------------------------------------------------- /server-boot-graph/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nebulario/tunnel-server-boot-graph", 3 | "version": "1.70.13-initial-server-prod", 4 | "description": "GraphQL for local bootstrap", 5 | "main": "dist/index.js", 6 | "repository": "github.com:vicjicaman/tunnel-server-boot-graph.git", 7 | "author": "Victor Jimenez ", 8 | "license": "SEE LICENSE IN LICENSE", 9 | "dependencies": { 10 | "@babel/runtime": "^7.6.0", 11 | "@babel/runtime-corejs3": "^7.6.0", 12 | "@nebulario/tunnel-logger": "1.70.0-master", 13 | "@nebulario/tunnel-utils": "1.70.0-master", 14 | "axios": "^0.19.0", 15 | "child-process-promise": "^2.2.1", 16 | "dotenv": "^8.2.0", 17 | "express": "^4.16.4", 18 | "express-graphql": "^0.7.1", 19 | "graphql": "^14.2.0", 20 | "graphql-iso-date": "^3.6.1", 21 | "graphql-tools": "^4.0.4", 22 | "graphql-tools-types": "^1.2.1", 23 | "lodash": "^4.17.15", 24 | "tree-kill": "^1.2.1", 25 | "uuid": "^3.3.3", 26 | "yamljs": "^0.3.0" 27 | }, 28 | "scripts": { 29 | "clean": "rm -Rf ./dist*", 30 | "start:prod": "NODE_ENV=\"production\" node ./dist/index.js", 31 | "start:dev": "NODE_ENV=\"development\" nodemon ./dist/index.js", 32 | "build:prod": "yarn clean && webpack --config webpack.config.js --mode=production ", 33 | "build:dev": "yarn clean && webpack --config webpack.config.js --mode=development ", 34 | "build:watch:dev": "yarn build:dev --watch", 35 | "build:watch:prod": "yarn build:prod --watch" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.6.0", 39 | "@babel/plugin-transform-runtime": "^7.6.0", 40 | "@babel/preset-env": "^7.6.0", 41 | "babel-loader": "^8.0.6", 42 | "nodemon": "^1.19.2", 43 | "webpack": "^4.39.3", 44 | "webpack-cli": "^3.3.8", 45 | "webpack-node-externals": "^1.7.2", 46 | "uglifyjs-webpack-plugin": "^1.1.2" 47 | } 48 | } -------------------------------------------------------------------------------- /server-boot-graph/src/model/service/handler/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import fs from "fs"; 3 | import _ from "lodash"; 4 | import path from "path"; 5 | import * as Template from "./template"; 6 | import * as Utils from "@nebulario/tunnel-utils"; 7 | import * as ComposeApi from "PKG/linker-compose"; 8 | 9 | 10 | export const start = async (instance, cxt) => { 11 | const { 12 | instanceid, 13 | network: { networkid } 14 | } = instance; 15 | const networksList = execSync(`docker network ls`).toString(); 16 | 17 | if (networksList.indexOf(networkid) === -1) { 18 | cxt.logger.debug("network.create", { networkid }); 19 | execSync(`docker network create ${networkid}`); 20 | } 21 | 22 | const inspect = await ComposeApi.networkInspect(networkid, cxt); 23 | const localhost = inspect[0].IPAM.Config[0].Gateway; 24 | instance.network.localhost = localhost; 25 | 26 | cxt.logger.debug("instance.network", { networkid, localhost }); 27 | 28 | const folder = path.join(cxt.workspace, "handler"); 29 | const composeFile = path.join(folder, "docker-compose.yml"); 30 | const handler = { 31 | paths: { 32 | folder, 33 | composeFile 34 | } 35 | }; 36 | 37 | if (!fs.existsSync(handler.paths.folder)) { 38 | fs.mkdirSync(handler.paths.folder); 39 | } 40 | 41 | const content = Template.compose( 42 | instance, 43 | cxt 44 | ); 45 | fs.writeFileSync(handler.paths.composeFile, content); 46 | 47 | await ComposeApi.start({ instanceid, folder: handler.paths.folder }, cxt); 48 | 49 | instance.network.graph = await ComposeApi.getServiceNetwork(networkid, "graph", cxt); 50 | 51 | return handler; 52 | }; 53 | 54 | export const stop = async (instance, cxt) => { 55 | const { instanceid, handler } = instance; 56 | 57 | await ComposeApi.stop({ instanceid, folder: handler.paths.folder }, cxt); 58 | 59 | return {}; 60 | }; 61 | -------------------------------------------------------------------------------- /local-worker-graph/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nebulario/tunnel-local-worker-graph", 3 | "version": "1.70.4-initial-client-prod", 4 | "description": "GraphQL for cluster handler", 5 | "main": "dist/index.js", 6 | "repository": "github.com:vicjicaman/tunnel-local-worker-graph.git", 7 | "author": "Victor Jimenez ", 8 | "license": "SEE LICENSE IN LICENSE", 9 | "dependencies": { 10 | "@babel/runtime": "^7.6.0", 11 | "@babel/runtime-corejs3": "^7.6.0", 12 | "@nebulario/tunnel-logger": "1.70.0-master", 13 | "@nebulario/tunnel-utils": "1.70.0-master", 14 | "axios": "^0.19.0", 15 | "child-process-promise": "^2.2.1", 16 | "express": "^4.16.4", 17 | "express-graphql": "^0.7.1", 18 | "graphql": "^14.2.0", 19 | "graphql-iso-date": "^3.6.1", 20 | "graphql-request": "^1.8.2", 21 | "graphql-tools": "^4.0.4", 22 | "graphql-tools-types": "^1.2.1", 23 | "jsonwebtoken": "^8.5.1", 24 | "lodash": "^4.17.15", 25 | "tree-kill": "^1.2.1", 26 | "yamljs": "^0.3.0" 27 | }, 28 | "scripts": { 29 | "clean": "rm -Rf ./dist*", 30 | "start:prod": "NODE_ENV=\"production\" node ./dist/index.js", 31 | "start:dev": "NODE_ENV=\"development\" nodemon ./dist/index.js", 32 | "build:prod": "yarn clean && webpack --config webpack.config.js --mode=production ", 33 | "build:dev": "yarn clean && webpack --config webpack.config.js --mode=development ", 34 | "build:watch:dev": "yarn build:dev --watch", 35 | "build:watch:prod": "yarn build:prod --watch" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.6.0", 39 | "@babel/plugin-transform-runtime": "^7.6.0", 40 | "@babel/preset-env": "^7.6.0", 41 | "babel-loader": "^8.0.6", 42 | "nodemon": "^1.19.2", 43 | "uglifyjs-webpack-plugin": "^1.1.2", 44 | "webpack": "^4.39.3", 45 | "webpack-cli": "^3.3.8", 46 | "webpack-node-externals": "^1.7.2" 47 | } 48 | } -------------------------------------------------------------------------------- /server-boot-graph/src/api/workers/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import path from "path"; 3 | import _ from "lodash"; 4 | import fs from "fs"; 5 | import * as FolderUtils from "PKG/linker-folder"; 6 | import * as ComposeApi from "PKG/linker-compose"; 7 | 8 | export const list = async ({}, cxt) => { 9 | const { 10 | paths: { 11 | workers: { folder } 12 | } 13 | } = cxt; 14 | 15 | const workersList = FolderUtils.getDirectories(folder); 16 | 17 | return _.map(workersList, folder => { 18 | const workerid = path.basename(folder); 19 | return { 20 | id: workerid, 21 | workerid, 22 | folder 23 | }; 24 | }); 25 | }; 26 | 27 | export const get = async (workerid, cxt) => { 28 | const workers = await list({}, cxt); 29 | const worker = _.find(workers, { workerid }) || null; 30 | return worker; 31 | }; 32 | 33 | export const start = async ({ workerid, folder }, cxt) => { 34 | const { 35 | instance: { instanceid } 36 | } = cxt; 37 | 38 | await ComposeApi.start( 39 | { instanceid: `${instanceid}-${workerid}`, folder }, 40 | cxt 41 | ); 42 | }; 43 | 44 | export const restart = async ({ workerid, folder }, cxt) => { 45 | const { 46 | instance: { instanceid } 47 | } = cxt; 48 | 49 | await ComposeApi.restart( 50 | { instanceid: `${instanceid}-${workerid}`, folder }, 51 | cxt 52 | ); 53 | }; 54 | 55 | export const stop = async ({ workerid, folder }, cxt) => { 56 | const { 57 | instance: { instanceid } 58 | } = cxt; 59 | 60 | await ComposeApi.stop( 61 | { instanceid: `${instanceid}-${workerid}`, folder }, 62 | cxt 63 | ); 64 | }; 65 | 66 | export const info = async ({ workerid, folder }, cxt) => { 67 | const { 68 | instance: { 69 | instanceid, 70 | network: { networkid } 71 | } 72 | } = cxt; 73 | 74 | return await ComposeApi.getServiceNetwork(networkid, workerid, cxt); 75 | }; 76 | -------------------------------------------------------------------------------- /local-boot-graph/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nebulario/tunnel-local-boot-graph", 3 | "version": "1.70.17-initial-client-prod", 4 | "description": "GraphQL for local bootstrap", 5 | "main": "dist/index.js", 6 | "repository": "github.com:vicjicaman/tunnel-local-boot-graph.git", 7 | "author": "Victor Jimenez ", 8 | "license": "SEE LICENSE IN LICENSE", 9 | "dependencies": { 10 | "@babel/runtime": "^7.6.0", 11 | "@babel/runtime-corejs3": "^7.6.0", 12 | "@nebulario/tunnel-logger": "1.70.0-master", 13 | "@nebulario/tunnel-utils": "1.70.0-master", 14 | "axios": "^0.19.0", 15 | "child-process-promise": "^2.2.1", 16 | "dotenv": "^8.2.0", 17 | "express": "^4.16.4", 18 | "express-graphql": "^0.7.1", 19 | "graphql": "^14.2.0", 20 | "graphql-iso-date": "^3.6.1", 21 | "graphql-request": "^1.8.2", 22 | "graphql-tools": "^4.0.4", 23 | "graphql-tools-types": "^1.2.1", 24 | "lodash": "^4.17.15", 25 | "tree-kill": "^1.2.1", 26 | "uuid": "^3.3.3", 27 | "validator": "^12.2.0", 28 | "yamljs": "^0.3.0" 29 | }, 30 | "scripts": { 31 | "clean": "rm -Rf ./dist*", 32 | "start:prod": "NODE_ENV=\"production\" node ./dist/index.js", 33 | "start:dev": "NODE_ENV=\"development\" nodemon ./dist/index.js", 34 | "build:prod": "yarn clean && webpack --config webpack.config.js --mode=production ", 35 | "build:dev": "yarn clean && webpack --config webpack.config.js --mode=development ", 36 | "build:watch:dev": "yarn build:dev --watch", 37 | "build:watch:prod": "yarn build:prod --watch" 38 | }, 39 | "devDependencies": { 40 | "@babel/core": "^7.6.0", 41 | "@babel/plugin-transform-runtime": "^7.6.0", 42 | "@babel/preset-env": "^7.6.0", 43 | "babel-loader": "^8.0.6", 44 | "nodemon": "^1.19.2", 45 | "uglifyjs-webpack-plugin": "^1.1.2", 46 | "webpack": "^4.39.3", 47 | "webpack-cli": "^3.3.8", 48 | "webpack-node-externals": "^1.7.2" 49 | } 50 | } -------------------------------------------------------------------------------- /local-web/src/server/template.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | export const header = ({ paths: { resources: resourcesPath } }) => { 4 | return ` 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Tunnel tool 14 | 15 | 16 |
17 | `; 18 | }; 19 | 20 | export const footer = ({ 21 | config, 22 | config: { 23 | paths: { base: basePath, resources: resourcesPath } 24 | }, 25 | preloadedState, 26 | preloadedGraphState 27 | }) => { 28 | let res = ` 29 |
`; 30 | res += ` 44 | 45 | 46 | 47 | 48 | 49 | `; 50 | return res; 51 | }; 52 | -------------------------------------------------------------------------------- /server-boot-graph/src/model/service/handler/template.js: -------------------------------------------------------------------------------- 1 | import * as DevConfig from "PKG/linker-dev"; 2 | 3 | export const compose = ({ network: { networkid, localhost } }, cxt) => { 4 | const { 5 | workspace, 6 | mode, 7 | services: { 8 | bootstrap: { port: localBootstrapPort }, 9 | graph: { port: localGraphPort, version: graphVersion }, 10 | worker: { version: workerVersion }, 11 | server: { port: sshdPort } 12 | }, 13 | dev, 14 | versions 15 | } = cxt; 16 | const localWorkspace = cxt.paths.inner.workspace; 17 | 18 | const linkGraph = DevConfig.get("graph.link", cxt); 19 | const devConfigEnv = `- DEV_CONFIG=${DevConfig.serialize(cxt)}`; 20 | 21 | return `version: '3' 22 | services: 23 | graph: 24 | image: repoflow/tunnel-server-graph-container:${DevConfig.get( 25 | "graph.version", 26 | cxt 27 | ) || graphVersion} 28 | environment: 29 | - LOCAL_WORKER_EXTERNAL_BASE_SSHD_PORT=${sshdPort} 30 | - LOCAL_VERSIONS=${JSON.stringify(versions)} 31 | - LOCALHOST=${localhost} 32 | - NODE_ENV=${DevConfig.get("graph.mode", cxt) || cxt.mode} 33 | - ENV_MODE=${DevConfig.get("graph.mode", cxt) || cxt.mode} 34 | - LOCAL_HANDLER_GRAPH_PORT=${localGraphPort} 35 | - LOCAL_WORKSPACE=${localWorkspace} 36 | - LOCAL_NETWORK=${networkid} 37 | - LOCAL_WORKER_VERSION=${workerVersion} 38 | - BOOTSTRAP_WORKSPACE=${cxt.workspace} 39 | - BOOTSTRAP_GRAPH_URL=http://boot.graph:${localBootstrapPort}/graphql 40 | ${linkGraph ? devConfigEnv : ""} 41 | volumes: 42 | - ${workspace}:${localWorkspace} 43 | ${linkGraph ? `- ${DevConfig.get("graph.folder", cxt)}/modules:/env` : ""} 44 | ${ 45 | linkGraph 46 | ? 'command: "node_modules/nodemon/bin/nodemon ./dist/index.js"' 47 | : "" 48 | } 49 | networks: 50 | - ${networkid} 51 | extra_hosts: 52 | - "boot.graph:${localhost}" 53 | logging: 54 | driver: "json-file" 55 | options: 56 | max-file: "5" 57 | max-size: "10m" 58 | networks: 59 | ${networkid}: 60 | external: true 61 | 62 | `; 63 | }; 64 | -------------------------------------------------------------------------------- /server-boot-graph/src/model/service/index.js: -------------------------------------------------------------------------------- 1 | import uuidv4 from "uuid/v4"; 2 | import fs from "fs"; 3 | import path from "path"; 4 | import * as JsonUtils from "PKG/linker-json"; 5 | 6 | import * as Handler from "./handler"; 7 | 8 | export const start = async cxt => { 9 | const { workspace } = cxt; 10 | 11 | const instanceFile = path.join(workspace, "instance.json"); 12 | 13 | if (!fs.existsSync(instanceFile)) { 14 | JsonUtils.save(instanceFile, { id: uuidv4() }); 15 | } 16 | const { id } = JsonUtils.load(instanceFile); 17 | cxt.logger.debug("instance.current", { id }); 18 | const instance = { 19 | id, 20 | instanceid: `repoflow-tunnel-server-${id}`, 21 | network: { 22 | networkid: `repoflow-tunnel-server-network-${id}` 23 | } 24 | }; 25 | 26 | instance.handler = await Handler.start(instance, cxt); 27 | 28 | return instance; 29 | }; 30 | 31 | export const stop = async (signal, cxt) => { 32 | if (!cxt.services.stopping) { 33 | cxt.services.stopping = true; 34 | cxt.logger.info("service.stopping...", { signal }); 35 | 36 | await Handler.stop(cxt.instance, cxt); 37 | 38 | cxt.logger.debug("service.stopped"); 39 | } 40 | }; 41 | 42 | export const update = async ({ boot, graph, worker }, cxt) => { 43 | cxt.logger.debug("service.update", { boot, graph, worker, mode: cxt.mode }); 44 | 45 | let content = fs.readFileSync("start.sh").toString(); 46 | 47 | content = sync(content, "@nebulario/tunnel-server-boot-graph", boot); 48 | content = sync(content, "repoflow/tunnel-server-graph-container", graph); 49 | content = sync( 50 | content, 51 | "repoflow/tunnel-server-worker-graph-container", 52 | worker 53 | ); 54 | 55 | cxt.logger.debug("service.updated", { content }); 56 | 57 | if (cxt.mode === "production") { 58 | cxt.logger.debug("service.production.rewrite"); 59 | fs.writeFileSync("start.sh", content); 60 | return true; 61 | } 62 | 63 | return false; 64 | }; 65 | 66 | const sync = (content, fullname, version) => { 67 | const depPath = "=(.+)\\s+#module\\s+" + fullname; 68 | const versionRegex = new RegExp(depPath, "g"); 69 | 70 | return content.replace(versionRegex, `=${version} #module ${fullname}`); 71 | }; 72 | -------------------------------------------------------------------------------- /local-web/src/common/actions/device/outlet/remove.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import _ from "lodash"; 3 | import { Button } from "reactstrap"; 4 | import { useMutation } from "@apollo/react-hooks"; 5 | import { gql } from "apollo-boost"; 6 | import Loading from "UI/utils/loading"; 7 | import { DeviceFragment } from "Fragments/device"; 8 | import * as Mutation from "UI/utils/mutation"; 9 | import ConfirmModal from "UI/utils/confirm"; 10 | 11 | const mutation = gql` 12 | mutation DeviceOutletRemove($deviceid: String!, $outletid: String!) { 13 | viewer { 14 | devices { 15 | device(deviceid: $deviceid) { 16 | outlets { 17 | outlet(outletid: $outletid) { 18 | remove { 19 | ...DeviceFragment 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | ${DeviceFragment} 28 | `; 29 | 30 | export default ({ device, outlet: { outletid } }) => { 31 | const [ 32 | remove, 33 | { loading: mutationLoading, error: mutationError } 34 | ] = useMutation(mutation, { 35 | refetchQueries: () => [], 36 | onCompleted: ({}) => {}, 37 | onError: Mutation.onError 38 | }); 39 | 40 | return ( 41 | ( 43 | 54 | )} 55 | confirm={({ close }) => ( 56 | 68 | )} 69 | body={ 70 | 71 | Remove the oulet and the associated inlets from all the devices?! 72 | 73 | } 74 | /> 75 | ); 76 | }; 77 | -------------------------------------------------------------------------------- /server-worker-graph/src/index.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { execSync } from "child_process"; 3 | import { spawn } from "child-process-promise"; 4 | const express = require("express"); 5 | const graphqlHTTP = require("express-graphql"); 6 | const { makeExecutableSchema } = require("graphql-tools"); 7 | const { schema: rootSchema, resolvers: rootResolvers } = require("./schema"); 8 | 9 | import * as DevConfig from "PKG/linker-dev"; 10 | 11 | import * as Utils from "@nebulario/tunnel-utils"; 12 | import * as Logger from "@nebulario/tunnel-logger"; 13 | import * as Service from "Model/service"; 14 | 15 | const ENV_MODE = process.env["ENV_MODE"]; 16 | 17 | const LOCAL_WORKER_ID = process.env["LOCAL_WORKER_ID"]; 18 | const LOCAL_WORKSPACE = process.env["LOCAL_WORKSPACE"]; 19 | const LOCAL_WORKER_GRAPH_PORT = process.env["LOCAL_WORKER_GRAPH_PORT"]; 20 | const LOCAL_WORKER_SSHD_PORT = process.env["LOCAL_WORKER_SSHD_PORT"]; 21 | 22 | const cxt = { 23 | workspace: LOCAL_WORKSPACE, 24 | services: { 25 | sshd: { 26 | process: null, 27 | port: LOCAL_WORKER_SSHD_PORT 28 | } 29 | }, 30 | logger: null, 31 | dev: null, 32 | env: { 33 | mode: ENV_MODE, 34 | logs: { 35 | folder: path.join(LOCAL_WORKSPACE, "logs", "graph") 36 | } 37 | } 38 | }; 39 | 40 | DevConfig.init(cxt); 41 | cxt.logger = Logger.create({ path: cxt.env.logs.folder, env: ENV_MODE }, cxt); 42 | 43 | (async () => { 44 | await Service.start(cxt); 45 | 46 | var app = express(); 47 | Logger.Service.use(app, cxt); 48 | 49 | const schema = makeExecutableSchema({ 50 | typeDefs: rootSchema, 51 | resolvers: rootResolvers 52 | }); 53 | 54 | app.use( 55 | "/graphql", 56 | graphqlHTTP(request => ({ 57 | schema: schema, 58 | graphiql: true, 59 | context: { 60 | request, 61 | parents: {}, 62 | ...cxt 63 | } 64 | })) 65 | ); 66 | app.listen(LOCAL_WORKER_GRAPH_PORT, () => { 67 | cxt.logger.info("service.running....", { 68 | port: LOCAL_WORKER_GRAPH_PORT 69 | }); 70 | }); 71 | })().catch(e => cxt.logger.error("service.error", { error: e.toString() })); 72 | 73 | Utils.Process.shutdown(async signal => 74 | cxt.logger.info("service.shutdown", { signal }) 75 | ); 76 | -------------------------------------------------------------------------------- /local-web/src/common/root/home/inlets/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import _ from "lodash"; 3 | import { Route, Switch, Link } from "react-router-dom"; 4 | import { Row, Col } from "reactstrap"; 5 | import Query from "UI/utils/query"; 6 | import List from "UI/utils/list"; 7 | import { 8 | Card, 9 | ButtonGroup, 10 | Button, 11 | CardHeader, 12 | CardFooter, 13 | CardBody, 14 | CardTitle, 15 | CardText 16 | } from "reactstrap"; 17 | 18 | import * as LabelUI from "UI/label"; 19 | import Field from "UI/utils/field"; 20 | 21 | import * as DeviceQueries from "Queries/device"; 22 | import * as DeviceComp from "Comps/device"; 23 | import * as DeviceInletComp from "Comps/device/inlet"; 24 | 25 | export default ({ history, viewer }) => { 26 | return ( 27 |
28 |

29 | Inlets 30 |

31 |
32 |

33 | An inlet is a local endpoint on the target device that exposes a server outlet locally. 34 |

35 | list} 42 | > 43 | {({ list: devicesList }) => { 44 | return ( 45 | 46 | 47 | {devicesList.map((device, k) => { 48 | const { deviceid } = device; 49 | 50 | const deviceProps = { 51 | history, 52 | viewer, 53 | device 54 | }; 55 | 56 | return ( 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | ); 69 | })} 70 | 71 | 72 | ); 73 | }} 74 | 75 |
76 | ); 77 | }; 78 | -------------------------------------------------------------------------------- /local-web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nebulario/tunnel-local-web", 3 | "version": "1.70.8-initial-client-prod", 4 | "main": "index.js", 5 | "repository": "git@github.com:vicjicaman/tunnel-local-web.git", 6 | "author": "Victor Jimenez ", 7 | "license": "SEE LICENSE IN LICENSE", 8 | "scripts": { 9 | "clean:server": "rm -Rf ./dist/server*", 10 | "clean:web": "rm -Rf ./dist/web*", 11 | "build:prod:server": "yarn clean:server && webpack --config webpack.config.server.js --mode=production ", 12 | "build:dev:server": "yarn clean:server && webpack --config webpack.config.server.js --mode=development ", 13 | "build:prod:web": "yarn clean:web && webpack --config webpack.config.web.js --mode=production ", 14 | "build:dev:web": "yarn clean:web && webpack --config webpack.config.web.js --mode=development ", 15 | "build:dev": "run-p \"build:dev:*\" ", 16 | "build:prod": "run-p \"build:prod:*\" ", 17 | "start:dev": "nodemon ./dist/index.js ", 18 | "start:prod": "node ./dist/index.js ", 19 | "build:watch:dev": "run-p \"build:dev:* --watch\" ", 20 | "build:watch:prod": "run-p \"build:prod:* --watch\" " 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.6.0", 24 | "@babel/preset-env": "^7.6.0", 25 | "@babel/preset-react": "^7.0.0", 26 | "babel-loader": "^8.0.6", 27 | "css-loader": "^3.2.0", 28 | "nodemon": "^1.19.2", 29 | "npm-run-all": "^4.1.5", 30 | "style-loader": "^1.0.0", 31 | "uglifyjs-webpack-plugin": "^1.1.2", 32 | "webpack": "^4.39.3", 33 | "webpack-cli": "^3.3.8", 34 | "webpack-node-externals": "^1.7.2" 35 | }, 36 | "dependencies": { 37 | "@apollo/react-hooks": "^3.1.0", 38 | "@nebulario/tunnel-layout": "1.70.0-master", 39 | "@nebulario/tunnel-logger": "1.70.0-master", 40 | "@nebulario/tunnel-utils": "1.70.0-master", 41 | "apollo-boost": "^0.4.4", 42 | "apollo-cache-inmemory": "^1.6.3", 43 | "bootstrap": "^4.3.1", 44 | "connected-react-router": "^6.5.2", 45 | "dotenv": "^8.2.0", 46 | "express": "^4.17.1", 47 | "font-awesome": "^4.7.0", 48 | "graphql": "^14.5.4", 49 | "jquery": "^3.4.1", 50 | "node-fetch": "^2.6.0", 51 | "react": "^16.9.0", 52 | "react-apollo": "^3.1.0", 53 | "react-dom": "^16.9.0", 54 | "react-redux": "^7.1.1", 55 | "react-router": "^5.0.1", 56 | "react-router-dom": "^5.0.1", 57 | "react-toastify": "^5.4.1", 58 | "reactstrap": "^8.0.1", 59 | "redux": "^4.0.4", 60 | "socket.io-client": "^2.3.0", 61 | "validator": "^12.0.0" 62 | } 63 | } -------------------------------------------------------------------------------- /local-worker-graph/pkg/linker-kubectl/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import * as Utils from "@nebulario/tunnel-utils"; 3 | import fs from "fs"; 4 | 5 | const JSON_OUTPUT = "-o json"; 6 | 7 | export const kubectl = async (cmd, opts, cxt) => { 8 | const { 9 | config, 10 | namespace, 11 | input = null, 12 | limit = 2, 13 | output = JSON_OUTPUT 14 | } = opts; 15 | 16 | let currLimit = limit; 17 | let i = 0; 18 | while (i < currLimit) { 19 | ++i; 20 | try { 21 | const buildCmd = `${cmd} ${namespace ? ` -n ${namespace} ` : ""}`; 22 | cxt.logger.debug("kubectl.cmd", { cmd: buildCmd, attempt: i }); 23 | const stdout = execSync( 24 | (input !== null ? `echo '${input}' | ` : "") + 25 | `kubectl ${buildCmd} ${output ? output : ""} ${ 26 | config ? ` --kubeconfig=${config} ` : "" 27 | }` 28 | ); 29 | 30 | if (output === null) { 31 | return stdout; 32 | } 33 | 34 | if (output === JSON_OUTPUT) { 35 | return JSON.parse(stdout); 36 | } else { 37 | return stdout.toString(); 38 | } 39 | } catch (e) { 40 | const error = e.toString(); 41 | cxt.logger.error("kubectl.error", { error }); 42 | if (i >= currLimit) { 43 | throw e; 44 | } 45 | 46 | if (error.includes("was refused")) { 47 | currLimit = 5; 48 | await Utils.Process.wait(1000); 49 | cxt.logger.debug("kubectl.error.retry.refuse", { attempt: i }); 50 | } else { 51 | await Utils.Process.wait(100); 52 | cxt.logger.debug("kubectl.error.retry", { attempt: i }); 53 | } 54 | } 55 | } 56 | }; 57 | 58 | export const init = async (name, namespace, cxt) => { 59 | const config = { 60 | path: `/home/node/.kube/${name}.config`, 61 | context: "default", 62 | user: "handler", 63 | host: "https://kubernetes.default.svc.cluster.local", 64 | ca: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt", 65 | token: fs.readFileSync( 66 | "/var/run/secrets/kubernetes.io/serviceaccount/token" 67 | ) 68 | }; 69 | 70 | execSync( 71 | `kubectl config --kubeconfig=${config.path} set-cluster ${name} --server=${config.host} --certificate-authority=${config.ca}` 72 | ); 73 | 74 | execSync( 75 | `kubectl config --kubeconfig=${config.path} set-credentials ${config.user} --token=${config.token}` 76 | ); 77 | execSync( 78 | `kubectl config --kubeconfig=${config.path} set-context ${config.context} --cluster=${name} --namespace=${namespace} --user=${config.user}` 79 | ); 80 | execSync( 81 | `kubectl config --kubeconfig=${config.path} use-context ${config.context}` 82 | ); 83 | 84 | return config; 85 | }; 86 | -------------------------------------------------------------------------------- /server-worker-graph/pkg/linker-kubectl/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import * as Utils from "@nebulario/tunnel-utils"; 3 | import fs from "fs"; 4 | 5 | const JSON_OUTPUT = "-o json"; 6 | 7 | export const kubectl = async (cmd, opts, cxt) => { 8 | const { 9 | config, 10 | namespace, 11 | input = null, 12 | limit = 2, 13 | output = JSON_OUTPUT 14 | } = opts; 15 | 16 | let currLimit = limit; 17 | let i = 0; 18 | while (i < currLimit) { 19 | ++i; 20 | try { 21 | const buildCmd = `${cmd} ${namespace ? ` -n ${namespace} ` : ""}`; 22 | cxt.logger.debug("kubectl.cmd", { cmd: buildCmd, attempt: i }); 23 | const stdout = execSync( 24 | (input !== null ? `echo '${input}' | ` : "") + 25 | `kubectl ${buildCmd} ${output ? output : ""} ${ 26 | config ? ` --kubeconfig=${config} ` : "" 27 | }` 28 | ); 29 | 30 | if (output === null) { 31 | return stdout; 32 | } 33 | 34 | if (output === JSON_OUTPUT) { 35 | return JSON.parse(stdout); 36 | } else { 37 | return stdout.toString(); 38 | } 39 | } catch (e) { 40 | const error = e.toString(); 41 | cxt.logger.error("kubectl.error", { error }); 42 | if (i >= currLimit) { 43 | throw e; 44 | } 45 | 46 | if (error.includes("was refused")) { 47 | currLimit = 5; 48 | await Utils.Process.wait(1000); 49 | cxt.logger.debug("kubectl.error.retry.refuse", { attempt: i }); 50 | } else { 51 | await Utils.Process.wait(100); 52 | cxt.logger.debug("kubectl.error.retry", { attempt: i }); 53 | } 54 | } 55 | } 56 | }; 57 | 58 | export const init = async (name, namespace, cxt) => { 59 | const config = { 60 | path: `/home/node/.kube/${name}.config`, 61 | context: "default", 62 | user: "handler", 63 | host: "https://kubernetes.default.svc.cluster.local", 64 | ca: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt", 65 | token: fs.readFileSync( 66 | "/var/run/secrets/kubernetes.io/serviceaccount/token" 67 | ) 68 | }; 69 | 70 | execSync( 71 | `kubectl config --kubeconfig=${config.path} set-cluster ${name} --server=${config.host} --certificate-authority=${config.ca}` 72 | ); 73 | 74 | execSync( 75 | `kubectl config --kubeconfig=${config.path} set-credentials ${config.user} --token=${config.token}` 76 | ); 77 | execSync( 78 | `kubectl config --kubeconfig=${config.path} set-context ${config.context} --cluster=${name} --namespace=${namespace} --user=${config.user}` 79 | ); 80 | execSync( 81 | `kubectl config --kubeconfig=${config.path} use-context ${config.context}` 82 | ); 83 | 84 | return config; 85 | }; 86 | -------------------------------------------------------------------------------- /local-web/src/common/actions/device/inlet/remove.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import _ from "lodash"; 3 | import { Button } from "reactstrap"; 4 | import { useMutation } from "@apollo/react-hooks"; 5 | import { gql } from "apollo-boost"; 6 | import Loading from "UI/utils/loading"; 7 | import { DeviceFragment } from "Fragments/device"; 8 | import * as Mutation from "UI/utils/mutation"; 9 | import ConfirmModal from "UI/utils/confirm"; 10 | 11 | const mutation = gql` 12 | mutation DeviceOutletRemove($deviceid: String!, $inletid: String!) { 13 | viewer { 14 | devices { 15 | device (deviceid: $deviceid) { 16 | inlets { 17 | inlet(inletid: $inletid) { 18 | remove { 19 | ...DeviceFragment 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | ${DeviceFragment} 28 | `; 29 | 30 | export default ({ 31 | device, 32 | inlet: { 33 | inletid 34 | } 35 | }) => { 36 | const [ 37 | remove, 38 | { loading: mutationLoading, error: mutationError } 39 | ] = useMutation(mutation, { 40 | refetchQueries: () => [], 41 | onCompleted: ({}) => {}, 42 | onError: Mutation.onError 43 | }); 44 | 45 | 46 | return ( 47 | ( 49 | 60 | )} 61 | confirm={({ close }) => ( 62 | 74 | )} 75 | body={ 76 | 77 | Remove the inlet from the device? 78 | 79 | } 80 | /> 81 | ); 82 | 83 | return ( 84 | 101 | ); 102 | }; 103 | -------------------------------------------------------------------------------- /local-web/src/common/root/home/outlets/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import _ from "lodash"; 3 | import { Route, Switch, Link } from "react-router-dom"; 4 | import { Row, Col } from "reactstrap"; 5 | import Query from "UI/utils/query"; 6 | import List from "UI/utils/list"; 7 | import { 8 | Card, 9 | ButtonGroup, 10 | Button, 11 | CardHeader, 12 | CardFooter, 13 | CardBody, 14 | CardTitle, 15 | CardText 16 | } from "reactstrap"; 17 | 18 | import * as LabelUI from "UI/label"; 19 | import Field from "UI/utils/field"; 20 | 21 | import * as DeviceQueries from "Queries/device"; 22 | import * as DeviceComp from "Comps/device"; 23 | import * as DeviceOutletComp from "Comps/device/outlet"; 24 | 25 | export default ({ history, viewer }) => { 26 | return ( 27 |
28 |

29 | Outlets 30 |

31 |
32 |

33 | An outlet is an exposed service on the server, once the outlet is 34 | created you can:{" "} 35 | 36 | Privately share it 37 | {" "} 38 | to another device by creating an inlet or{" "} 39 | 40 | Expose it to the public 41 | {" "} 42 | with a proxy on the server. 43 |

44 | list} 51 | > 52 | {({ list: devicesList }) => { 53 | return ( 54 | 55 | 56 | {devicesList.map((device, k) => { 57 | const { deviceid } = device; 58 | 59 | const deviceProps = { 60 | history, 61 | viewer, 62 | device 63 | }; 64 | 65 | return ( 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | ); 80 | })} 81 | 82 | 83 | ); 84 | }} 85 | 86 |
87 | ); 88 | }; 89 | -------------------------------------------------------------------------------- /local-worker-graph/src/index.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { execSync } from "child_process"; 3 | import { spawn } from "child-process-promise"; 4 | const express = require("express"); 5 | const graphqlHTTP = require("express-graphql"); 6 | const { makeExecutableSchema } = require("graphql-tools"); 7 | const { schema: rootSchema, resolvers: rootResolvers } = require("./schema"); 8 | 9 | import * as DevConfig from "PKG/linker-dev"; 10 | 11 | import * as Utils from "@nebulario/tunnel-utils"; 12 | import * as Logger from "@nebulario/tunnel-logger"; 13 | import * as Service from "Model/service"; 14 | 15 | const ENV_MODE = process.env["ENV_MODE"]; 16 | 17 | const LOCAL_DEVICE_ID = process.env["LOCAL_DEVICE_ID"]; 18 | const LOCAL_WORKER_ID = process.env["LOCAL_WORKER_ID"]; 19 | const LOCAL_WORKSPACE = process.env["LOCAL_WORKSPACE"]; 20 | const LOCAL_WORKER_GRAPH_PORT = process.env["LOCAL_WORKER_GRAPH_PORT"]; 21 | const LOCAL_HANDLER_GRAPH_PORT = process.env["LOCAL_HANDLER_GRAPH_PORT"]; 22 | 23 | const LOCAL_TARGET_SERVER_HOST = process.env["LOCAL_TARGET_SERVER_HOST"]; 24 | const LOCAL_TARGET_SERVER_PORT = parseInt( 25 | process.env["LOCAL_TARGET_SERVER_PORT"] 26 | ); 27 | 28 | const cxt = { 29 | workspace: LOCAL_WORKSPACE, 30 | services: { 31 | server: { 32 | host: LOCAL_TARGET_SERVER_HOST, 33 | port: LOCAL_TARGET_SERVER_PORT 34 | }, 35 | config: { 36 | workerid: LOCAL_WORKER_ID, 37 | deviceid: LOCAL_DEVICE_ID 38 | }, 39 | graph: { 40 | port: LOCAL_HANDLER_GRAPH_PORT 41 | } 42 | }, 43 | paths: { 44 | keys: { 45 | folder: path.join(LOCAL_WORKSPACE, "keys") 46 | } 47 | }, 48 | state: { 49 | ip: null, 50 | inlets: [], 51 | outlets: [] 52 | }, 53 | logger: null, 54 | dev: null, 55 | env: { 56 | mode: ENV_MODE, 57 | logs: { 58 | folder: path.join(LOCAL_WORKSPACE, "logs", "graph") 59 | } 60 | } 61 | }; 62 | 63 | DevConfig.init(cxt); 64 | cxt.logger = Logger.create({ path: cxt.env.logs.folder, env: ENV_MODE }, cxt); 65 | 66 | (async () => { 67 | var app = express(); 68 | Logger.Service.use(app, cxt); 69 | 70 | const schema = makeExecutableSchema({ 71 | typeDefs: rootSchema, 72 | resolvers: rootResolvers 73 | }); 74 | 75 | app.use( 76 | "/graphql", 77 | graphqlHTTP(request => ({ 78 | schema: schema, 79 | graphiql: true, 80 | context: { 81 | request, 82 | parents: {}, 83 | ...cxt 84 | } 85 | })) 86 | ); 87 | app.listen(LOCAL_WORKER_GRAPH_PORT, () => { 88 | cxt.logger.info("service.running....", { 89 | port: LOCAL_WORKER_GRAPH_PORT 90 | }); 91 | 92 | Service.start(cxt); 93 | }); 94 | })().catch(e => cxt.logger.error("service.error", { error: e.toString() })); 95 | 96 | Utils.Process.shutdown(async signal => 97 | cxt.logger.info("service.shutdown", { signal }) 98 | ); 99 | -------------------------------------------------------------------------------- /local-boot-graph/src/model/service/handler/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import fs from "fs"; 3 | import _ from "lodash"; 4 | import path from "path"; 5 | import * as Template from "./template"; 6 | import * as Utils from "@nebulario/tunnel-utils"; 7 | import * as ComposeApi from "PKG/linker-compose"; 8 | 9 | const networkInspect = async (networkid, cxt) => 10 | JSON.parse(execSync(`docker network inspect ${networkid}`)); 11 | 12 | const getServiceNetwork = async (networkid, service, cxt) => { 13 | let networkInfo = null; 14 | 15 | const inspect = await networkInspect(networkid, cxt); 16 | 17 | while (networkInfo === null) { 18 | const inspectInitial = await networkInspect(networkid, cxt); 19 | 20 | networkInfo = 21 | _.find(inspect[0].Containers, c => c.Name.indexOf(service) !== -1) || 22 | null; 23 | 24 | cxt.logger.debug("instance.network.service.raw", { 25 | container: networkInfo 26 | }); 27 | 28 | await Utils.Process.wait(500); 29 | } 30 | 31 | const info = { 32 | ip: networkInfo.IPv4Address.split("/")[0] 33 | }; 34 | 35 | cxt.logger.debug("instance.network.service", { 36 | service, 37 | info 38 | }); 39 | return info; 40 | }; 41 | 42 | export const start = async (instance, cxt) => { 43 | const { 44 | instanceid, 45 | network: { networkid } 46 | } = instance; 47 | const networksList = execSync(`docker network ls`).toString(); 48 | 49 | if (networksList.indexOf(networkid) === -1) { 50 | cxt.logger.debug("network.create", { networkid }); 51 | execSync(`docker network create ${networkid}`); 52 | } 53 | 54 | const inspect = await networkInspect(networkid, cxt); 55 | const localhost = inspect[0].IPAM.Config[0].Gateway; 56 | instance.network.localhost = localhost; 57 | 58 | cxt.logger.debug("instance.network", { networkid, localhost }); 59 | 60 | const folder = path.join(cxt.workspace, "handler"); 61 | const composeFile = path.join(folder, "docker-compose.yml"); 62 | const handler = { 63 | paths: { 64 | folder, 65 | composeFile 66 | } 67 | }; 68 | 69 | if (!fs.existsSync(handler.paths.folder)) { 70 | fs.mkdirSync(handler.paths.folder); 71 | } 72 | 73 | const content = Template.compose( 74 | instance, 75 | cxt 76 | ); 77 | fs.writeFileSync(handler.paths.composeFile, content); 78 | 79 | await ComposeApi.start({ instanceid, folder: handler.paths.folder }, cxt); 80 | 81 | instance.network.graph = await getServiceNetwork(networkid, "graph", cxt); 82 | instance.network.web = 83 | cxt.state.mode !== "key" 84 | ? await getServiceNetwork(networkid, "web", cxt) 85 | : null; 86 | 87 | return handler; 88 | }; 89 | 90 | export const stop = async (instance, cxt) => { 91 | const { instanceid, handler } = instance; 92 | 93 | await ComposeApi.stop({ instanceid, folder: handler.paths.folder }, cxt); 94 | 95 | return {}; 96 | }; 97 | -------------------------------------------------------------------------------- /local-boot-graph/src/model/service/index.js: -------------------------------------------------------------------------------- 1 | import uuidv4 from "uuid/v4"; 2 | import fs from "fs"; 3 | import path from "path"; 4 | import * as JsonUtils from "PKG/linker-json"; 5 | import * as ValidationUtils from "PKG/linker-validation"; 6 | import validator from "validator"; 7 | import * as Handler from "./handler"; 8 | 9 | export const validate = cxt => { 10 | const { 11 | services: { 12 | config: { deviceid }, 13 | server: { host } 14 | } 15 | } = cxt; 16 | 17 | if (validator.isEmpty(deviceid) || !ValidationUtils.isEntityName(deviceid)) { 18 | console.log("Please provide a valid device name."); 19 | console.log("./start.sh <>"); 20 | return false; 21 | } 22 | 23 | return true; 24 | }; 25 | 26 | export const start = async cxt => { 27 | const { 28 | workspace, 29 | services: { 30 | config: { deviceid }, 31 | server: { host } 32 | }, state 33 | } = cxt; 34 | 35 | if(!host){ 36 | state.mode = "key"; 37 | }else{ 38 | state.mode = "worker"; 39 | } 40 | 41 | const instanceFile = path.join(workspace, "instance.json"); 42 | 43 | if (!fs.existsSync(instanceFile)) { 44 | JsonUtils.save(instanceFile, { id: uuidv4() }); 45 | } 46 | const { id } = JsonUtils.load(instanceFile); 47 | cxt.logger.debug("instance.current", { id }); 48 | const instance = { 49 | id, 50 | instanceid: `repoflow-tunnel-local-${id}`, 51 | network: { 52 | networkid: `repoflow-tunnel-local-network-${id}` 53 | } 54 | }; 55 | 56 | instance.handler = await Handler.start(instance, cxt); 57 | 58 | return instance; 59 | }; 60 | 61 | export const stop = async (signal, cxt) => { 62 | if (!cxt.services.stopping) { 63 | cxt.services.stopping = true; 64 | cxt.logger.info("service.stopping...", { signal }); 65 | 66 | await Handler.stop(cxt.instance, cxt); 67 | 68 | cxt.logger.debug("service.stopped"); 69 | } 70 | }; 71 | 72 | export const update = async ({ boot, graph, web, worker }, cxt) => { 73 | cxt.logger.debug("service.update", { 74 | boot, 75 | graph, 76 | worker, 77 | web, 78 | mode: cxt.mode 79 | }); 80 | 81 | let content = fs.readFileSync("start.sh").toString(); 82 | 83 | content = sync(content, "@nebulario/tunnel-local-boot-graph", boot); 84 | content = sync(content, "repoflow/tunnel-local-web-container", web); 85 | content = sync(content, "repoflow/tunnel-local-graph-container", graph); 86 | content = sync( 87 | content, 88 | "repoflow/tunnel-local-worker-graph-container", 89 | worker 90 | ); 91 | 92 | cxt.logger.debug("service.updated", { content }); 93 | 94 | if (cxt.mode === "production") { 95 | cxt.logger.debug("service.production.rewrite"); 96 | fs.writeFileSync("start.sh", content); 97 | return true; 98 | } 99 | 100 | cxt.logger.debug("service.non-production"); 101 | return false; 102 | }; 103 | 104 | const sync = (content, fullname, version) => { 105 | const depPath = "=(.+)\\s+#module\\s+" + fullname; 106 | const versionRegex = new RegExp(depPath, "g"); 107 | 108 | return content.replace(versionRegex, `=${version} #module ${fullname}`); 109 | }; 110 | -------------------------------------------------------------------------------- /local-web/src/web/render.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { hydrate } from "react-dom"; 3 | import { Provider } from "react-redux"; 4 | import { ApolloProvider } from "react-apollo"; 5 | import { ApolloConsumer } from "react-apollo"; 6 | import { ToastContainer } from "react-toastify"; 7 | 8 | import { ApolloClient } from "apollo-boost"; 9 | import { HttpLink } from "apollo-link-http"; 10 | import { InMemoryCache } from "apollo-cache-inmemory"; 11 | 12 | import { createStore, applyMiddleware, combineReducers, compose } from "redux"; 13 | import createHistory from "history/createBrowserHistory"; 14 | import { 15 | ConnectedRouter, 16 | connectRouter, 17 | routerMiddleware 18 | } from "connected-react-router"; 19 | import socketIOClient from "socket.io-client"; 20 | 21 | import * as DeviceQueries from "Queries/device"; 22 | 23 | class SocketContainer extends Component { 24 | constructor() { 25 | super(); 26 | this.state = {}; 27 | } 28 | 29 | componentDidMount() { 30 | const { client, events } = this.props; 31 | 32 | const socket = socketIOClient(events); 33 | socket.on("server.init", data => { 34 | socket.emit("client.init"); 35 | }); 36 | 37 | socket.on("client.update", data => { 38 | console.log("UPDATE DEVICES"); 39 | client.query({ 40 | fetchPolicy: "network-only", 41 | query: DeviceQueries.List 42 | }); 43 | }); 44 | } 45 | 46 | render() { 47 | return null; 48 | } 49 | } 50 | 51 | export const render = ({ 52 | App, 53 | watchers, 54 | reducers, 55 | urls: { graphql, events } 56 | }) => { 57 | const { store, history } = configureStore({ 58 | reducers, 59 | initState: window.__PRELOADED_STATE__ 60 | }); 61 | const client = configureGraph({ 62 | url: graphql, 63 | initState: window.__APOLLO_STATE__ 64 | }); 65 | 66 | const AppRoot = () => { 67 | return ( 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | ); 78 | }; 79 | 80 | hydrate(, document.getElementById("root")); 81 | }; 82 | 83 | const configureGraph = ({ url, initState }) => { 84 | return new ApolloClient({ 85 | cache: new InMemoryCache().restore(window.__APOLLO_STATE__), 86 | link: new HttpLink({ uri: url }) 87 | }); 88 | }; 89 | 90 | const configureStore = ({ reducers, initialState }) => { 91 | const history = createHistory(); 92 | 93 | // Allow the passed state to be garbage-collected 94 | //delete window.__PRELOADED_STATE__; 95 | const store = createStore( 96 | /***/ 97 | combineReducers({ router: connectRouter(history), app: reducers }), 98 | initialState, 99 | /**/ 100 | compose( 101 | applyMiddleware(routerMiddleware(history)), 102 | /**/ 103 | typeof window.__REDUX_DEVTOOLS_EXTENSION__ !== "undefined" 104 | ? window.__REDUX_DEVTOOLS_EXTENSION__() 105 | : f => f 106 | ) 107 | ); 108 | 109 | return { store, history }; 110 | }; 111 | -------------------------------------------------------------------------------- /local-boot-graph/src/model/viewer/hosts/index.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | 4 | const MANAGED_COMMENT = "#repoflow-linker-entry"; 5 | const HOSTS_FILE_PATH = "/etc/hosts"; 6 | 7 | const isIP = ip => { 8 | if (typeof ip !== "string") return false; 9 | if (!ip.match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)) { 10 | return false; 11 | } 12 | return ( 13 | ip.split(".").filter(octect => octect >= 0 && octect <= 255).length === 4 14 | ); 15 | }; 16 | 17 | const getHostsEntries = () => { 18 | const hostsContent = fs.readFileSync(HOSTS_FILE_PATH).toString(); 19 | const lines = hostsContent.split("\n"); 20 | 21 | const index = {}; 22 | const entries = lines 23 | .map(line => { 24 | const values = line.trim().split(/\s+/); 25 | const curr = { 26 | id: values[1], 27 | host: values[1], 28 | ip: values[0], 29 | managed: values[2] === MANAGED_COMMENT 30 | }; 31 | 32 | index[curr.host] = curr; 33 | return curr; 34 | }) 35 | .filter(entry => entry.host && isIP(entry.ip)); 36 | 37 | return { entries, index }; 38 | }; 39 | 40 | const readable = cxt => { 41 | try { 42 | fs.accessSync(HOSTS_FILE_PATH, fs.constants.R_OK); 43 | return true; 44 | } catch (e) { 45 | return false; 46 | } 47 | }; 48 | 49 | const writable = cxt => { 50 | try { 51 | fs.accessSync(HOSTS_FILE_PATH, fs.constants.W_OK); 52 | return true; 53 | } catch (e) { 54 | return false; 55 | } 56 | }; 57 | 58 | export const status = async cxt => { 59 | return { 60 | writable: writable(cxt), 61 | readable: readable(cxt) 62 | }; 63 | }; 64 | 65 | export const list = async (host, cxt) => { 66 | const { entries } = getHostsEntries(cxt); 67 | return entries; 68 | }; 69 | 70 | export const get = async (host, cxt) => { 71 | const { index } = getHostsEntries(cxt); 72 | return index[host.trim()] || null; 73 | }; 74 | 75 | export const add = async ({ host, ip }, cxt) => { 76 | const isWritable = writable(cxt); 77 | 78 | if (!isWritable) { 79 | cxt.logger.debug("hosts.add.noaccess"); 80 | return null; 81 | } 82 | 83 | const current = await get(host); 84 | cxt.logger.debug("hosts.add.compare", { current, ip }); 85 | 86 | if (current && current === ip) { 87 | return current; 88 | } 89 | 90 | try { 91 | await remove({ host }, cxt); 92 | fs.appendFileSync(HOSTS_FILE_PATH, `${ip} ${host} ${MANAGED_COMMENT}\n`); 93 | return await get(host); 94 | } catch (e) { 95 | cxt.logger.error("hosts.add.error", { error: e.toString() }); 96 | return null; 97 | } 98 | }; 99 | 100 | export const remove = async ({ host }, cxt) => { 101 | const isWritable = writable(cxt); 102 | 103 | if (!isWritable) { 104 | cxt.logger.debug("hosts.remove.noaccess"); 105 | return null; 106 | } 107 | 108 | const hostsContent = fs.readFileSync(HOSTS_FILE_PATH).toString(); 109 | 110 | const lines = hostsContent.split("\n"); 111 | for (let i = 0; i < lines.length; i++) { 112 | const line = lines[i]; 113 | const values = line.trim().split(/\s+/); 114 | if (values[1] === host) { 115 | lines.splice(i, 1); 116 | } 117 | } 118 | 119 | fs.writeFileSync(HOSTS_FILE_PATH, lines.join("\n")); 120 | return true; 121 | }; 122 | -------------------------------------------------------------------------------- /local-web/src/server/render.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { renderToNodeStream, renderToString } from "react-dom/server"; 3 | import { StaticRouter } from "react-router-dom"; 4 | import { Provider } from "react-redux"; 5 | import { ApolloProvider, getDataFromTree } from "react-apollo"; 6 | import * as Template from "./template"; 7 | 8 | import fetch from "node-fetch"; 9 | import { ApolloClient } from "apollo-boost"; 10 | import { HttpLink } from "apollo-link-http"; 11 | import { InMemoryCache } from "apollo-cache-inmemory"; 12 | 13 | import { createStore, applyMiddleware, combineReducers, compose } from "redux"; 14 | import { routerMiddleware } from "connected-react-router"; 15 | const createMemoryHistory = require("history").createMemoryHistory; 16 | 17 | export const render = ( 18 | { 19 | App, 20 | paths: { resources: RESOURCES_BASE_ROUTE, base: BASE_ROUTE }, 21 | urls: { 22 | external: { graphql: EXTERNAL_URL_GRAPH, events: EXTERNAL_URL_EVENTS }, 23 | internal: { graphql: INTERNAL_URL_GRAPH, events: INTERNAL_URL_EVENTS } 24 | }, 25 | watchers, 26 | reducers, 27 | req, 28 | res 29 | }, 30 | cxt 31 | ) => { 32 | let routerContext = {}; 33 | const { store } = configureStore({ reducers, initState: {} }); 34 | const { graph } = configureGraph({ 35 | url: INTERNAL_URL_GRAPH, 36 | req, 37 | initState: {} 38 | }); 39 | 40 | const AppRoot = ( 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | ); 49 | 50 | getDataFromTree(AppRoot) 51 | .then(() => { 52 | const preloadedState = store.getState(); 53 | const htmlSteam = 54 | Template.header({ 55 | paths: { base: BASE_ROUTE, resources: RESOURCES_BASE_ROUTE } 56 | }) + 57 | renderToString(AppRoot) + 58 | Template.footer({ 59 | config: { 60 | paths: { base: BASE_ROUTE, resources: RESOURCES_BASE_ROUTE }, 61 | urls: { 62 | graphql: EXTERNAL_URL_GRAPH, 63 | events: EXTERNAL_URL_EVENTS 64 | } 65 | }, 66 | preloadedState, 67 | preloadedGraphState: graph.extract() 68 | }); 69 | 70 | if (routerContext.url) { 71 | res.redirect(routerContext.url); 72 | } else { 73 | res.status(200); 74 | res.send(htmlSteam); 75 | } 76 | }) 77 | .catch(function(error) { 78 | console.log(error); 79 | }); 80 | }; 81 | 82 | const configureGraph = ({ url, req }) => ({ 83 | graph: new ApolloClient({ 84 | ssrMode: true, 85 | link: new HttpLink({ 86 | uri: url, 87 | onError: e => { 88 | console.log("APOLLO_CLIENT_ERROR"); 89 | console.log(e.graphQLErrors); 90 | }, 91 | credentials: "include", 92 | fetch: fetch, 93 | headers: { 94 | cookie: req.header("Cookie") 95 | } 96 | }), 97 | cache: new InMemoryCache() 98 | }) 99 | }); 100 | 101 | const configureStore = ({ reducers, initialState }) => { 102 | const reduxMiddlewares = [routerMiddleware(createMemoryHistory())]; 103 | 104 | const store = createStore( 105 | combineReducers({ app: reducers }), 106 | initialState, 107 | compose(applyMiddleware(...reduxMiddlewares)) 108 | ); 109 | 110 | return { store }; 111 | }; 112 | -------------------------------------------------------------------------------- /local-boot-graph/src/model/service/handler/template.js: -------------------------------------------------------------------------------- 1 | import * as DevConfig from "PKG/linker-dev"; 2 | 3 | export const compose = ({ network: { networkid, localhost } }, cxt) => { 4 | const { 5 | workspace, 6 | services: { 7 | bootstrap: { port: localBootstrapPort }, 8 | web: { port: localWebPort, version: webVersion }, 9 | graph: { port: localGraphPort, version: graphVersion }, 10 | worker: { version: workerVersion }, 11 | config: { deviceid }, 12 | server: { host: targetServerHost, port: targetServerPort } 13 | }, 14 | state, 15 | dev, 16 | versions 17 | } = cxt; 18 | const localWorkspace = cxt.paths.inner.workspace; 19 | const linkWeb = DevConfig.get("web.link", cxt); 20 | const linkGraph = DevConfig.get("graph.link", cxt); 21 | const devConfigEnv = `- DEV_CONFIG=${DevConfig.serialize(cxt)}`; 22 | 23 | const webService = `web: 24 | image: repoflow/tunnel-local-web-container:${DevConfig.get( 25 | "web.version", 26 | cxt 27 | ) || webVersion} 28 | environment: 29 | - NODE_ENV=${DevConfig.get("web.mode", cxt) || cxt.mode} 30 | - ENV_MODE=${DevConfig.get("web.mode", cxt) || cxt.mode} 31 | - LOCAL_HANDLER_WEB_PORT=${localWebPort} 32 | - LOCAL_HANDLER_GRAPH_PORT=${localGraphPort} 33 | - LOCAL_WORKSPACE=${localWorkspace} 34 | ${linkWeb ? devConfigEnv : ""} 35 | volumes: 36 | - ${workspace}:${localWorkspace} 37 | ${linkWeb ? `- ${DevConfig.get("web.folder", cxt)}/modules:/env` : ""} 38 | 39 | ${ 40 | linkWeb ? 'command: "node_modules/nodemon/bin/nodemon ./dist/index.js"' : "" 41 | } 42 | networks: 43 | - ${networkid} 44 | logging: 45 | driver: "json-file" 46 | options: 47 | max-file: "5" 48 | max-size: "10m" 49 | depends_on: 50 | - graph 51 | `; 52 | 53 | return `version: '3' 54 | 55 | services: 56 | ${state.mode !== "key" ? webService : ""} 57 | graph: 58 | image: repoflow/tunnel-local-graph-container:${DevConfig.get( 59 | "graph.version", 60 | cxt 61 | ) || graphVersion} 62 | environment: 63 | - LOCAL_EXEC_MODE=${state.mode} 64 | - LOCAL_VERSIONS=${JSON.stringify(versions)} 65 | - LOCALHOST=${localhost} 66 | - NODE_ENV=${DevConfig.get("graph.mode", cxt) || cxt.mode} 67 | - ENV_MODE=${DevConfig.get("graph.mode", cxt) || cxt.mode} 68 | - LOCAL_HANDLER_GRAPH_PORT=${localGraphPort} 69 | - LOCAL_WORKSPACE=${localWorkspace} 70 | - LOCAL_NETWORK=${networkid} 71 | - LOCAL_WORKER_VERSION=${workerVersion} 72 | - LOCAL_DEVICE_ID=${deviceid} 73 | - LOCAL_TARGET_SERVER_HOST=${targetServerHost} 74 | - LOCAL_TARGET_SERVER_PORT=${targetServerPort} 75 | - BOOTSTRAP_WORKSPACE=${cxt.workspace} 76 | - BOOTSTRAP_GRAPH_URL=http://boot.graph:${localBootstrapPort}/graphql 77 | ${linkGraph ? devConfigEnv : ""} 78 | volumes: 79 | - ${workspace}:${localWorkspace} 80 | ${linkGraph ? `- ${DevConfig.get("graph.folder", cxt)}/modules:/env` : ""} 81 | ${ 82 | linkGraph 83 | ? 'command: "node_modules/nodemon/bin/nodemon ./dist/index.js"' 84 | : "" 85 | } 86 | networks: 87 | - ${networkid} 88 | extra_hosts: 89 | - "boot.graph:${localhost}" 90 | logging: 91 | driver: "json-file" 92 | options: 93 | max-file: "5" 94 | max-size: "10m" 95 | 96 | networks: 97 | ${networkid}: 98 | external: true 99 | 100 | `; 101 | }; 102 | -------------------------------------------------------------------------------- /local-web/src/server/index.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | import { execSync } from "child_process"; 3 | import path from "path"; 4 | import fs from "fs"; 5 | 6 | import React from "react"; 7 | import express from "express"; 8 | import { render } from "./render"; 9 | import App from "../common/App.js"; 10 | import { reducers, watchers } from "../common/state"; 11 | import * as Logger from "@nebulario/tunnel-logger"; 12 | import * as Utils from "@nebulario/tunnel-utils"; 13 | 14 | import * as NetworkUtils from "PKG/linker-network"; 15 | 16 | const ENV_MODE = process.env["ENV_MODE"]; 17 | const LOCAL_WORKSPACE = process.env["LOCAL_WORKSPACE"]; 18 | const LOCAL_HANDLER_WEB_PORT = process.env["LOCAL_HANDLER_WEB_PORT"]; 19 | const LOCAL_HANDLER_GRAPH_PORT = process.env["LOCAL_HANDLER_GRAPH_PORT"]; 20 | const LOCAL_BASE_ROUTE_APP = ""; 21 | const LOCAL_RESOURCE_BASE_ROUTE = "/resources"; 22 | 23 | const cxt = { 24 | workspace: LOCAL_WORKSPACE, 25 | services: {}, 26 | logger: null, 27 | env: { 28 | mode: ENV_MODE, 29 | logs: { 30 | folder: path.join(LOCAL_WORKSPACE, "logs", "web") 31 | } 32 | } 33 | }; 34 | 35 | cxt.logger = Logger.create( 36 | { 37 | path: cxt.env.logs.folder, 38 | env: ENV_MODE 39 | }, 40 | cxt 41 | ); 42 | 43 | const nameserver = NetworkUtils.getNameserver(cxt); 44 | const LOCAL_GRAPH_IP = NetworkUtils.getHostname("graph", nameserver, cxt); 45 | const LOCAL_WEB_IP = NetworkUtils.getHostname("web", nameserver, cxt); 46 | 47 | cxt.services.graph = { 48 | host: LOCAL_GRAPH_IP, 49 | port: LOCAL_HANDLER_GRAPH_PORT 50 | }; 51 | cxt.services.web = { 52 | host: LOCAL_WEB_IP, 53 | port: LOCAL_HANDLER_WEB_PORT 54 | }; 55 | 56 | (async () => { 57 | //const LOCAL_HANDLER_GRAPH_URL = `http://localhost:7000/graphql`; 58 | //cxt.logger.debug("graph.url", { url: LOCAL_HANDLER_GRAPH_URL }); 59 | 60 | const app = express(); 61 | Logger.Service.use(app, cxt); 62 | 63 | app.use( 64 | LOCAL_RESOURCE_BASE_ROUTE + "/jquery", 65 | express.static("node_modules/jquery/dist") 66 | ); 67 | app.use( 68 | LOCAL_RESOURCE_BASE_ROUTE + "/bootstrap", 69 | express.static("node_modules/bootstrap/dist") 70 | ); 71 | app.use( 72 | LOCAL_RESOURCE_BASE_ROUTE + "/font-awesome", 73 | express.static("node_modules/font-awesome") 74 | ); 75 | 76 | app.use( 77 | LOCAL_RESOURCE_BASE_ROUTE + "/react-toastify", 78 | express.static("node_modules/react-toastify") 79 | ); 80 | 81 | app.use(LOCAL_BASE_ROUTE_APP + "/app", express.static("dist/web")); 82 | 83 | app.get("/*", (req, res) => { 84 | render( 85 | { 86 | App, 87 | req, 88 | res, 89 | watchers, 90 | reducers, 91 | paths: { 92 | base: LOCAL_BASE_ROUTE_APP, 93 | resources: LOCAL_RESOURCE_BASE_ROUTE 94 | }, 95 | urls: { 96 | external: { 97 | graphql: `http://${cxt.services.graph.host}:9000/graphql`, 98 | events: `http://${cxt.services.graph.host}:9000` 99 | }, 100 | internal: { 101 | graphql: `http://${cxt.services.graph.host}:9000/graphql`, 102 | events: `http://${cxt.services.graph.host}:9000` 103 | } 104 | } 105 | }, 106 | cxt 107 | ); 108 | }); 109 | 110 | app.listen(LOCAL_HANDLER_WEB_PORT, () => { 111 | cxt.logger.info("service.running", { port: LOCAL_HANDLER_WEB_PORT }); 112 | }); 113 | })().catch(e => cxt.logger.error("service.error", { error: e.toString() })); 114 | 115 | Utils.Process.shutdown(async signal => 116 | cxt.logger.info("service.shutdown", { signal }) 117 | ); 118 | -------------------------------------------------------------------------------- /server-worker-graph/pkg/linker-tunnel/index.js: -------------------------------------------------------------------------------- 1 | const uuidv4 = require("uuid/v4"); 2 | import _ from "lodash"; 3 | import kill from "tree-kill"; 4 | import * as OperationUtils from "PKG/linker-operation"; 5 | 6 | import { spawn } from "child-process-promise"; 7 | import * as Utils from "@nebulario/tunnel-utils"; 8 | 9 | export const listen = async (tunnelid, fwds, opts, cxt) => { 10 | const { key, user, host, port } = opts; 11 | 12 | return await frame( 13 | tunnelid, 14 | "listen", 15 | [ 16 | "-4", 17 | "-N", 18 | "-p", 19 | port, 20 | "-oStrictHostKeyChecking=no", 21 | "-oExitOnForwardFailure=yes", 22 | ..._.reduce( 23 | fwds, 24 | (res, { dest, source }) => { 25 | res.push( 26 | "-L", 27 | `${dest.host}:${dest.port}:${source.host}:${source.port}` 28 | ); 29 | return res; 30 | }, 31 | [] 32 | ), 33 | "-i", 34 | key, 35 | `${user}@${host}` 36 | ], 37 | cxt 38 | ); 39 | }; 40 | 41 | export const remote = async (tunnelid, fwds, opts, cxt) => { 42 | const { key, user, host, port } = opts; 43 | 44 | return await frame( 45 | tunnelid, 46 | "remote", 47 | [ 48 | "-N", 49 | "-p", 50 | port, 51 | "-oStrictHostKeyChecking=no", 52 | "-oExitOnForwardFailure=yes", 53 | ..._.reduce( 54 | fwds, 55 | (res, { dest, source }) => { 56 | res.push( 57 | "-R", 58 | `${dest.host}:${dest.port}:${source.host}:${source.port}` 59 | ); 60 | return res; 61 | }, 62 | [] 63 | ), 64 | "-i", 65 | key, 66 | `${user}@${host}` 67 | ], 68 | cxt 69 | ); 70 | }; 71 | 72 | const frame = async (tunnelid, mode, args, cxt) => { 73 | const op = await OperationUtils.start( 74 | tunnelid + "_op", 75 | { 76 | start: async (operation, cxt) => { 77 | cxt.logger.debug("tunnel.start", { 78 | tunnelid, 79 | mode, 80 | args 81 | }); 82 | 83 | operation.data.promise = spawn("ssh", args); 84 | 85 | cxt.logger.debug("tunnel.start.pid", { 86 | pid: operation.data.promise.childProcess.pid 87 | }); 88 | 89 | await operation.data.promise; 90 | }, 91 | stop: async (operation, cxt) => { 92 | const { 93 | data: { 94 | promise: { 95 | childProcess: { pid } 96 | } 97 | } 98 | } = operation; 99 | 100 | cxt.logger.debug("tunnel.stopping", { 101 | tunnelid, 102 | pid 103 | }); 104 | 105 | const pkill = new Promise(function(resolve, reject) { 106 | kill(pid, err => { 107 | if (err) { 108 | cxt.logger.error("tunnel.stop.error", { 109 | error: err.toString(), 110 | pid 111 | }); 112 | } 113 | 114 | cxt.logger.debug("tunnel.stopped", { 115 | tunnelid, 116 | pid 117 | }); 118 | operation.change(OperationUtils.Status.stopped); 119 | resolve(operation); 120 | }); 121 | }); 122 | 123 | await pkill; 124 | await Utils.Process.wait(500); 125 | }, 126 | retry: async (operation, error, i, cxt) => { 127 | cxt.logger.debug("tunnel.retry", { 128 | error: error ? error.toString() : "NO_ERROR", 129 | tunnelid, 130 | attempt: i, 131 | command: operation.data.command 132 | }); 133 | 134 | if (operation.data.command !== "stop" && i < 5) { 135 | await Utils.Process.wait(2500); 136 | cxt.logger.debug("tunnel.recover"); 137 | return true; 138 | } else { 139 | cxt.logger.debug("tunnel.giveup"); 140 | return false; 141 | } 142 | } 143 | }, 144 | {}, 145 | cxt 146 | ); 147 | 148 | return { id: tunnelid, tunnelid, operation: op }; 149 | }; 150 | 151 | export const stop = async (tunnel, cxt) => { 152 | tunnel.operation.data.command = "stop"; 153 | return await OperationUtils.stop(tunnel.operation, {}, cxt); 154 | }; 155 | -------------------------------------------------------------------------------- /local-web/src/common/comps/device/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import _ from "lodash"; 3 | import { Route, NavLink, Switch, Link } from "react-router-dom"; 4 | 5 | import { Row, Col } from "reactstrap"; 6 | import { 7 | Card as ReactCard, 8 | ButtonGroup, 9 | Button, 10 | CardHeader, 11 | CardFooter, 12 | CardBody, 13 | CardTitle, 14 | CardText 15 | } from "reactstrap"; 16 | import { 17 | Alert, 18 | Badge, 19 | ButtonDropdown, 20 | DropdownToggle, 21 | DropdownMenu, 22 | DropdownItem 23 | } from "reactstrap"; 24 | import * as LabelUI from "UI/label"; 25 | import Field from "UI/utils/field"; 26 | import * as Outlet from "./outlet"; 27 | import * as Inlet from "./inlet"; 28 | 29 | import * as InfoUtils from "UI/utils/info"; 30 | 31 | import DeviceServiceUpdate from "Actions/device/service/update"; 32 | import DeviceOutletAdd from "Actions/device/outlet/add"; 33 | 34 | export const IconInfo = () => ; 35 | export const IconWarning = () => ; 36 | 37 | 38 | const DeviceWarning = ({ device }) => { 39 | const { 40 | deviceid, 41 | service: { needRestart, upgradable } 42 | } = device; 43 | 44 | return ( 45 |
} 48 | > 49 | {needRestart && ( 50 | 51 | You need to restart the device's local side service. 52 | 53 | )} 54 | 55 | {upgradable && ( 56 | 57 | There is an update to the device's local side service.{" "} 58 | 59 | 60 | )} 61 | 62 | ); 63 | }; 64 | 65 | export const Icon = () => ; 66 | 67 | export const OnlineLabel = () => ( 68 | 69 | 70 | online 71 | 72 | 73 | ); 74 | 75 | export const OfflineLabel = () => ( 76 | 77 | 78 | offline 79 | 80 | 81 | ); 82 | 83 | // {device.state.online ? : } 84 | export const Head = ({ history, device, title = true }) => { 85 | const lbl = ( 86 | 87 | {device.state.online ? ( 88 | {device.deviceid} 89 | ) : ( 90 | {device.deviceid} 91 | )} 92 | {device.service && 93 | device.state.online && 94 | (device.service.upgradable || device.service.needRestart) && ( 95 | 96 | )} 97 | 98 | ); 99 | return
{title ?

{lbl}

: lbl}
; 100 | }; 101 | 102 | export const OutletInfo = ({ history, device }) => { 103 | return ( 104 | 105 | {device.state.online && ( 106 | 107 | 108 | 113 | 114 | 115 | )} 116 | 117 | ); 118 | }; 119 | 120 | export const InletInfo = ({ history, device }) => { 121 | return ( 122 | 123 | {device.state.online && ( 124 | 125 | 126 | 131 | 132 | 133 | )} 134 | 135 | ); 136 | }; 137 | 138 | export const OutletControl = ({ history, device }) => { 139 | const initial = { outletid: "", host: "localhost", port: "" }; 140 | 141 | return ( 142 |
143 | {device.state.online && ( 144 | 145 | )} 146 |
147 | ); 148 | }; 149 | -------------------------------------------------------------------------------- /local-boot-graph/pkg/linker-compose/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import _ from "lodash"; 5 | import * as Utils from "@nebulario/tunnel-utils"; 6 | 7 | export const networkInspect = async (networkid, cxt) => 8 | JSON.parse(execSync(`docker network inspect ${networkid}`)); 9 | 10 | export const start = ({ instanceid, folder }, cxt) => { 11 | const stdout = execSync( 12 | `docker-compose -p ${instanceid} up --remove-orphans --detach`, 13 | { 14 | cwd: folder 15 | } 16 | ); 17 | cxt.logger.debug("compose.start", { 18 | instanceid, 19 | folder, 20 | stdout: stdout.toString() 21 | }); 22 | }; 23 | 24 | export const restart = ({ instanceid, folder }, cxt) => { 25 | const stdout = execSync(`docker-compose -p ${instanceid} restart`, { 26 | cwd: folder 27 | }); 28 | 29 | cxt.logger.debug("compose.restart", { 30 | instanceid, 31 | folder, 32 | stdout: stdout.toString() 33 | }); 34 | }; 35 | 36 | export const stop = async ({ instanceid, folder }, cxt) => { 37 | /*let retry = 0; 38 | 39 | while (retry++ < 5) { 40 | cxt.logger.debug("instance.stop", { retry, instanceid }); 41 | try { 42 | const out = execSync(`docker-compose -p ${instanceid} stop`, { 43 | cwd: folder 44 | }).toString(); 45 | cxt.logger.debug("instance.compose.stop", { out }); 46 | break; 47 | } catch (e) { 48 | cxt.logger.debug("instance.stop.warning", { warning: e.toString() }); 49 | await Utils.Process.wait(2000); 50 | } 51 | }*/ 52 | 53 | let containers = await getContainersState({ instanceid, folder }, cxt); 54 | 55 | let working = true; 56 | while (working) { 57 | cxt.logger.debug("service.worker.stopping", { 58 | instanceid 59 | }); 60 | working = false; 61 | for (const container of containers) { 62 | cxt.logger.info("service.worker.stopping"); 63 | cxt.logger.debug("service.worker.stopping.info", { 64 | containerid: container.Id, 65 | State: container.State 66 | }); 67 | const out = execSync(`docker stop ${container.Id}`, { 68 | cwd: folder 69 | }).toString(); 70 | 71 | cxt.logger.debug("compose.stopped", { 72 | containerid: container.Id 73 | }); 74 | } 75 | containers = await getContainersState({ instanceid, folder }, cxt); 76 | for (const container of containers) { 77 | if (container.State.Running === true) { 78 | working = true; 79 | } 80 | } 81 | await Utils.Process.wait(1000); 82 | } 83 | cxt.logger.debug("compose.stopped", { 84 | instanceid 85 | }); 86 | }; 87 | 88 | export const getServiceNetwork = async (networkid, service, cxt) => { 89 | let networkInfo = null; 90 | 91 | const inspect = await networkInspect(networkid, cxt); 92 | 93 | while (networkInfo === null) { 94 | const inspectInitial = await networkInspect(networkid, cxt); 95 | 96 | networkInfo = 97 | _.find(inspect[0].Containers, c => c.Name.indexOf(service) !== -1) || 98 | null; 99 | 100 | cxt.logger.debug("instance.network.service.raw", { 101 | container: networkInfo 102 | }); 103 | 104 | await Utils.Process.wait(500); 105 | } 106 | 107 | const info = { 108 | ip: networkInfo.IPv4Address.split("/")[0] 109 | }; 110 | 111 | cxt.logger.debug("instance.network.service", { 112 | service, 113 | info 114 | }); 115 | return info; 116 | }; 117 | 118 | export const getContainersState = async ({ instanceid, folder }, cxt) => { 119 | try { 120 | const res = []; 121 | const idsStr = execSync(`docker-compose -p ${instanceid} ps -q`, { 122 | cwd: folder 123 | }) 124 | .toString() 125 | .trim(); 126 | 127 | const ids = idsStr.match(/[^\r\n]+/g); 128 | 129 | for (const id of ids) { 130 | const jsonInfoStr = execSync( 131 | `docker inspect --format='{{json .}}' ${id}`, 132 | { 133 | cwd: folder 134 | } 135 | ).toString(); 136 | 137 | const jsonInfo = JSON.parse(jsonInfoStr); 138 | res.push(jsonInfo); 139 | } 140 | 141 | cxt.logger.debug("compose.instance", { res: res.map(({ Id: id }) => id) }); 142 | return res; 143 | } catch (e) { 144 | cxt.logger.debug("compose.container.error", { error: e.toString() }); 145 | throw e; 146 | } 147 | }; 148 | -------------------------------------------------------------------------------- /server-boot-graph/pkg/linker-compose/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from "child_process"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import _ from "lodash"; 5 | import * as Utils from "@nebulario/tunnel-utils"; 6 | 7 | export const networkInspect = async (networkid, cxt) => 8 | JSON.parse(execSync(`docker network inspect ${networkid}`)); 9 | 10 | export const start = ({ instanceid, folder }, cxt) => { 11 | const stdout = execSync( 12 | `docker-compose -p ${instanceid} up --remove-orphans --detach`, 13 | { 14 | cwd: folder 15 | } 16 | ); 17 | cxt.logger.debug("compose.start", { 18 | instanceid, 19 | folder, 20 | stdout: stdout.toString() 21 | }); 22 | }; 23 | 24 | export const restart = ({ instanceid, folder }, cxt) => { 25 | const stdout = execSync(`docker-compose -p ${instanceid} restart`, { 26 | cwd: folder 27 | }); 28 | 29 | cxt.logger.debug("compose.restart", { 30 | instanceid, 31 | folder, 32 | stdout: stdout.toString() 33 | }); 34 | }; 35 | 36 | export const stop = async ({ instanceid, folder }, cxt) => { 37 | /*let retry = 0; 38 | 39 | while (retry++ < 5) { 40 | cxt.logger.debug("instance.stop", { retry, instanceid }); 41 | try { 42 | const out = execSync(`docker-compose -p ${instanceid} stop`, { 43 | cwd: folder 44 | }).toString(); 45 | cxt.logger.debug("instance.compose.stop", { out }); 46 | break; 47 | } catch (e) { 48 | cxt.logger.debug("instance.stop.warning", { warning: e.toString() }); 49 | await Utils.Process.wait(2000); 50 | } 51 | }*/ 52 | 53 | let containers = await getContainersState({ instanceid, folder }, cxt); 54 | 55 | let working = true; 56 | while (working) { 57 | cxt.logger.debug("service.worker.stopping", { 58 | instanceid 59 | }); 60 | working = false; 61 | for (const container of containers) { 62 | cxt.logger.info("service.worker.stopping"); 63 | cxt.logger.debug("service.worker.stopping.info", { 64 | containerid: container.Id, 65 | State: container.State 66 | }); 67 | const out = execSync(`docker stop ${container.Id}`, { 68 | cwd: folder 69 | }).toString(); 70 | 71 | cxt.logger.debug("compose.stopped", { 72 | containerid: container.Id 73 | }); 74 | } 75 | containers = await getContainersState({ instanceid, folder }, cxt); 76 | for (const container of containers) { 77 | if (container.State.Running === true) { 78 | working = true; 79 | } 80 | } 81 | await Utils.Process.wait(1000); 82 | } 83 | cxt.logger.debug("compose.stopped", { 84 | instanceid 85 | }); 86 | }; 87 | 88 | export const getServiceNetwork = async (networkid, service, cxt) => { 89 | let networkInfo = null; 90 | 91 | const inspect = await networkInspect(networkid, cxt); 92 | 93 | while (networkInfo === null) { 94 | const inspectInitial = await networkInspect(networkid, cxt); 95 | 96 | networkInfo = 97 | _.find(inspect[0].Containers, c => c.Name.indexOf(service) !== -1) || 98 | null; 99 | 100 | cxt.logger.debug("instance.network.service.raw", { 101 | container: networkInfo 102 | }); 103 | 104 | await Utils.Process.wait(500); 105 | } 106 | 107 | const info = { 108 | ip: networkInfo.IPv4Address.split("/")[0] 109 | }; 110 | 111 | cxt.logger.debug("instance.network.service", { 112 | service, 113 | info 114 | }); 115 | return info; 116 | }; 117 | 118 | export const getContainersState = async ({ instanceid, folder }, cxt) => { 119 | try { 120 | const res = []; 121 | const idsStr = execSync(`docker-compose -p ${instanceid} ps -q`, { 122 | cwd: folder 123 | }) 124 | .toString() 125 | .trim(); 126 | 127 | const ids = idsStr.match(/[^\r\n]+/g); 128 | 129 | for (const id of ids) { 130 | const jsonInfoStr = execSync( 131 | `docker inspect --format='{{json .}}' ${id}`, 132 | { 133 | cwd: folder 134 | } 135 | ).toString(); 136 | 137 | const jsonInfo = JSON.parse(jsonInfoStr); 138 | res.push(jsonInfo); 139 | } 140 | 141 | cxt.logger.debug("compose.instance", { res: res.map(({ Id: id }) => id) }); 142 | return res; 143 | } catch (e) { 144 | cxt.logger.debug("compose.container.error", { error: e.toString() }); 145 | throw e; 146 | } 147 | }; 148 | -------------------------------------------------------------------------------- /local-worker-graph/pkg/linker-tunnel/index.js: -------------------------------------------------------------------------------- 1 | const uuidv4 = require("uuid/v4"); 2 | import _ from "lodash"; 3 | import kill from "tree-kill"; 4 | import * as OperationUtils from "PKG/linker-operation"; 5 | 6 | import { spawn } from "child-process-promise"; 7 | import * as Utils from "@nebulario/tunnel-utils"; 8 | 9 | export const listen = async (tunnelid, fwds, opts, cxt) => { 10 | const { key, user, host, port, onError } = opts; 11 | 12 | return await frame( 13 | tunnelid, 14 | "listen", 15 | [ 16 | "-4", 17 | "-N", 18 | "-p", 19 | port, 20 | "-oStrictHostKeyChecking=no", 21 | "-oExitOnForwardFailure=yes", 22 | ..._.reduce( 23 | fwds, 24 | (res, { dest, source }) => { 25 | res.push( 26 | "-L", 27 | `${dest.host}:${dest.port}:${source.host}:${source.port}` 28 | ); 29 | return res; 30 | }, 31 | [] 32 | ), 33 | "-i", 34 | key, 35 | `${user}@${host}` 36 | ], 37 | { onError }, 38 | cxt 39 | ); 40 | }; 41 | 42 | export const remote = async (tunnelid, fwds, opts, cxt) => { 43 | const { key, user, host, port, onError } = opts; 44 | 45 | return await frame( 46 | tunnelid, 47 | "remote", 48 | [ 49 | "-N", 50 | "-p", 51 | port, 52 | "-oStrictHostKeyChecking=no", 53 | "-oExitOnForwardFailure=yes", 54 | ..._.reduce( 55 | fwds, 56 | (res, { dest, source }) => { 57 | res.push( 58 | "-R", 59 | `${dest.host}:${dest.port}:${source.host}:${source.port}` 60 | ); 61 | return res; 62 | }, 63 | [] 64 | ), 65 | "-i", 66 | key, 67 | `${user}@${host}` 68 | ], 69 | { onError }, 70 | cxt 71 | ); 72 | }; 73 | 74 | const frame = async (tunnelid, mode, args, opts, cxt) => { 75 | const { onError } = opts; 76 | 77 | const op = await OperationUtils.start( 78 | tunnelid + "_op", 79 | { 80 | start: async (operation, cxt) => { 81 | cxt.logger.debug("tunnel.start", { 82 | tunnelid, 83 | mode, 84 | args 85 | }); 86 | 87 | operation.data.promise = spawn("ssh", args); 88 | 89 | cxt.logger.debug("tunnel.start.pid", { 90 | pid: operation.data.promise.childProcess.pid 91 | }); 92 | 93 | await operation.data.promise; 94 | }, 95 | stop: async (operation, cxt) => { 96 | const { 97 | data: { 98 | promise: { 99 | childProcess: { pid } 100 | } 101 | } 102 | } = operation; 103 | 104 | cxt.logger.debug("tunnel.stopping", { 105 | tunnelid, 106 | pid 107 | }); 108 | 109 | const pkill = new Promise(function(resolve, reject) { 110 | kill(pid, err => { 111 | if (err) { 112 | cxt.logger.error("tunnel.stop.error", { 113 | error: err.toString(), 114 | pid 115 | }); 116 | } 117 | 118 | cxt.logger.debug("tunnel.stopped", { 119 | tunnelid, 120 | pid 121 | }); 122 | operation.change(OperationUtils.Status.stopped); 123 | resolve(operation); 124 | }); 125 | }); 126 | 127 | await pkill; 128 | await Utils.Process.wait(500); 129 | }, 130 | retry: async (operation, error, i, cxt) => { 131 | cxt.logger.debug("tunnel.retry", { 132 | error: error ? error.toString() : "NO_ERROR", 133 | tunnelid, 134 | attempt: i, 135 | command: operation.data.command 136 | }); 137 | 138 | onError && (await onError(operation, error, cxt)); 139 | 140 | if (operation.data.command !== "stop" && i < 5) { 141 | await Utils.Process.wait(2500); 142 | cxt.logger.debug("tunnel.recover"); 143 | return true; 144 | } else { 145 | cxt.logger.debug("tunnel.giveup"); 146 | return false; 147 | } 148 | } 149 | }, 150 | {}, 151 | cxt 152 | ); 153 | 154 | return { id: tunnelid, tunnelid, operation: op }; 155 | }; 156 | 157 | export const stop = async (tunnel, cxt) => { 158 | tunnel.operation.data.command = "stop"; 159 | return await OperationUtils.stop(tunnel.operation, {}, cxt); 160 | }; 161 | -------------------------------------------------------------------------------- /server-boot-graph/src/index.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | const path = require("path"); 3 | const fs = require("fs"); 4 | const express = require("express"); 5 | const graphqlHTTP = require("express-graphql"); 6 | const { makeExecutableSchema } = require("graphql-tools"); 7 | const { schema: rootSchema, resolvers: rootResolvers } = require("./schema"); 8 | 9 | import * as FolderUtils from "PKG/linker-folder"; 10 | import * as DevConfig from "PKG/linker-dev"; 11 | 12 | import * as Utils from "@nebulario/tunnel-utils"; 13 | import * as Logger from "@nebulario/tunnel-logger"; 14 | 15 | import * as ServiceModel from "Model/service"; 16 | 17 | const ENV_MODE = process.env["ENV_MODE"] || "production"; 18 | const INNER_WORKSPACE = "/workspace"; 19 | const LOCAL_WORKSPACE = path.join( 20 | FolderUtils.resolveCurrent( 21 | FolderUtils.resolveTilde(process.env["LOCAL_WORKSPACE"]) 22 | ), 23 | "workspace" 24 | ); 25 | const LOCAL_TARGET_SERVER_PORT = parseInt( 26 | process.env["LOCAL_TARGET_SERVER_PORT"] 27 | ); 28 | 29 | const LOCAL_GRAPH_BOOTSTRAP_SERVICE_PORT = parseInt( 30 | process.env["LOCAL_GRAPH_BOOTSTRAP_SERVICE_PORT"] || "52551" 31 | ); 32 | const LOCAL_GRAPH_SERVICE_PORT = parseInt( 33 | process.env["LOCAL_GRAPH_SERVICE_PORT"] || "17007" 34 | ); 35 | 36 | const LOCAL_VERSION_BOOTSTRAP = process.env["LOCAL_VERSION_BOOTSTRAP"]; 37 | const LOCAL_VERSION_GRAPH = process.env["LOCAL_VERSION_GRAPH"]; 38 | const LOCAL_VERSION_WORKER = process.env["LOCAL_VERSION_WORKER"]; 39 | 40 | const cxt = { 41 | workspace: LOCAL_WORKSPACE, 42 | mode: ENV_MODE, 43 | paths: { 44 | inner: { 45 | workspace: INNER_WORKSPACE 46 | }, 47 | workers: { 48 | folder: path.join(LOCAL_WORKSPACE, "workers") 49 | } 50 | }, 51 | services: { 52 | bootstrap: { port: LOCAL_GRAPH_BOOTSTRAP_SERVICE_PORT }, 53 | graph: { port: LOCAL_GRAPH_SERVICE_PORT, version: LOCAL_VERSION_GRAPH }, 54 | worker: { 55 | version: LOCAL_VERSION_WORKER 56 | }, 57 | server: { 58 | port: LOCAL_TARGET_SERVER_PORT 59 | } 60 | }, 61 | dev: null, 62 | logger: null, 63 | instance: null, 64 | versions: { 65 | boot: LOCAL_VERSION_BOOTSTRAP, 66 | graph: LOCAL_VERSION_GRAPH, 67 | worker: LOCAL_VERSION_WORKER 68 | } 69 | }; 70 | 71 | DevConfig.init(cxt); 72 | 73 | if (DevConfig.get("mode", cxt)) { 74 | cxt.mode = DevConfig.get("mode", cxt); 75 | } 76 | if (DevConfig.get("workspace", cxt)) { 77 | cxt.workspace = DevConfig.get("workspace", cxt); 78 | cxt.workspace = FolderUtils.resolveCurrent( 79 | FolderUtils.resolveTilde(cxt.workspace) 80 | ); 81 | } 82 | 83 | if (!fs.existsSync(cxt.workspace)) { 84 | FolderUtils.makePath(cxt.workspace); 85 | } 86 | cxt.logger = Logger.create({ 87 | path: path.join(cxt.workspace, "logs", "bootstrap"), 88 | env: cxt.mode 89 | }); 90 | 91 | cxt.logger.debug("context", { 92 | workspace: cxt.workspace 93 | }); 94 | 95 | (async () => { 96 | console.log("-----------------------------------------------------------"); 97 | console.log(`WORKSPACE: ${cxt.workspace}`); 98 | console.log(`KEYS FOLDER: ${cxt.workspace}/keys`); 99 | console.log("-----------------------------------------------------------"); 100 | 101 | cxt.instance = await ServiceModel.start(cxt); 102 | 103 | console.log("-----------------------------------------------------------"); 104 | console.log(`- The server side is ready to handle device connections`); 105 | console.log( 106 | `- Initialize a device by coping its key file to the keys folder.` 107 | ); 108 | console.log("-----------------------------------------------------------"); 109 | 110 | var app = express(); 111 | Logger.Service.use(app, cxt); 112 | 113 | const schema = makeExecutableSchema({ 114 | typeDefs: rootSchema, 115 | resolvers: rootResolvers 116 | }); 117 | 118 | app.use( 119 | "/graphql", 120 | graphqlHTTP(request => ({ 121 | schema: schema, 122 | graphiql: true, 123 | context: { 124 | request, 125 | ...cxt 126 | } 127 | })) 128 | ); 129 | 130 | app.listen(LOCAL_GRAPH_BOOTSTRAP_SERVICE_PORT, () => { 131 | cxt.logger.debug("service.bootstrap.running", { 132 | port: LOCAL_GRAPH_BOOTSTRAP_SERVICE_PORT 133 | }); 134 | }); 135 | })().catch(e => cxt.logger.error("service.error", { error: e.toString() })); 136 | 137 | Utils.Process.shutdown(async signal => { 138 | await ServiceModel.stop(signal, cxt); 139 | }); 140 | -------------------------------------------------------------------------------- /local-web/src/common/root/home/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import _ from "lodash"; 3 | import { 4 | Route, 5 | NavLink as NavLinkRoute, 6 | Switch, 7 | Link, 8 | Redirect 9 | } from "react-router-dom"; 10 | import { TabContent, TabPane, Nav, NavItem, NavLink } from "reactstrap"; 11 | import { Row, Col } from "reactstrap"; 12 | import { Query } from "react-apollo"; 13 | import OutletsSection from "./outlets"; 14 | import InletsSection from "./inlets"; 15 | 16 | import ServiceUpdate from "Actions/service/update"; 17 | 18 | import * as ServiceQueries from "Queries/service"; 19 | 20 | import { 21 | Alert, 22 | Card, 23 | ButtonGroup, 24 | Button, 25 | CardHeader, 26 | CardFooter, 27 | CardBody, 28 | CardTitle, 29 | CardText 30 | } from "reactstrap"; 31 | 32 | import * as OutletComps from "Comps/device/outlet"; 33 | import * as InletComps from "Comps/device/inlet"; 34 | 35 | export const IconInfo = () => ; 36 | export const IconWarning = () => ; 37 | 38 | export default ({ history, viewer }) => { 39 | const sectionProps = { 40 | history, 41 | viewer 42 | }; 43 | 44 | return ( 45 |
46 | 47 | 48 | 49 | {({ loading, error, data }) => { 50 | if (loading) return

Loading...

; 51 | if (error) return

Error: {error}

; 52 | 53 | const { 54 | viewer: { 55 | service: { messages, upgradable, needRestart } 56 | } 57 | } = data; 58 | 59 | return ( 60 |
61 | {needRestart && ( 62 | 63 | You need to restart the server side 64 | service. 65 | 66 | )} 67 | 68 | {upgradable && !needRestart && ( 69 | 70 | There is an update to the server side 71 | service. 72 | 73 | )} 74 | 75 | {_.map(messages, ({ type, message }, i) => ( 76 | 77 | {message} 78 | 79 | ))} 80 |
81 | ); 82 | }} 83 |
84 | 85 | 86 | 87 |
    88 |
  • 89 | 90 | Outlets 91 | 92 |
  • 93 | 94 |
  • 95 | 96 | Inlets 97 | 98 |
  • 99 |
100 | 101 |
102 | 103 | 104 | 105 | 106 | ( 110 |
111 |

112 | Go to the outlets and inlets sections to manage the active 113 | tunnels. 114 |

115 | 116 | 117 | If you have any question or feedback don't 118 | hesitate to open an issue or reach out to vic@repoflow.com 119 | 120 |
121 | )} 122 | /> 123 | ( 126 | 127 | )} 128 | /> 129 | ( 132 | 133 | )} 134 | /> 135 |
136 | 137 |
138 |
139 | ); 140 | }; 141 | -------------------------------------------------------------------------------- /local-boot-graph/pkg/linker-tunnel/index.js: -------------------------------------------------------------------------------- 1 | const uuidv4 = require("uuid/v4"); 2 | import _ from "lodash"; 3 | import kill from "tree-kill"; 4 | import * as OperationUtils from "PKG/linker-operation"; 5 | 6 | import { spawn } from "child-process-promise"; 7 | import * as Utils from "@nebulario/tunnel-utils"; 8 | 9 | export const listen = async (tunnelid, fwds, opts, cxt) => { 10 | const { key, user, host, port } = opts; 11 | 12 | return await frame( 13 | tunnelid, 14 | "listen", 15 | [ 16 | "-4", 17 | "-N", 18 | "-p", 19 | port, 20 | "-oStrictHostKeyChecking=no", 21 | "-oExitOnForwardFailure=yes", 22 | ..._.reduce( 23 | fwds, 24 | (res, { dest, source }) => { 25 | res.push( 26 | "-L", 27 | `${dest.host}:${dest.port}:${source.host}:${source.port}` 28 | ); 29 | return res; 30 | }, 31 | [] 32 | ), 33 | "-i", 34 | key, 35 | `${user}@${host}` 36 | ], 37 | cxt 38 | ); 39 | }; 40 | 41 | export const remote = async (tunnelid, fwds, opts, cxt) => { 42 | const { key, user, host, port } = opts; 43 | 44 | return await frame( 45 | tunnelid, 46 | "remote", 47 | [ 48 | "-N", 49 | "-p", 50 | port, 51 | "-oStrictHostKeyChecking=no", 52 | "-oExitOnForwardFailure=yes", 53 | ..._.reduce( 54 | fwds, 55 | (res, { dest, source }) => { 56 | res.push( 57 | "-R", 58 | `${dest.host}:${dest.port}:${source.host}:${source.port}` 59 | ); 60 | return res; 61 | }, 62 | [] 63 | ), 64 | "-i", 65 | key, 66 | `${user}@${host}` 67 | ], 68 | cxt 69 | ); 70 | }; 71 | 72 | const frame = async (tunnelid, mode, args, cxt) => { 73 | const op = await OperationUtils.start( 74 | tunnelid + "_op", 75 | { 76 | start: async (operation, cxt) => { 77 | cxt.logger.debug("tunnel.start", { 78 | tunnelid, 79 | mode, 80 | args 81 | }); 82 | 83 | operation.data.promise = spawn("ssh", args); 84 | 85 | cxt.logger.debug("tunnel.start.pid", { 86 | pid: operation.data.promise.childProcess.pid 87 | }); 88 | 89 | await operation.data.promise; 90 | }, 91 | stop: async (operation, cxt) => { 92 | const { 93 | data: { 94 | promise: { 95 | childProcess: { pid } 96 | } 97 | } 98 | } = operation; 99 | 100 | cxt.logger.debug("tunnel.stopping", { 101 | tunnelid, 102 | pid 103 | }); 104 | 105 | const pkill = new Promise(function(resolve, reject) { 106 | kill(pid, err => { 107 | if (err) { 108 | cxt.logger.error("tunnel.stop.error", { 109 | error: err.toString(), 110 | pid 111 | }); 112 | } 113 | 114 | cxt.logger.debug("tunnel.stopped", { 115 | tunnelid, 116 | pid 117 | }); 118 | operation.change(OperationUtils.Status.stopped); 119 | resolve(operation); 120 | }); 121 | }); 122 | 123 | await pkill; 124 | await Utils.Process.wait(500); 125 | }, 126 | retry: async (operation, error, i, cxt) => { 127 | cxt.logger.debug("tunnel.retry", { 128 | error: error ? error.toString() : "NO_ERROR", 129 | tunnelid, 130 | attempt: i, 131 | command: operation.data.command 132 | }); 133 | 134 | if (operation.data.command !== "stop" && i < 5) { 135 | await Utils.Process.wait(2500); 136 | cxt.logger.debug("tunnel.recover"); 137 | return true; 138 | } else { 139 | cxt.logger.debug("tunnel.giveup"); 140 | return false; 141 | } 142 | } 143 | }, 144 | {}, 145 | cxt 146 | ); 147 | 148 | return { id: tunnelid, tunnelid, operation: op }; 149 | }; 150 | 151 | export const stop = async (tunnel, cxt) => { 152 | tunnel.operation.data.command = "stop"; 153 | return await OperationUtils.stop(tunnel.operation, {}, cxt); 154 | }; 155 | 156 | export const forceStop = async (tunnelid, cxt) => { 157 | const op = await OperationUtils.get(tunnelid + "_op", cxt); 158 | if (op) { 159 | cxt.logger.debug("tunnel.force.stop", { tunnelid }); 160 | await OperationUtils.stop(op, {}, cxt); 161 | await Utils.Process.wait(1000); 162 | } 163 | cxt.logger.debug("tunnel.force.none", { tunnelid }); 164 | return null; 165 | }; 166 | -------------------------------------------------------------------------------- /server-boot-graph/pkg/linker-tunnel/index.js: -------------------------------------------------------------------------------- 1 | const uuidv4 = require("uuid/v4"); 2 | import _ from "lodash"; 3 | import kill from "tree-kill"; 4 | import * as OperationUtils from "PKG/linker-operation"; 5 | 6 | import { spawn } from "child-process-promise"; 7 | import * as Utils from "@nebulario/tunnel-utils"; 8 | 9 | export const listen = async (tunnelid, fwds, opts, cxt) => { 10 | const { key, user, host, port } = opts; 11 | 12 | return await frame( 13 | tunnelid, 14 | "listen", 15 | [ 16 | "-4", 17 | "-N", 18 | "-p", 19 | port, 20 | "-oStrictHostKeyChecking=no", 21 | "-oExitOnForwardFailure=yes", 22 | ..._.reduce( 23 | fwds, 24 | (res, { dest, source }) => { 25 | res.push( 26 | "-L", 27 | `${dest.host}:${dest.port}:${source.host}:${source.port}` 28 | ); 29 | return res; 30 | }, 31 | [] 32 | ), 33 | "-i", 34 | key, 35 | `${user}@${host}` 36 | ], 37 | cxt 38 | ); 39 | }; 40 | 41 | export const remote = async (tunnelid, fwds, opts, cxt) => { 42 | const { key, user, host, port } = opts; 43 | 44 | return await frame( 45 | tunnelid, 46 | "remote", 47 | [ 48 | "-N", 49 | "-p", 50 | port, 51 | "-oStrictHostKeyChecking=no", 52 | "-oExitOnForwardFailure=yes", 53 | ..._.reduce( 54 | fwds, 55 | (res, { dest, source }) => { 56 | res.push( 57 | "-R", 58 | `${dest.host}:${dest.port}:${source.host}:${source.port}` 59 | ); 60 | return res; 61 | }, 62 | [] 63 | ), 64 | "-i", 65 | key, 66 | `${user}@${host}` 67 | ], 68 | cxt 69 | ); 70 | }; 71 | 72 | const frame = async (tunnelid, mode, args, cxt) => { 73 | const op = await OperationUtils.start( 74 | tunnelid + "_op", 75 | { 76 | start: async (operation, cxt) => { 77 | cxt.logger.debug("tunnel.start", { 78 | tunnelid, 79 | mode, 80 | args 81 | }); 82 | 83 | operation.data.promise = spawn("ssh", args); 84 | 85 | cxt.logger.debug("tunnel.start.pid", { 86 | pid: operation.data.promise.childProcess.pid 87 | }); 88 | 89 | await operation.data.promise; 90 | }, 91 | stop: async (operation, cxt) => { 92 | const { 93 | data: { 94 | promise: { 95 | childProcess: { pid } 96 | } 97 | } 98 | } = operation; 99 | 100 | cxt.logger.debug("tunnel.stopping", { 101 | tunnelid, 102 | pid 103 | }); 104 | 105 | const pkill = new Promise(function(resolve, reject) { 106 | kill(pid, err => { 107 | if (err) { 108 | cxt.logger.error("tunnel.stop.error", { 109 | error: err.toString(), 110 | pid 111 | }); 112 | } 113 | 114 | cxt.logger.debug("tunnel.stopped", { 115 | tunnelid, 116 | pid 117 | }); 118 | operation.change(OperationUtils.Status.stopped); 119 | resolve(operation); 120 | }); 121 | }); 122 | 123 | await pkill; 124 | await Utils.Process.wait(500); 125 | }, 126 | retry: async (operation, error, i, cxt) => { 127 | cxt.logger.debug("tunnel.retry", { 128 | error: error ? error.toString() : "NO_ERROR", 129 | tunnelid, 130 | attempt: i, 131 | command: operation.data.command 132 | }); 133 | 134 | if (operation.data.command !== "stop" && i < 5) { 135 | await Utils.Process.wait(2500); 136 | cxt.logger.debug("tunnel.recover"); 137 | return true; 138 | } else { 139 | cxt.logger.debug("tunnel.giveup"); 140 | return false; 141 | } 142 | } 143 | }, 144 | {}, 145 | cxt 146 | ); 147 | 148 | return { id: tunnelid, tunnelid, operation: op }; 149 | }; 150 | 151 | export const stop = async (tunnel, cxt) => { 152 | tunnel.operation.data.command = "stop"; 153 | return await OperationUtils.stop(tunnel.operation, {}, cxt); 154 | }; 155 | 156 | export const forceStop = async (tunnelid, cxt) => { 157 | const op = await OperationUtils.get(tunnelid + "_op", cxt); 158 | if (op) { 159 | cxt.logger.debug("tunnel.force.stop", { tunnelid }); 160 | await OperationUtils.stop(op, {}, cxt); 161 | await Utils.Process.wait(1000); 162 | } 163 | cxt.logger.debug("tunnel.force.none", { tunnelid }); 164 | return null; 165 | }; 166 | -------------------------------------------------------------------------------- /local-boot-graph/pkg/linker-operation/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | const uuidv4 = require("uuid/v4"); 3 | import * as Utils from "@nebulario/tunnel-utils"; 4 | export const OPERATION_TABLE = []; 5 | export const OPERATION_INDEX = {}; 6 | 7 | export const keepWhile = async (tag, fn, cxt) => { 8 | let i = 0; 9 | while (fn()) { 10 | await Utils.Process.wait(10); 11 | if (i++ > 10000) { 12 | i = 0; 13 | cxt.logger.debug(tag, {}); 14 | } 15 | } 16 | }; 17 | 18 | export const Status = { 19 | init: "init", 20 | running: "running", 21 | stopping: "stopping", 22 | stopped: "stopped" 23 | }; 24 | 25 | function change(status) { 26 | this.cxt.logger.debug("operation.status", { 27 | operationid: this.operationid, 28 | from: this.status, 29 | to: status 30 | }); 31 | this.status = status; 32 | } 33 | 34 | export const start = async (opid, handler, opts, cxt) => { 35 | const id = uuidv4(); 36 | const operationid = opid ? opid : id; 37 | 38 | if (OPERATION_INDEX[operationid]) { 39 | throw new Error("operation.exists", { operationid }); 40 | } 41 | const op = { 42 | id, 43 | operationid, 44 | handler, 45 | status: Status.init, 46 | data: {}, 47 | cxt, 48 | error: null, 49 | attempt: 0, 50 | stopped: false, 51 | retry: true 52 | }; 53 | op.change = change.bind(op); 54 | 55 | OPERATION_TABLE.push(op); 56 | OPERATION_INDEX[operationid] = op; 57 | 58 | (async () => { 59 | while (op.retry) { 60 | cxt.logger.debug("operation.start", { 61 | operationid, 62 | attempt: op.attempt 63 | }); 64 | 65 | op.retry = false; 66 | op.attempt = 0; 67 | op.change(Status.running); 68 | 69 | op.handler 70 | .start(op, cxt) 71 | .then(() => {}) 72 | .catch(e => { 73 | op.error = e; 74 | cxt.logger.error("operation.start.error", { 75 | operationid, 76 | error: e.toString() 77 | }); 78 | }) 79 | .finally(() => { 80 | op.change(Status.stopped); 81 | }); 82 | 83 | await keepWhile( 84 | `operation.wait.${op.operationid}`, 85 | () => op.status !== Status.stopped, 86 | cxt 87 | ); 88 | 89 | if (op.handler.stop) { 90 | try { 91 | await op.handler.stop(op, cxt); 92 | } catch (e) { 93 | cxt.logger.error("operation.stopping.error", { 94 | operationid, 95 | error: e.toString() 96 | }); 97 | } 98 | } 99 | 100 | if (op.stopped === false && op.handler.retry) { 101 | cxt.logger.debug("operation.retry.handler", { 102 | operationid 103 | }); 104 | 105 | const res = await op.handler.retry(op, op.error, op.attempt, cxt); 106 | if (res === true) { 107 | op.attempt++; 108 | op.retry = true; 109 | op.error = null; 110 | } 111 | } 112 | } 113 | })() 114 | .catch(function(e) { 115 | op.error = e; 116 | cxt.logger.error("operation.loop.error", { 117 | operationid, 118 | error: e.toString() 119 | }); 120 | }) 121 | .finally(function() { 122 | cxt.logger.debug("operation.finished", { 123 | operationid 124 | }); 125 | OPERATION_INDEX[op.operationid + ":" + op.id + ":archive"] = op; 126 | delete OPERATION_INDEX[op.operationid]; 127 | }); 128 | 129 | return op; 130 | }; 131 | 132 | export const stop = async (op, { wait = true }, cxt) => { 133 | op.stopped = true; 134 | if (!op || op.status === Status.stopped || op.status === Status.stopping) { 135 | return op; 136 | } 137 | 138 | cxt.logger.debug("operation.stop", { 139 | operationid: op.operationid 140 | }); 141 | 142 | if (!op.handler.stop) { 143 | op.change(Status.stopped); 144 | } else { 145 | op.change(Status.stopping); 146 | const stopPms = op.handler 147 | .stop(op, cxt) 148 | .catch(function(e) { 149 | op.error = e; 150 | cxt.logger.error("operation.stop.error", { 151 | operationid: op.operationid, 152 | error: e.toString() 153 | }); 154 | }) 155 | .finally(() => { 156 | op.change(Status.stopped); 157 | }); 158 | 159 | const outStopPms = (async () => { 160 | await stopPms; 161 | })(); 162 | 163 | if (wait) { 164 | await outStopPms; 165 | } 166 | } 167 | 168 | return op; 169 | }; 170 | 171 | export const get = (operationid, cxt) => OPERATION_INDEX[operationid] || null; 172 | 173 | export const list = ({ status }, cxt) => { 174 | return _.filter(OPERATION_TABLE, op => { 175 | if (status) { 176 | if (status === op.status) { 177 | return true; 178 | } else { 179 | return false; 180 | } 181 | } 182 | 183 | return true; 184 | }); 185 | }; 186 | -------------------------------------------------------------------------------- /local-worker-graph/pkg/linker-operation/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | const uuidv4 = require("uuid/v4"); 3 | import * as Utils from "@nebulario/tunnel-utils"; 4 | export const OPERATION_TABLE = []; 5 | export const OPERATION_INDEX = {}; 6 | 7 | export const keepWhile = async (tag, fn, cxt) => { 8 | let i = 0; 9 | while (fn()) { 10 | await Utils.Process.wait(10); 11 | if (i++ > 10000) { 12 | i = 0; 13 | cxt.logger.debug(tag, {}); 14 | } 15 | } 16 | }; 17 | 18 | export const Status = { 19 | init: "init", 20 | running: "running", 21 | stopping: "stopping", 22 | stopped: "stopped" 23 | }; 24 | 25 | function change(status) { 26 | this.cxt.logger.debug("operation.status", { 27 | operationid: this.operationid, 28 | from: this.status, 29 | to: status 30 | }); 31 | this.status = status; 32 | } 33 | 34 | export const start = async (opid, handler, opts, cxt) => { 35 | const id = uuidv4(); 36 | const operationid = opid ? opid : id; 37 | 38 | if (OPERATION_INDEX[operationid]) { 39 | throw new Error("operation.exists", { operationid }); 40 | } 41 | const op = { 42 | id, 43 | operationid, 44 | handler, 45 | status: Status.init, 46 | data: {}, 47 | cxt, 48 | error: null, 49 | attempt: 0, 50 | stopped: false, 51 | retry: true 52 | }; 53 | op.change = change.bind(op); 54 | 55 | OPERATION_TABLE.push(op); 56 | OPERATION_INDEX[operationid] = op; 57 | 58 | (async () => { 59 | while (op.retry) { 60 | cxt.logger.debug("operation.start", { 61 | operationid, 62 | attempt: op.attempt 63 | }); 64 | 65 | op.retry = false; 66 | op.attempt = 0; 67 | op.change(Status.running); 68 | 69 | op.handler 70 | .start(op, cxt) 71 | .then(() => {}) 72 | .catch(e => { 73 | op.error = e; 74 | cxt.logger.error("operation.start.error", { 75 | operationid, 76 | error: e.toString() 77 | }); 78 | }) 79 | .finally(() => { 80 | op.change(Status.stopped); 81 | }); 82 | 83 | await keepWhile( 84 | `operation.wait.${op.operationid}`, 85 | () => op.status !== Status.stopped, 86 | cxt 87 | ); 88 | 89 | if (op.handler.stop) { 90 | try { 91 | await op.handler.stop(op, cxt); 92 | } catch (e) { 93 | cxt.logger.error("operation.stopping.error", { 94 | operationid, 95 | error: e.toString() 96 | }); 97 | } 98 | } 99 | 100 | if (op.stopped === false && op.handler.retry) { 101 | cxt.logger.debug("operation.retry.handler", { 102 | operationid 103 | }); 104 | 105 | const res = await op.handler.retry(op, op.error, op.attempt, cxt); 106 | if (res === true) { 107 | op.attempt++; 108 | op.retry = true; 109 | op.error = null; 110 | } 111 | } 112 | } 113 | })() 114 | .catch(function(e) { 115 | op.error = e; 116 | cxt.logger.error("operation.loop.error", { 117 | operationid, 118 | error: e.toString() 119 | }); 120 | }) 121 | .finally(function() { 122 | cxt.logger.debug("operation.finished", { 123 | operationid 124 | }); 125 | OPERATION_INDEX[op.operationid + ":" + op.id + ":archive"] = op; 126 | delete OPERATION_INDEX[op.operationid]; 127 | }); 128 | 129 | return op; 130 | }; 131 | 132 | export const stop = async (op, { wait = true }, cxt) => { 133 | op.stopped = true; 134 | if (!op || op.status === Status.stopped || op.status === Status.stopping) { 135 | return op; 136 | } 137 | 138 | cxt.logger.debug("operation.stop", { 139 | operationid: op.operationid 140 | }); 141 | 142 | if (!op.handler.stop) { 143 | op.change(Status.stopped); 144 | } else { 145 | op.change(Status.stopping); 146 | const stopPms = op.handler 147 | .stop(op, cxt) 148 | .catch(function(e) { 149 | op.error = e; 150 | cxt.logger.error("operation.stop.error", { 151 | operationid: op.operationid, 152 | error: e.toString() 153 | }); 154 | }) 155 | .finally(() => { 156 | op.change(Status.stopped); 157 | }); 158 | 159 | const outStopPms = (async () => { 160 | await stopPms; 161 | })(); 162 | 163 | if (wait) { 164 | await outStopPms; 165 | } 166 | } 167 | 168 | return op; 169 | }; 170 | 171 | export const get = (operationid, cxt) => OPERATION_INDEX[operationid] || null; 172 | 173 | export const list = ({ status }, cxt) => { 174 | return _.filter(OPERATION_TABLE, op => { 175 | if (status) { 176 | if (status === op.status) { 177 | return true; 178 | } else { 179 | return false; 180 | } 181 | } 182 | 183 | return true; 184 | }); 185 | }; 186 | -------------------------------------------------------------------------------- /server-boot-graph/pkg/linker-operation/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | const uuidv4 = require("uuid/v4"); 3 | import * as Utils from "@nebulario/tunnel-utils"; 4 | export const OPERATION_TABLE = []; 5 | export const OPERATION_INDEX = {}; 6 | 7 | export const keepWhile = async (tag, fn, cxt) => { 8 | let i = 0; 9 | while (fn()) { 10 | await Utils.Process.wait(10); 11 | if (i++ > 10000) { 12 | i = 0; 13 | cxt.logger.debug(tag, {}); 14 | } 15 | } 16 | }; 17 | 18 | export const Status = { 19 | init: "init", 20 | running: "running", 21 | stopping: "stopping", 22 | stopped: "stopped" 23 | }; 24 | 25 | function change(status) { 26 | this.cxt.logger.debug("operation.status", { 27 | operationid: this.operationid, 28 | from: this.status, 29 | to: status 30 | }); 31 | this.status = status; 32 | } 33 | 34 | export const start = async (opid, handler, opts, cxt) => { 35 | const id = uuidv4(); 36 | const operationid = opid ? opid : id; 37 | 38 | if (OPERATION_INDEX[operationid]) { 39 | throw new Error("operation.exists", { operationid }); 40 | } 41 | const op = { 42 | id, 43 | operationid, 44 | handler, 45 | status: Status.init, 46 | data: {}, 47 | cxt, 48 | error: null, 49 | attempt: 0, 50 | stopped: false, 51 | retry: true 52 | }; 53 | op.change = change.bind(op); 54 | 55 | OPERATION_TABLE.push(op); 56 | OPERATION_INDEX[operationid] = op; 57 | 58 | (async () => { 59 | while (op.retry) { 60 | cxt.logger.debug("operation.start", { 61 | operationid, 62 | attempt: op.attempt 63 | }); 64 | 65 | op.retry = false; 66 | op.attempt = 0; 67 | op.change(Status.running); 68 | 69 | op.handler 70 | .start(op, cxt) 71 | .then(() => {}) 72 | .catch(e => { 73 | op.error = e; 74 | cxt.logger.error("operation.start.error", { 75 | operationid, 76 | error: e.toString() 77 | }); 78 | }) 79 | .finally(() => { 80 | op.change(Status.stopped); 81 | }); 82 | 83 | await keepWhile( 84 | `operation.wait.${op.operationid}`, 85 | () => op.status !== Status.stopped, 86 | cxt 87 | ); 88 | 89 | if (op.handler.stop) { 90 | try { 91 | await op.handler.stop(op, cxt); 92 | } catch (e) { 93 | cxt.logger.error("operation.stopping.error", { 94 | operationid, 95 | error: e.toString() 96 | }); 97 | } 98 | } 99 | 100 | if (op.stopped === false && op.handler.retry) { 101 | cxt.logger.debug("operation.retry.handler", { 102 | operationid 103 | }); 104 | 105 | const res = await op.handler.retry(op, op.error, op.attempt, cxt); 106 | if (res === true) { 107 | op.attempt++; 108 | op.retry = true; 109 | op.error = null; 110 | } 111 | } 112 | } 113 | })() 114 | .catch(function(e) { 115 | op.error = e; 116 | cxt.logger.error("operation.loop.error", { 117 | operationid, 118 | error: e.toString() 119 | }); 120 | }) 121 | .finally(function() { 122 | cxt.logger.debug("operation.finished", { 123 | operationid 124 | }); 125 | OPERATION_INDEX[op.operationid + ":" + op.id + ":archive"] = op; 126 | delete OPERATION_INDEX[op.operationid]; 127 | }); 128 | 129 | return op; 130 | }; 131 | 132 | export const stop = async (op, { wait = true }, cxt) => { 133 | op.stopped = true; 134 | if (!op || op.status === Status.stopped || op.status === Status.stopping) { 135 | return op; 136 | } 137 | 138 | cxt.logger.debug("operation.stop", { 139 | operationid: op.operationid 140 | }); 141 | 142 | if (!op.handler.stop) { 143 | op.change(Status.stopped); 144 | } else { 145 | op.change(Status.stopping); 146 | const stopPms = op.handler 147 | .stop(op, cxt) 148 | .catch(function(e) { 149 | op.error = e; 150 | cxt.logger.error("operation.stop.error", { 151 | operationid: op.operationid, 152 | error: e.toString() 153 | }); 154 | }) 155 | .finally(() => { 156 | op.change(Status.stopped); 157 | }); 158 | 159 | const outStopPms = (async () => { 160 | await stopPms; 161 | })(); 162 | 163 | if (wait) { 164 | await outStopPms; 165 | } 166 | } 167 | 168 | return op; 169 | }; 170 | 171 | export const get = (operationid, cxt) => OPERATION_INDEX[operationid] || null; 172 | 173 | export const list = ({ status }, cxt) => { 174 | return _.filter(OPERATION_TABLE, op => { 175 | if (status) { 176 | if (status === op.status) { 177 | return true; 178 | } else { 179 | return false; 180 | } 181 | } 182 | 183 | return true; 184 | }); 185 | }; 186 | -------------------------------------------------------------------------------- /server-worker-graph/pkg/linker-operation/index.js: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | const uuidv4 = require("uuid/v4"); 3 | import * as Utils from "@nebulario/tunnel-utils"; 4 | export const OPERATION_TABLE = []; 5 | export const OPERATION_INDEX = {}; 6 | 7 | export const keepWhile = async (tag, fn, cxt) => { 8 | let i = 0; 9 | while (fn()) { 10 | await Utils.Process.wait(10); 11 | if (i++ > 10000) { 12 | i = 0; 13 | cxt.logger.debug(tag, {}); 14 | } 15 | } 16 | }; 17 | 18 | export const Status = { 19 | init: "init", 20 | running: "running", 21 | stopping: "stopping", 22 | stopped: "stopped" 23 | }; 24 | 25 | function change(status) { 26 | this.cxt.logger.debug("operation.status", { 27 | operationid: this.operationid, 28 | from: this.status, 29 | to: status 30 | }); 31 | this.status = status; 32 | } 33 | 34 | export const start = async (opid, handler, opts, cxt) => { 35 | const id = uuidv4(); 36 | const operationid = opid ? opid : id; 37 | 38 | if (OPERATION_INDEX[operationid]) { 39 | throw new Error("operation.exists", { operationid }); 40 | } 41 | const op = { 42 | id, 43 | operationid, 44 | handler, 45 | status: Status.init, 46 | data: {}, 47 | cxt, 48 | error: null, 49 | attempt: 0, 50 | stopped: false, 51 | retry: true 52 | }; 53 | op.change = change.bind(op); 54 | 55 | OPERATION_TABLE.push(op); 56 | OPERATION_INDEX[operationid] = op; 57 | 58 | (async () => { 59 | while (op.retry) { 60 | cxt.logger.debug("operation.start", { 61 | operationid, 62 | attempt: op.attempt 63 | }); 64 | 65 | op.retry = false; 66 | op.attempt = 0; 67 | op.change(Status.running); 68 | 69 | op.handler 70 | .start(op, cxt) 71 | .then(() => {}) 72 | .catch(e => { 73 | op.error = e; 74 | cxt.logger.error("operation.start.error", { 75 | operationid, 76 | error: e.toString() 77 | }); 78 | }) 79 | .finally(() => { 80 | op.change(Status.stopped); 81 | }); 82 | 83 | await keepWhile( 84 | `operation.wait.${op.operationid}`, 85 | () => op.status !== Status.stopped, 86 | cxt 87 | ); 88 | 89 | if (op.handler.stop) { 90 | try { 91 | await op.handler.stop(op, cxt); 92 | } catch (e) { 93 | cxt.logger.error("operation.stopping.error", { 94 | operationid, 95 | error: e.toString() 96 | }); 97 | } 98 | } 99 | 100 | if (op.stopped === false && op.handler.retry) { 101 | cxt.logger.debug("operation.retry.handler", { 102 | operationid 103 | }); 104 | 105 | const res = await op.handler.retry(op, op.error, op.attempt, cxt); 106 | if (res === true) { 107 | op.attempt++; 108 | op.retry = true; 109 | op.error = null; 110 | } 111 | } 112 | } 113 | })() 114 | .catch(function(e) { 115 | op.error = e; 116 | cxt.logger.error("operation.loop.error", { 117 | operationid, 118 | error: e.toString() 119 | }); 120 | }) 121 | .finally(function() { 122 | cxt.logger.debug("operation.finished", { 123 | operationid 124 | }); 125 | OPERATION_INDEX[op.operationid + ":" + op.id + ":archive"] = op; 126 | delete OPERATION_INDEX[op.operationid]; 127 | }); 128 | 129 | return op; 130 | }; 131 | 132 | export const stop = async (op, { wait = true }, cxt) => { 133 | op.stopped = true; 134 | if (!op || op.status === Status.stopped || op.status === Status.stopping) { 135 | return op; 136 | } 137 | 138 | cxt.logger.debug("operation.stop", { 139 | operationid: op.operationid 140 | }); 141 | 142 | if (!op.handler.stop) { 143 | op.change(Status.stopped); 144 | } else { 145 | op.change(Status.stopping); 146 | const stopPms = op.handler 147 | .stop(op, cxt) 148 | .catch(function(e) { 149 | op.error = e; 150 | cxt.logger.error("operation.stop.error", { 151 | operationid: op.operationid, 152 | error: e.toString() 153 | }); 154 | }) 155 | .finally(() => { 156 | op.change(Status.stopped); 157 | }); 158 | 159 | const outStopPms = (async () => { 160 | await stopPms; 161 | })(); 162 | 163 | if (wait) { 164 | await outStopPms; 165 | } 166 | } 167 | 168 | return op; 169 | }; 170 | 171 | export const get = (operationid, cxt) => OPERATION_INDEX[operationid] || null; 172 | 173 | export const list = ({ status }, cxt) => { 174 | return _.filter(OPERATION_TABLE, op => { 175 | if (status) { 176 | if (status === op.status) { 177 | return true; 178 | } else { 179 | return false; 180 | } 181 | } 182 | 183 | return true; 184 | }); 185 | }; 186 | --------------------------------------------------------------------------------