@things-factory/edge 8.0.0 → 9.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/edge",
3
- "version": "8.0.0",
3
+ "version": "9.0.0-beta.3",
4
4
  "main": "dist-server/index.js",
5
5
  "browser": "dist-client/index.js",
6
6
  "things-factory": true,
@@ -27,13 +27,13 @@
27
27
  "migration:create": "node ../../node_modules/typeorm/cli.js migration:create ./server/migrations/migration"
28
28
  },
29
29
  "dependencies": {
30
- "@operato/graphql": "^8.0.0",
31
- "@operato/shell": "^8.0.0",
32
- "@things-factory/auth-base": "^8.0.0",
33
- "@things-factory/env": "^8.0.0",
34
- "@things-factory/integration-base": "^8.0.0",
35
- "@things-factory/shell": "^8.0.0",
30
+ "@operato/graphql": "^9.0.0-beta",
31
+ "@operato/shell": "^9.0.0-beta",
32
+ "@things-factory/auth-base": "^9.0.0-beta.3",
33
+ "@things-factory/env": "^8.0.0-beta.4",
34
+ "@things-factory/integration-base": "^9.0.0-beta.3",
35
+ "@things-factory/shell": "^9.0.0-beta.0",
36
36
  "cross-fetch": "^3.0.4"
37
37
  },
38
- "gitHead": "07ef27d272dd9a067a9648ac7013748510556a18"
38
+ "gitHead": "1d7e0dd4c88f3c3f3bd311c00e4b1d1542d53634"
39
39
  }
@@ -1 +0,0 @@
1
- export default function bootstrap() {}
package/client/index.ts DELETED
File without changes
@@ -1,173 +0,0 @@
1
- import '@operato/data-grist'
2
- import '@operato/context/ox-context-page-toolbar.js'
3
- import '@operato/input/ox-input-select-buttons.js'
4
-
5
- import { PropertyValues, css, html } from 'lit'
6
- import { customElement, property, query, state } from 'lit/decorators.js'
7
- import { connect } from 'pwa-helpers/connect-mixin.js'
8
- import gql from 'graphql-tag'
9
-
10
- import { store, PageView } from '@operato/shell'
11
- import { client } from '@operato/graphql'
12
- import { DataGrist, FetchOption, GristRecord } from '@operato/data-grist'
13
- import { i18next, localize } from '@operato/i18n'
14
- import { openPopup } from '@operato/layout'
15
- import { CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'
16
- import { isMobileDevice } from '@operato/utils'
17
-
18
- @customElement('edge-status-page')
19
- export class EdgeStatusPage extends connect(store)(localize(i18next)(PageView)) {
20
- static styles = [
21
- ScrollbarStyles,
22
- CommonHeaderStyles,
23
- css`
24
- :host {
25
- display: flex;
26
- flex-direction: column;
27
-
28
- overflow: hidden;
29
-
30
- --grid-header-padding: 2px 0 2px 9px;
31
- }
32
-
33
- ox-grist {
34
- overflow-y: auto;
35
- flex: 1;
36
- }
37
- `
38
- ]
39
-
40
- @state() private gristConfig: any
41
- @state() private mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'CARD' : 'GRID'
42
- @state() private state?: string[]
43
-
44
- @query('ox-grist') private grist!: DataGrist
45
-
46
- get context() {
47
- return {
48
- title: i18next.t('title.edge-status'),
49
- search: {
50
- handler: (search: string) => {
51
- this.grist.searchText = search
52
- },
53
- value: this.grist?.searchText || ''
54
- },
55
- filter: {
56
- handler: () => {
57
- this.grist.toggleHeadroom()
58
- }
59
- },
60
- help: 'edge/edge-status',
61
- actions: [],
62
- toolbar: false
63
- }
64
- }
65
-
66
- render() {
67
- const mode = this.mode || (isMobileDevice() ? 'LIST' : 'GRID')
68
-
69
- return html`
70
- <ox-grist .mode=${mode} .config=${this.gristConfig} .fetchHandler=${this.fetchHandler.bind(this)}>
71
- <div slot="headroom" class="header">
72
- <div class="title">${i18next.t('label.connections')}</div>
73
-
74
- <ox-context-page-toolbar class="actions" .context=${this.context}></ox-context-page-toolbar>
75
- </div>
76
- </ox-grist>
77
- `
78
- }
79
-
80
- async pageInitialized(lifecycle) {
81
- this.gristConfig = {
82
- list: {
83
- fields: ['name', 'description'],
84
- details: ['active', 'updatedAt']
85
- },
86
- columns: [
87
- { type: 'gutter', gutterName: 'sequence' },
88
- {
89
- type: 'string',
90
- name: 'domain',
91
- header: i18next.t('field.domain'),
92
- record: {
93
- renderer: value => value.name
94
- },
95
- width: 200
96
- },
97
- {
98
- type: 'string',
99
- name: 'type',
100
- header: i18next.t('field.type'),
101
- width: 150
102
- },
103
- {
104
- type: 'string',
105
- name: 'name',
106
- header: i18next.t('field.name'),
107
- width: 150
108
- },
109
- {
110
- type: 'string',
111
- name: 'description',
112
- header: i18next.t('field.description'),
113
- width: 200
114
- },
115
- {
116
- type: 'checkbox',
117
- name: 'active',
118
- label: true,
119
- header: i18next.t('field.active'),
120
- width: 60
121
- },
122
- {
123
- type: 'string',
124
- name: 'state',
125
- label: true,
126
- header: i18next.t('field.status'),
127
- width: 100
128
- }
129
- ],
130
- rows: {
131
- appendable: false,
132
- selectable: false,
133
- handlers: {}
134
- },
135
- pagination: { infinite: true },
136
- sorters: []
137
- }
138
- }
139
-
140
- async pageUpdated(changes: any, lifecycle: any) {
141
- if (this.active) {
142
- // do something here when this page just became as active
143
- }
144
- }
145
-
146
- async fetchHandler() {
147
- const response = await client.query({
148
- query: gql`
149
- query {
150
- responses: connectionsOnEdge {
151
- domain {
152
- id
153
- name
154
- subdomain
155
- }
156
- id
157
- type
158
- name
159
- description
160
- endpoint
161
- active
162
- state
163
- }
164
- }
165
- `
166
- })
167
-
168
- return {
169
- total: response.data.responses.length || 0,
170
- records: response.data.responses || []
171
- }
172
- }
173
- }
package/client/route.ts DELETED
@@ -1,7 +0,0 @@
1
- export default function route(page: string) {
2
- switch (page) {
3
- case 'edge-status-page':
4
- import('./pages/edge-status-page')
5
- return page
6
- }
7
- }
@@ -1,11 +0,0 @@
1
- {
2
- "extends": "../../tsconfig-base.json",
3
- "compilerOptions": {
4
- "strict": true,
5
- "declaration": true,
6
- "module": "esnext",
7
- "outDir": "../dist-client",
8
- "baseUrl": "./"
9
- },
10
- "include": ["./**/*"]
11
- }
@@ -1,84 +0,0 @@
1
- import gql from 'graphql-tag'
2
-
3
- import { logger } from '@things-factory/env'
4
- import { Connection, ConnectionManager, ConnectionStatus } from '@things-factory/integration-base'
5
-
6
- import { getOperatoClient } from './operato-client'
7
- import { LogAggregator } from './log-aggregator'
8
-
9
- export async function initConnectConnectionsSubscription() {
10
- const client = await getOperatoClient()
11
-
12
- const subscription = client.subscribe({
13
- query: gql`
14
- subscription {
15
- connectConnections {
16
- domain {
17
- id
18
- name
19
- subdomain
20
- }
21
- id
22
- type
23
- name
24
- description
25
- endpoint
26
- active
27
- state
28
- params
29
- }
30
- }
31
- `
32
- })
33
-
34
- subscription.subscribe({
35
- next: async subscription => {
36
- const connections = subscription.data?.connectConnections || []
37
- await connectConnections(
38
- connections.map(connection => {
39
- try {
40
- const params = JSON.parse(connection.params)
41
-
42
- return {
43
- ...connection,
44
- params
45
- }
46
- } catch (e) {
47
- return {
48
- ...connection,
49
- params: null
50
- }
51
- }
52
- })
53
- )
54
- },
55
- error: error => {
56
- logger.error(`operato subscription error: ${error}`)
57
- },
58
- complete: () => {
59
- logger.info(`operato subscription complete`)
60
- }
61
- })
62
- }
63
-
64
- async function connectConnections(connections: Connection[]) {
65
- console.log('connectConnections ...', connections)
66
-
67
- connections.forEach(connection => connectConnection(connection))
68
- }
69
-
70
- async function connectConnection(connection: Connection) {
71
- const { type } = connection
72
-
73
- const old = ConnectionManager.getConnectionInstance(connection)
74
- if (old) {
75
- ConnectionManager.removeConnectionInstance(connection)
76
- }
77
-
78
- const connector = ConnectionManager.getConnector(type)
79
- if (!connector) {
80
- return
81
- }
82
-
83
- await connector.connect(connection)
84
- }
@@ -1,77 +0,0 @@
1
- import gql from 'graphql-tag'
2
-
3
- import { logger } from '@things-factory/env'
4
- import { Connection, ConnectionManager, ConnectionStatus } from '@things-factory/integration-base'
5
-
6
- import { getOperatoClient } from './operato-client'
7
- import { LogAggregator } from './log-aggregator'
8
-
9
- export async function initDisconnectConnectionsSubscription() {
10
- const client = await getOperatoClient()
11
-
12
- const subscription = client.subscribe({
13
- query: gql`
14
- subscription {
15
- disconnectConnections {
16
- domain {
17
- id
18
- name
19
- subdomain
20
- }
21
- id
22
- type
23
- name
24
- description
25
- endpoint
26
- active
27
- state
28
- params
29
- }
30
- }
31
- `
32
- })
33
-
34
- subscription.subscribe({
35
- next: async subscription => {
36
- const connections = subscription.data?.disconnectConnections || []
37
- await disconnectConnections(
38
- connections.map(connection => {
39
- try {
40
- const params = JSON.parse(connection.params)
41
-
42
- return {
43
- ...connection,
44
- params
45
- }
46
- } catch (e) {
47
- return {
48
- ...connection,
49
- params: null
50
- }
51
- }
52
- })
53
- )
54
- },
55
- error: error => {
56
- logger.error(`operato subscription error: ${error}`)
57
- },
58
- complete: () => {
59
- logger.info(`operato subscription complete`)
60
- }
61
- })
62
- }
63
-
64
- async function disconnectConnections(connections: Connection[]) {
65
- console.log('disconnectConnections ...', connections)
66
-
67
- connections.forEach(connection => disconnectConnection(connection))
68
- }
69
-
70
- async function disconnectConnection(connection: Connection) {
71
- const { type } = connection
72
-
73
- const old = ConnectionManager.getConnectionInstance(connection)
74
- if (old) {
75
- ConnectionManager.removeConnectionInstance(connection)
76
- }
77
- }
@@ -1,14 +0,0 @@
1
- import TransportStream from 'winston-transport'
2
-
3
- export class LogAggregator extends TransportStream {
4
- private logs: any[] = []
5
-
6
- log(info, callback) {
7
- this.logs.push(info)
8
- callback()
9
- }
10
-
11
- formatResults() {
12
- return this.logs
13
- }
14
- }
@@ -1,108 +0,0 @@
1
- import 'cross-fetch/polyfill'
2
-
3
- import { ApolloClient, InMemoryCache, createHttpLink, split } from '@apollo/client/core'
4
- import { setContext } from '@apollo/client/link/context'
5
-
6
- import WebSocket from 'ws'
7
- import { createClient } from 'graphql-ws'
8
- import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
9
- import { getMainDefinition } from '@apollo/client/utilities'
10
-
11
- import { Domain, getRepository } from '@things-factory/shell'
12
- import { User } from '@things-factory/auth-base'
13
- import { config } from '@things-factory/env'
14
-
15
- const OperatoConfig = config.get('operato', {})
16
- const { endpoint: uri, domain: subdomain, authKey } = OperatoConfig
17
-
18
- const defaultOptions: any = {
19
- watchQuery: {
20
- fetchPolicy: 'no-cache',
21
- errorPolicy: 'ignore'
22
- },
23
- query: {
24
- fetchPolicy: 'no-cache', //'network-only'
25
- errorPolicy: 'all'
26
- },
27
- mutate: {
28
- errorPolicy: 'all'
29
- }
30
- }
31
-
32
- var client
33
-
34
- export async function getOperatoClient() {
35
- if (!client) {
36
- if (!authKey || !subdomain) {
37
- throw new Error('some operato config missing.')
38
- }
39
-
40
- const domain = await getRepository(Domain).findOne({
41
- where: {
42
- subdomain: 'system'
43
- }
44
- })
45
-
46
- const user = await getRepository(User).findOne({
47
- where: {
48
- id: domain.owner
49
- }
50
- })
51
-
52
- const httpLink = createHttpLink({
53
- uri: uri
54
- })
55
-
56
- /*
57
- CHECKPOINT:
58
- 1. GraphqQLWsLink를 사용하면 setContext를 통한 추가 헤더 설정이 무시됩니다.
59
- 따라서, GraphQLWsLink를 사용하려면, connectionParams를 통해 헤더를 설정해야 합니다.
60
-
61
- 2. 서버에서 실행시, webSocketImpl을 명시적으로 지정해야 합니다.
62
- */
63
- const wsLink = new GraphQLWsLink(
64
- createClient({
65
- url: uri.replace(/^http/, 'ws'),
66
- keepAlive: 10_000,
67
- retryAttempts: 1_000_000,
68
- shouldRetry: e => true,
69
- webSocketImpl: WebSocket,
70
- connectionParams: {
71
- headers: {
72
- 'x-things-factory-domain': subdomain,
73
- authorization: authKey ? `Bearer ${authKey}` : ''
74
- }
75
- }
76
- })
77
- )
78
-
79
- const splitLink = split(
80
- ({ query }) => {
81
- const def = getMainDefinition(query)
82
- return def.kind === 'OperationDefinition' && def.operation === 'subscription'
83
- },
84
- wsLink,
85
- setContext((_, { headers }) => {
86
- return {
87
- headers: {
88
- ...headers,
89
- 'x-things-factory-domain': subdomain,
90
- authorization: authKey ? `Bearer ${authKey}` : ''
91
- }
92
- }
93
- }).concat(httpLink)
94
- )
95
-
96
- const cache = new InMemoryCache({
97
- addTypename: false
98
- })
99
-
100
- client = new ApolloClient({
101
- defaultOptions,
102
- cache,
103
- link: splitLink
104
- })
105
- }
106
-
107
- return client
108
- }
@@ -1,104 +0,0 @@
1
- import gql from 'graphql-tag'
2
-
3
- import { createLogger, format } from 'winston'
4
-
5
- import { logger } from '@things-factory/env'
6
- import { Step, Context, TaskRegistry } from '@things-factory/integration-base/'
7
-
8
- import { getOperatoClient } from './operato-client'
9
- import { LogAggregator } from './log-aggregator'
10
-
11
- export async function initRunTaskSubscription() {
12
- const client = await getOperatoClient()
13
-
14
- const subscription = client.subscribe({
15
- query: gql`
16
- subscription {
17
- runTask {
18
- id
19
- step {
20
- task
21
- connection
22
- params
23
- }
24
- context {
25
- domain {
26
- id
27
- name
28
- subdomain
29
- }
30
- data
31
- variables
32
- lng
33
- }
34
- }
35
- }
36
- `
37
- })
38
-
39
- subscription.subscribe({
40
- next: async subscription => {
41
- const { id, step, context }: { id: string; step: Step; context: Context } = subscription?.data?.runTask || {}
42
- logger.info(`received run-task request: %s, %s, %s`, id, step, context)
43
- await runTask(id, step, context)
44
- },
45
- error: error => {
46
- logger.error(`operato subscription for runTask error: ${error}`)
47
- },
48
- complete: () => {
49
- logger.info(`operato subscription for runTask complete`)
50
- }
51
- })
52
- }
53
-
54
- async function runTask(id: string, step: Step, context: Context) {
55
- var handler = TaskRegistry.getTaskHandler(step.task)
56
- if (!handler) {
57
- throw new Error(`no task handler for step '${step.name}'(${step.id})`)
58
- }
59
-
60
- var error
61
-
62
- const logAggregator = new LogAggregator({})
63
-
64
- const logger = createLogger({
65
- format: format.combine(
66
- format.timestamp(),
67
- format.printf(({ timestamp, level, message, stack }) => `${timestamp} ${level}: ${stack || message}`)
68
- ),
69
- transports: [logAggregator]
70
- })
71
-
72
- try {
73
- var out: any = await handler(
74
- {
75
- ...step,
76
- params: JSON.parse(step.params)
77
- },
78
- {
79
- ...context,
80
- logger
81
- }
82
- )
83
- } catch (e) {
84
- error = e?.stack || e
85
- }
86
-
87
- const client = await getOperatoClient()
88
-
89
- await client.mutate({
90
- mutation: gql`
91
- mutation RunTaskCallback($result: RunTaskCallbackInput!) {
92
- runTaskCallback(result: $result)
93
- }
94
- `,
95
- variables: {
96
- result: {
97
- id,
98
- out,
99
- logs: logAggregator.formatResults(),
100
- error
101
- }
102
- }
103
- })
104
- }
@@ -1,89 +0,0 @@
1
- import gql from 'graphql-tag'
2
-
3
- import { logger } from '@things-factory/env'
4
- import { Connection, ConnectionManager, ConnectionStatus } from '@things-factory/integration-base'
5
-
6
- import { getOperatoClient } from './operato-client'
7
- import { LogAggregator } from './log-aggregator'
8
-
9
- export async function initSyncConnectionsSubscription() {
10
- const client = await getOperatoClient()
11
-
12
- const subscription = client.subscribe({
13
- query: gql`
14
- subscription {
15
- syncConnections {
16
- domain {
17
- id
18
- name
19
- subdomain
20
- }
21
- id
22
- type
23
- name
24
- description
25
- endpoint
26
- active
27
- state
28
- params
29
- }
30
- }
31
- `
32
- })
33
-
34
- subscription.subscribe({
35
- next: async subscription => {
36
- const connections = subscription.data?.syncConnections || []
37
- await syncConnections(
38
- connections.map(connection => {
39
- try {
40
- const params = JSON.parse(connection.params)
41
-
42
- return {
43
- // domain,
44
- ...connection,
45
- params
46
- }
47
- } catch (e) {
48
- return {
49
- // domain,
50
- ...connection,
51
- params: null
52
- }
53
- }
54
- })
55
- )
56
- },
57
- error: error => {
58
- logger.error(`operato subscription error: ${error}`)
59
- },
60
- complete: () => {
61
- logger.info(`operato subscription complete`)
62
- }
63
- })
64
- }
65
-
66
- async function syncConnections(connections: Connection[]) {
67
- console.log('syncConnections ...', connections)
68
-
69
- if (connections.length == 0) {
70
- return
71
- }
72
-
73
- const domain = connections[0]?.domain
74
- const oldConnections = ConnectionManager.getConnectionInstances(domain)
75
- Object.values(oldConnections).forEach(old => ConnectionManager.removeConnectionInstance(old))
76
-
77
- connections.forEach(connection => syncConnection(connection))
78
- }
79
-
80
- async function syncConnection(connection: Connection) {
81
- const { type, state } = connection
82
-
83
- var connector = ConnectionManager.getConnector(type)
84
- if (!connector) {
85
- return
86
- }
87
-
88
- await connector.connect(connection)
89
- }