├── 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 |
18 | Cancel
19 |
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 | {
44 | e.preventDefault();
45 | upgrade();
46 | }}
47 | >
48 | {busy ? (
49 |
50 | ) : (
51 |
52 | Update
53 |
54 | )}
55 |
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 | {
46 | e.preventDefault();
47 | upgrade({ variables: { deviceid } });
48 | }}
49 | >
50 | {busy ? (
51 |
52 | ) : (
53 |
54 | Update
55 |
56 | )}
57 |
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 | {
36 | open();
37 | }}
38 | >
39 | {icon ? icon : }
40 |
41 |
42 |
43 | {title}
44 | {children}
45 |
46 | {
49 | close();
50 | }}
51 | >
52 | Close
53 |
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 | {
49 | e.preventDefault();
50 | stop({
51 | variables: { deviceid: device.deviceid, inletid: inlet.inletid }
52 | });
53 | }}
54 | >
55 | {busy ? (
56 |
57 | ) : (
58 |
59 |
60 |
61 | )}
62 |
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 | {
48 | e.preventDefault();
49 | stop({
50 | variables: { deviceid: device.deviceid, outletid: outlet.outletid }
51 | });
52 | }}
53 | >
54 | {busy ? (
55 |
56 | ) : (
57 |
58 |
59 |
60 | )}
61 |
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 | {
49 | e.preventDefault();
50 | start({
51 | variables: { deviceid: device.deviceid, inletid: inlet.inletid }
52 | });
53 | }}
54 | >
55 | {busy ? (
56 |
57 | ) : (
58 |
59 |
60 |
61 | )}
62 |
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 | {
49 | e.preventDefault();
50 | start({
51 | variables: { deviceid: device.deviceid, outletid: outlet.outletid }
52 | });
53 | }}
54 | >
55 | {busy ? (
56 |
57 | ) : (
58 |
59 |
60 |
61 | )}
62 |
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 | {
48 | e.preventDefault();
49 | open();
50 | }}
51 | >
52 | {mutationLoading ? : }
53 |
54 | )}
55 | confirm={({ close }) => (
56 | {
59 | e.preventDefault();
60 | remove({
61 | variables: { deviceid: device.deviceid, outletid }
62 | });
63 | close();
64 | }}
65 | >
66 | Remove
67 |
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 | {
54 | e.preventDefault();
55 | open();
56 | }}
57 | >
58 | {mutationLoading ? : }
59 |
60 | )}
61 | confirm={({ close }) => (
62 | {
65 | e.preventDefault();
66 | remove({
67 | variables: { deviceid: device.deviceid, inletid }
68 | });
69 | close();
70 | }}
71 | >
72 | Remove
73 |
74 | )}
75 | body={
76 |
77 | Remove the inlet from the device?
78 |
79 | }
80 | />
81 | );
82 |
83 | return (
84 | {
89 | e.preventDefault();
90 | remove({ variables: { deviceid: device.deviceid, inletid } });
91 | }}
92 | >
93 | {mutationLoading ? (
94 |
95 | ) : (
96 |
97 |
98 |
99 | )}
100 |
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 |
--------------------------------------------------------------------------------