@things-factory/integration-base 8.0.0-alpha.8 → 8.0.0-beta.0

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.
Files changed (65) hide show
  1. package/dist-server/controllers/scenario-controller.d.ts +2 -0
  2. package/dist-server/controllers/scenario-controller.js +16 -19
  3. package/dist-server/controllers/scenario-controller.js.map +1 -1
  4. package/dist-server/engine/connection-manager.js.map +1 -1
  5. package/dist-server/engine/connector/headless-connector.d.ts +23 -0
  6. package/dist-server/engine/connector/headless-connector.js +283 -0
  7. package/dist-server/engine/connector/headless-connector.js.map +1 -0
  8. package/dist-server/engine/connector/http-connector.js +1 -1
  9. package/dist-server/engine/connector/http-connector.js.map +1 -1
  10. package/dist-server/engine/connector/index.d.ts +1 -0
  11. package/dist-server/engine/connector/index.js +1 -0
  12. package/dist-server/engine/connector/index.js.map +1 -1
  13. package/dist-server/engine/connector/operato-connector.js +3 -4
  14. package/dist-server/engine/connector/operato-connector.js.map +1 -1
  15. package/dist-server/engine/index.d.ts +1 -0
  16. package/dist-server/engine/index.js +1 -0
  17. package/dist-server/engine/index.js.map +1 -1
  18. package/dist-server/engine/resource-pool/headless-pool.d.ts +1 -0
  19. package/dist-server/engine/resource-pool/headless-pool.js +121 -0
  20. package/dist-server/engine/resource-pool/headless-pool.js.map +1 -0
  21. package/dist-server/engine/resource-pool/index.d.ts +1 -0
  22. package/dist-server/engine/resource-pool/index.js +5 -0
  23. package/dist-server/engine/resource-pool/index.js.map +1 -0
  24. package/dist-server/engine/task/headless-post.js +19 -33
  25. package/dist-server/engine/task/headless-post.js.map +1 -1
  26. package/dist-server/engine/task/headless-scrap.js +20 -13
  27. package/dist-server/engine/task/headless-scrap.js.map +1 -1
  28. package/dist-server/engine/task/utils/headless-pool-for-scenario.js +1 -1
  29. package/dist-server/engine/task/utils/headless-pool-for-scenario.js.map +1 -1
  30. package/dist-server/service/payload-log/payload-log.js +7 -4
  31. package/dist-server/service/payload-log/payload-log.js.map +1 -1
  32. package/dist-server/service/scenario/scenario-query.d.ts +2 -1
  33. package/dist-server/service/scenario/scenario-query.js +10 -0
  34. package/dist-server/service/scenario/scenario-query.js.map +1 -1
  35. package/dist-server/service/scenario/scenario-type.d.ts +3 -3
  36. package/dist-server/service/scenario/scenario-type.js +7 -7
  37. package/dist-server/service/scenario/scenario-type.js.map +1 -1
  38. package/dist-server/service/scenario/scenario.d.ts +3 -1
  39. package/dist-server/service/scenario/scenario.js +9 -0
  40. package/dist-server/service/scenario/scenario.js.map +1 -1
  41. package/dist-server/service/scenario-instance/scenario-instance-type.js.map +1 -1
  42. package/dist-server/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +12 -11
  44. package/server/controllers/scenario-controller.ts +27 -36
  45. package/server/engine/connection-manager.ts +1 -1
  46. package/server/engine/connector/headless-connector.ts +341 -0
  47. package/server/engine/connector/http-connector.ts +1 -1
  48. package/server/engine/connector/index.ts +1 -0
  49. package/server/engine/connector/operato-connector.ts +4 -7
  50. package/server/engine/index.ts +1 -0
  51. package/server/engine/resource-pool/headless-pool.ts +136 -0
  52. package/server/engine/resource-pool/index.ts +1 -0
  53. package/server/engine/task/headless-post.ts +21 -40
  54. package/server/engine/task/headless-scrap.ts +21 -18
  55. package/server/engine/task/utils/headless-pool-for-scenario.ts +1 -1
  56. package/server/service/payload-log/payload-log.ts +8 -4
  57. package/server/service/scenario/scenario-query.ts +6 -1
  58. package/server/service/scenario/scenario-type.ts +5 -5
  59. package/server/service/scenario/scenario.ts +15 -19
  60. package/server/service/scenario-instance/scenario-instance-type.ts +1 -1
  61. package/translations/en.json +12 -5
  62. package/translations/ja.json +12 -5
  63. package/translations/ko.json +12 -5
  64. package/translations/ms.json +12 -5
  65. package/translations/zh.json +12 -5
@@ -0,0 +1,136 @@
1
+ import * as genericPool from 'generic-pool'
2
+ import { config, logger } from '@things-factory/env'
3
+
4
+ try {
5
+ var puppeteer = require('puppeteer')
6
+ } catch (err) {
7
+ logger.error(err)
8
+ }
9
+
10
+ let headlessPool
11
+
12
+ export function getHeadlessPool() {
13
+ if (!headlessPool) {
14
+ headlessPool = createHeadlessPool({ min: 2, max: 20, acquireTimeoutMillis: 15000, testOnBorrow: true })
15
+ }
16
+
17
+ return headlessPool
18
+ }
19
+
20
+ function createHeadlessPool(options: genericPool.Options) {
21
+ return genericPool.createPool(
22
+ {
23
+ create() {
24
+ console.log('headless instance in headless-pool-integration about to create')
25
+ return initializeChromium()
26
+ },
27
+ validate(browser) {
28
+ return Promise.race([
29
+ new Promise(res => setTimeout(() => res(false), 1500)),
30
+ browser
31
+ //@ts-ignore
32
+ .version()
33
+ .then(() => true)
34
+ .catch(() => false)
35
+ ])
36
+ },
37
+ destroy(browser) {
38
+ //@ts-ignore
39
+ return browser.close()
40
+ }
41
+ } as genericPool.Factory<any>,
42
+ options
43
+ )
44
+ }
45
+
46
+ async function destroyHeadlessPool() {
47
+ if (headlessPool) {
48
+ console.log('headless-pool-integration about to destroy')
49
+
50
+ try {
51
+ await Promise.race([
52
+ headlessPool.drain().then(() => headlessPool.clear()), // 정리 작업
53
+ new Promise(
54
+ (_, reject) => setTimeout(() => reject(new Error('Destroy timeout')), 5000) // 5초 타임아웃 설정
55
+ )
56
+ ])
57
+ console.log('Headless pool destroyed')
58
+ } catch (err) {
59
+ logger.error('Failed to destroy headless pool:', err)
60
+ }
61
+ }
62
+ }
63
+
64
+ const CHROMIUM_PATH = config.get('CHROMIUM_PATH')
65
+
66
+ async function initializeChromium() {
67
+ try {
68
+ if (!puppeteer) {
69
+ return
70
+ }
71
+
72
+ const launchSetting = {
73
+ args: ['--hide-scrollbars', '--mute-audio', '--no-sandbox', '--use-gl=egl'],
74
+ headless: 'shell'
75
+ }
76
+
77
+ if (CHROMIUM_PATH) {
78
+ launchSetting['executablePath'] = CHROMIUM_PATH
79
+ }
80
+
81
+ const browser = await puppeteer.launch(launchSetting)
82
+
83
+ return browser
84
+ } catch (err) {
85
+ logger.error(err)
86
+ }
87
+ }
88
+
89
+ // Graceful shutdown logic
90
+ function setupProcessExitHandlers() {
91
+ let isCleaningUp = false // 중복 정리를 방지
92
+
93
+ const cleanup = async () => {
94
+ if (isCleaningUp) return // 이미 정리 중이면 무시
95
+ isCleaningUp = true
96
+
97
+ console.log('Application is shutting down. Cleaning up resources...')
98
+ try {
99
+ // Pool 정리 작업 실행
100
+ await destroyHeadlessPool()
101
+ } catch (err) {
102
+ logger.error('Error during cleanup:', err)
103
+ } finally {
104
+ console.log('Cleanup completed.')
105
+ }
106
+ }
107
+
108
+ const onExit = async (signal?: string) => {
109
+ console.log(`Received signal: ${signal || 'unknown'}`)
110
+ await cleanup()
111
+
112
+ // 다른 핸들러가 실행될 수 있도록 exit 호출을 지연
113
+ process.nextTick(() => process.exit(0)) // Tick 뒤에 종료
114
+ }
115
+
116
+ // Handle termination signals
117
+ process.once('SIGINT', () => onExit('SIGINT')) // Ctrl+C
118
+ process.once('SIGTERM', () => onExit('SIGTERM')) // Termination signal
119
+
120
+ // Handle uncaught exceptions
121
+ process.once('uncaughtException', async err => {
122
+ logger.error('Uncaught Exception:', err)
123
+ await cleanup()
124
+ process.nextTick(() => process.exit(1)) // Tick 뒤에 종료
125
+ })
126
+
127
+ // Handle unhandled promise rejections
128
+ process.once('unhandledRejection', async reason => {
129
+ logger.error('Unhandled Rejection:', reason)
130
+ await cleanup()
131
+ process.nextTick(() => process.exit(1)) // Tick 뒤에 종료
132
+ })
133
+ }
134
+
135
+ // Initialize process exit handlers
136
+ setupProcessExitHandlers()
@@ -0,0 +1 @@
1
+ export * from './headless-pool'
@@ -5,29 +5,23 @@ import { access } from '@things-factory/utils'
5
5
  import { TaskRegistry } from '../task-registry'
6
6
  import { ConnectionManager } from '../connection-manager'
7
7
 
8
- import { getHeadlessPool } from './utils/headless-pool-for-scenario'
8
+ async function HeadlessPost(step, { logger, data, domain }) {
9
+ const { connection: connectionName, params: stepOptions } = step
10
+ const { headers: requestHeaders, contentType, path, accessor } = stepOptions || {}
9
11
 
10
- import { InputStep } from '../../service/step/step-type'
11
- import { Context } from '../types'
12
-
13
- async function HeadlessPost(step: InputStep, { logger, data, domain }: Context) {
14
- var { connection: connectionName, params: stepOptions } = step
15
- var { headers: requestHeaders, contentType, path, accessor } = stepOptions || {}
16
-
17
- var connection = ConnectionManager.getConnectionInstanceByName(domain, connectionName)
12
+ const connection = ConnectionManager.getConnectionInstanceByName(domain, connectionName)
18
13
 
19
14
  if (!connection) {
20
- throw new Error(`connection '${connectionName}' is not established.`)
15
+ throw new Error(`Connection '${connectionName}' is not established.`)
21
16
  }
22
17
 
23
- var { endpoint, params: connectionParams, authHeaders = {} } = connection
18
+ const { endpoint, params: connectionParams, acquireSessionPage, releasePage } = connection
24
19
 
25
- var headers = {
26
- ...authHeaders,
20
+ const headers = {
27
21
  ...requestHeaders
28
22
  }
29
23
 
30
- var body = access(accessor, data)
24
+ let body = access(accessor, data)
31
25
  if (contentType && body) {
32
26
  headers['content-type'] = contentType
33
27
  switch (contentType) {
@@ -42,18 +36,18 @@ async function HeadlessPost(step: InputStep, { logger, data, domain }: Context)
42
36
  for (const prop in body) {
43
37
  searchParams.set(prop, body[prop])
44
38
  }
45
- body = searchParams
39
+ body = searchParams.toString()
46
40
  break
47
41
  }
48
42
  }
49
43
 
50
- var options: any = {
44
+ const options = {
51
45
  method: 'POST',
52
46
  headers,
53
47
  body
54
- }
48
+ } as any
55
49
 
56
- var { rejectUnauthorized } = connectionParams
50
+ const { rejectUnauthorized } = connectionParams
57
51
 
58
52
  if (!rejectUnauthorized) {
59
53
  const httpsAgent = new https.Agent({
@@ -62,8 +56,7 @@ async function HeadlessPost(step: InputStep, { logger, data, domain }: Context)
62
56
  options.agent = httpsAgent
63
57
  }
64
58
 
65
- const browser = (await getHeadlessPool().acquire()) as any
66
- const page = await browser.newPage()
59
+ const page = await acquireSessionPage()
67
60
 
68
61
  try {
69
62
  page.on('console', async msg => {
@@ -93,11 +86,11 @@ async function HeadlessPost(step: InputStep, { logger, data, domain }: Context)
93
86
  return {
94
87
  data: response
95
88
  }
96
- } catch (e) {
97
- console.error(e)
89
+ } catch (error) {
90
+ logger.error('Error in HeadlessPost:', error)
91
+ throw error
98
92
  } finally {
99
- page.close()
100
- getHeadlessPool().release(browser)
93
+ await releasePage(page)
101
94
  }
102
95
  }
103
96
 
@@ -118,22 +111,10 @@ HeadlessPost.parameterSpec = [
118
111
  label: 'content-type',
119
112
  property: {
120
113
  options: [
121
- {
122
- display: '',
123
- value: ''
124
- },
125
- {
126
- display: 'application/json',
127
- value: 'application/json'
128
- },
129
- {
130
- display: 'text/plain',
131
- value: 'text/plain'
132
- },
133
- {
134
- display: 'application/x-www-form-urlencoded',
135
- value: 'application/x-www-form-urlencoded'
136
- }
114
+ { display: '', value: '' },
115
+ { display: 'application/json', value: 'application/json' },
116
+ { display: 'text/plain', value: 'text/plain' },
117
+ { display: 'application/x-www-form-urlencoded', value: 'application/x-www-form-urlencoded' }
137
118
  ]
138
119
  }
139
120
  },
@@ -3,29 +3,23 @@ import { URL } from 'url'
3
3
  import { TaskRegistry } from '../task-registry'
4
4
  import { ConnectionManager } from '../connection-manager'
5
5
 
6
- import { getHeadlessPool } from './utils/headless-pool-for-scenario'
7
- import { InputStep } from '../../service/step/step-type'
8
- import { Context } from '../types'
6
+ async function HeadlessScrap(step, { logger, data, domain }) {
7
+ const { connection: connectionName, params: stepOptions } = step
8
+ const { headers: requestHeaders, path, selectors = [] } = stepOptions || {}
9
9
 
10
- async function HeadlessScrap(step: InputStep, { logger, data, domain }: Context) {
11
- var { connection: connectionName, params: stepOptions } = step
12
- var { headers: requestHeaders, path, selectors = [] } = stepOptions || {}
13
-
14
- var connection = ConnectionManager.getConnectionInstanceByName(domain, connectionName)
10
+ const connection = ConnectionManager.getConnectionInstanceByName(domain, connectionName)
15
11
 
16
12
  if (!connection) {
17
- throw new Error(`connection '${connectionName}' is not established.`)
13
+ throw new Error(`Connection '${connectionName}' is not established.`)
18
14
  }
19
15
 
20
- var { endpoint, params: connectionParams, authHeaders = {} } = connection
16
+ const { endpoint, params: connectionParams, acquireSessionPage, releasePage } = connection
21
17
 
22
- var headers = {
23
- ...authHeaders,
18
+ const headers = {
24
19
  ...requestHeaders
25
20
  }
26
21
 
27
- const browser = (await getHeadlessPool().acquire()) as any
28
- const page = await browser.newPage()
22
+ const page = await acquireSessionPage()
29
23
 
30
24
  try {
31
25
  page.on('console', async msg => {
@@ -33,7 +27,16 @@ async function HeadlessScrap(step: InputStep, { logger, data, domain }: Context)
33
27
  })
34
28
 
35
29
  page.on('requestfailed', request => {
36
- console.log('Request failed:', request.url())
30
+ console.log('Request failed:')
31
+ console.log(`- URL: ${request.url()}`)
32
+ console.log(`- Method: ${request.method()}`)
33
+ console.log(`- Failure Text: ${request.failure()?.errorText}`)
34
+ console.log(`- Headers:`, request.headers())
35
+
36
+ // POST 데이터 (필요한 경우)
37
+ if (request.postData()) {
38
+ console.log(`- Post Data: ${request.postData()}`)
39
+ }
37
40
  })
38
41
 
39
42
  await page.setExtraHTTPHeaders(headers)
@@ -52,10 +55,10 @@ async function HeadlessScrap(step: InputStep, { logger, data, domain }: Context)
52
55
  data: result
53
56
  }
54
57
  } catch (e) {
55
- console.error(e)
58
+ logger.error('Error in HeadlessScrap:', e)
59
+ throw e
56
60
  } finally {
57
- page.close()
58
- getHeadlessPool().release(browser)
61
+ await releasePage(page)
59
62
  }
60
63
  }
61
64
 
@@ -15,7 +15,7 @@ export function getHeadlessPool() {
15
15
  headlessPool = genericPool.createPool(
16
16
  {
17
17
  create() {
18
- console.log('headless-pool-for-scensrio about to create')
18
+ console.log('headless instance in headless-pool-for-scensrio about to create')
19
19
  return initializeChromium()
20
20
  },
21
21
  validate(browser) {
@@ -43,15 +43,19 @@ export class PayloadLog {
43
43
  name: string
44
44
 
45
45
  @Column({
46
+ nullable: true,
46
47
  type:
47
48
  DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
48
49
  ? 'enum'
49
50
  : DATABASE_TYPE == 'oracle'
50
51
  ? 'varchar2'
51
- : 'smallint',
52
- enum: PayloadType,
53
- default: PayloadType.EGESTION,
54
- nullable: true
52
+ : DATABASE_TYPE == 'mssql'
53
+ ? 'nvarchar'
54
+ : 'varchar',
55
+ enum:
56
+ DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? PayloadType : undefined,
57
+ length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined : 32,
58
+ default: PayloadType.EGESTION
55
59
  })
56
60
  @Field(type => String)
57
61
  type: PayloadType
@@ -1,7 +1,7 @@
1
1
  import { Arg, Args, Ctx, Directive, FieldResolver, Query, Resolver, Root } from 'type-graphql'
2
2
  import { Not, IsNull } from 'typeorm'
3
3
 
4
- import { User } from '@things-factory/auth-base'
4
+ import { User, Role } from '@things-factory/auth-base'
5
5
  import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
6
6
 
7
7
  import { ScenarioEngine } from '../../engine'
@@ -101,4 +101,9 @@ export class ScenarioQuery {
101
101
 
102
102
  return ScenarioEngine.getScenarioInstances(domain, scenario.name)
103
103
  }
104
+
105
+ @FieldResolver(type => Role)
106
+ async role(@Root() scenario: Scenario) {
107
+ return scenario.roleId && (await getRepository(Role).findOneBy({ id: scenario.roleId }))
108
+ }
104
109
  }
@@ -1,6 +1,6 @@
1
1
  import { Field, ID, InputType, Int, ObjectType } from 'type-graphql'
2
2
 
3
- import { PrivilegeInput } from '@things-factory/auth-base'
3
+ import { ObjectRef } from '@things-factory/shell'
4
4
 
5
5
  import { Scenario } from './scenario'
6
6
  import { StepPatch } from '../step/step-type'
@@ -28,8 +28,8 @@ export class NewScenario {
28
28
  @Field({ nullable: true })
29
29
  active?: boolean
30
30
 
31
- @Field({ nullable: true })
32
- privilege?: PrivilegeInput
31
+ @Field(type => ObjectRef, { nullable: true })
32
+ role?: ObjectRef
33
33
  }
34
34
 
35
35
  @InputType()
@@ -61,8 +61,8 @@ export class ScenarioPatch {
61
61
  @Field(type => [StepPatch], { nullable: true })
62
62
  steps?: StepPatch[]
63
63
 
64
- @Field({ nullable: true })
65
- privilege?: PrivilegeInput
64
+ @Field(type => ObjectRef, { nullable: true })
65
+ role?: ObjectRef
66
66
 
67
67
  @Field({ nullable: true })
68
68
  cuFlag?: string
@@ -11,7 +11,7 @@ import {
11
11
  UpdateDateColumn
12
12
  } from 'typeorm'
13
13
 
14
- import { PrivilegeObject, PrivilegeInput, User } from '@things-factory/auth-base'
14
+ import { PrivilegeObject, Role, User } from '@things-factory/auth-base'
15
15
  import { Domain } from '@things-factory/shell'
16
16
 
17
17
  import { ScenarioEngine } from '../../engine'
@@ -76,6 +76,13 @@ export class Scenario {
76
76
  @Field(type => PrivilegeObject, { nullable: true })
77
77
  privilege?: PrivilegeObject
78
78
 
79
+ @ManyToOne(type => Role)
80
+ @Field(type => Role, { nullable: true })
81
+ role?: Role
82
+
83
+ @RelationId((scenario: Scenario) => scenario.role)
84
+ roleId?: string
85
+
79
86
  @CreateDateColumn()
80
87
  @Field({ nullable: true })
81
88
  createdAt?: Date
@@ -98,28 +105,17 @@ export class Scenario {
98
105
  @RelationId((scenario: Scenario) => scenario.updater)
99
106
  updaterId?: string
100
107
 
101
- async start(options?: {
102
- instanceName?: string;
103
- domain?: Domain;
104
- user?: User;
105
- variables?: any;
106
- }) {
108
+ async start(options?: { instanceName?: string; domain?: Domain; user?: User; variables?: any }) {
107
109
  try {
108
- await ScenarioEngine.load(options?.instanceName || this.name,
109
- this,
110
- {
111
- domain: options?.domain || this.domain,
112
- user: options?.user || this.updater,
113
- variables: options?.variables
114
- })
110
+ await ScenarioEngine.load(options?.instanceName || this.name, this, {
111
+ domain: options?.domain || this.domain,
112
+ user: options?.user || this.updater,
113
+ variables: options?.variables
114
+ })
115
115
  } catch (ex) {}
116
116
  }
117
117
 
118
- async stop(options?: {
119
- instanceName?: string;
120
- domain?: Domain,
121
- user?: User
122
- }) {
118
+ async stop(options?: { instanceName?: string; domain?: Domain; user?: User }) {
123
119
  try {
124
120
  await ScenarioEngine.unload(options?.domain || this.domain, options?.instanceName || this.name)
125
121
  } finally {
@@ -39,7 +39,7 @@ function getSystemTimeZone() {
39
39
  }
40
40
 
41
41
  const SYSTEM_TZ = getSystemTimeZone()
42
- const systemTimestamp = format((info, opts) => {
42
+ const systemTimestamp = format((info, opts: { tz?: string }) => {
43
43
  if (opts.tz) info.timestamp = moment().tz(opts.tz).format()
44
44
  return info
45
45
  })
@@ -1,14 +1,21 @@
1
1
  {
2
+ "error.scenario instance not found": "scenario instance '{instance}' not found.",
2
3
  "error.scenario not found": "scenario '{scenario}' not found.",
3
4
  "error.scenario run error": "an error occurred while processing the '{scenario}' request. please contact the administrator.",
4
- "error.scenario run unauthorized": "you do not have permission to run the {scenario} scenario. please contact the administrator.",
5
+ "error.scenario run unauthorized": "you do not have permission to run the '{scenario}' scenario. please contact the administrator.",
5
6
  "error.schedule is not set": "schedule should be set for the scenario '{scenario}' in order to register as a schedule",
6
7
  "error.timezone is not set": "timezone should be set for the scenario '{scenario}' in order to register as a schedule",
7
- "error.scenario instance not found": "scenario instance '{instance}' not found.",
8
8
  "label.auth-key": "authentication key",
9
+ "label.connectionTimeoutMillis": "Connection Timeout (milliseconds)",
10
+ "label.idleTimeoutMillis": "Idle Timeout (milliseconds)",
11
+ "label.login-path": "login path",
12
+ "label.maxPoolConnection": "Max Pool Connection",
13
+ "label.maximum-retries": "maximum retry count",
14
+ "label.password-selector": "password selector",
15
+ "label.shadow-dom-selectors": "Shadow-DOM selector",
16
+ "label.submit-selector": "submit selector",
9
17
  "label.subscription-handlers": "subscription handlers",
18
+ "label.success-selector": "success selector",
10
19
  "label.trust-server-certificate": "trust server certificate",
11
- "label.maxPoolConnection": "Max Pool Connection",
12
- "label.idleTimeoutMillis": "Idle Timeout (milliseconds)",
13
- "label.connectionTimeoutMillis": "Connection Timeout (milliseconds)"
20
+ "label.username-selector": "username selector"
14
21
  }
@@ -1,14 +1,21 @@
1
1
  {
2
+ "error.scenario instance not found": "シナリオ インスタンス'{instance}'が見つかりません. 既に終了している可能性があります.",
2
3
  "error.scenario not found": "シナリオ'{scenario}'が見つかりません.",
3
4
  "error.scenario run error": "シナリオ '{scenario}' のリクエストを処理中にエラーが発生しました。管理者に連絡してください。",
4
- "error.scenario run unauthorized": "シナリオ {scenario} を実行する権限がありません。管理者に連絡してください。",
5
+ "error.scenario run unauthorized": "シナリオ '{scenario}' を実行する権限がありません。管理者に連絡してください。",
5
6
  "error.schedule is not set": "スケジュールとして登録するためにはシナリオ'{scenario}'にスケジュール情報が設定される必要があります.",
6
7
  "error.timezone is not set": "スケジュールとして登録するためにはシナリオ'{scenario}'にタイム ゾーン情報が設定される必要があります.",
7
- "error.scenario instance not found": "シナリオ インスタンス'{instance}'が見つかりません. 既に終了している可能性があります.",
8
8
  "label.auth-key": "認証キー",
9
+ "label.connectionTimeoutMillis": "接続タイムアウト(ミリ秒)",
10
+ "label.idleTimeoutMillis": "アイドルタイムアウト(ミリ秒)",
11
+ "label.login-path": "ログインパス",
12
+ "label.maxPoolConnection": "最大プール接続数",
13
+ "label.maximum-retries": "最大リトライ回数",
14
+ "label.password-selector": "パスワードセレクター",
15
+ "label.shadow-dom-selectors": "Shadow-DOMセレクター",
16
+ "label.submit-selector": "送信セレクター",
9
17
  "label.subscription-handlers": "サブスクリプションハンドラー",
18
+ "label.success-selector": "成功セレクター",
10
19
  "label.trust-server-certificate": "サーバー証明書を信頼する",
11
- "label.maxPoolConnection": "最大プール接続数",
12
- "label.idleTimeoutMillis": "アイドルタイムアウト(ミリ秒)",
13
- "label.connectionTimeoutMillis": "接続タイムアウト(ミリ秒)"
20
+ "label.username-selector": "ユーザー名セレクター"
14
21
  }
@@ -1,14 +1,21 @@
1
1
  {
2
+ "error.scenario instance not found": "시나리오 인스턴스 '{instance}'를 찾을 수 없습니다. 이미 종료되었을 수 있습니다.",
2
3
  "error.scenario not found": "시나리오 '{scenario}'를 찾을 수 없습니다.",
3
4
  "error.scenario run error": "시나리오 '{scenario}' 요청을 처리하는 중 오류가 발생했습니다. 관리자에게 문의하십시오.",
4
- "error.scenario run unauthorized": "시나리오 {scenario}를 실행할 권한이 없습니다. 관리자에게 문의하십히오.",
5
+ "error.scenario run unauthorized": "시나리오 '{scenario}'를 실행할 권한이 없습니다. 관리자에게 문의하십히오.",
5
6
  "error.schedule is not set": "스케쥴로 등록하기 위해서는 시나리오 '{scenario}'에 스케쥴 정보가 설정되어야 합니다.",
6
7
  "error.timezone is not set": "스케쥴로 등록하기 위해서는 시나리오 '{scenario}'에 타임존 정보가 설정되어야 합니다.",
7
- "error.scenario instance not found": "시나리오 인스턴스 '{instance}'를 찾을 수 없습니다. 이미 종료되었을 수 있습니다.",
8
8
  "label.auth-key": "인증 키",
9
+ "label.connectionTimeoutMillis": "연결 시간 제한 (밀리초)",
10
+ "label.idleTimeoutMillis": "유휴 시간 제한 (밀리초)",
11
+ "label.login-path": "로그인 경로",
12
+ "label.maxPoolConnection": "최대 풀 연결 수",
13
+ "label.maximum-retries": "최대 재시도 횟수",
14
+ "label.password-selector": "비밀번호 선택자",
15
+ "label.shadow-dom-selectors": "Shadow-DOM 선택자",
16
+ "label.submit-selector": "제출 선택자",
9
17
  "label.subscription-handlers": "구독 핸들러",
18
+ "label.success-selector": "성공 선택자",
10
19
  "label.trust-server-certificate": "서버 인증서 신뢰",
11
- "label.maxPoolConnection": "최대 연결 수",
12
- "label.idleTimeoutMillis": "유휴 시간 제한 (밀리초)",
13
- "label.connectionTimeoutMillis": "연결 시간 제한 (밀리초)"
20
+ "label.username-selector": "사용자 이름 선택자"
14
21
  }
@@ -1,14 +1,21 @@
1
1
  {
2
+ "error.scenario instance not found": "Tidak dapat mencari instans senario '{instance}'. Mungkin telah berakhir.",
2
3
  "error.scenario not found": "Tidak dapat mencari senario '{scenario}'.",
3
4
  "error.scenario run error": "Ralat berlaku semasa memproses permintaan senario '{scenario}'. Sila hubungi pentadbir.",
4
- "error.scenario run unauthorized": "Anda tidak mempunyai kebenaran untuk menjalankan senario {scenario}. Sila hubungi pentadbir.",
5
+ "error.scenario run unauthorized": "Anda tidak mempunyai kebenaran untuk menjalankan senario '{scenario}'. Sila hubungi pentadbir.",
5
6
  "error.schedule is not set": "Untuk mendaftarkan sebagai jadual, maklumat jadual mesti diset dalam senario '{scenario}'.",
6
7
  "error.timezone is not set": "Untuk mendaftarkan sebagai jadual, maklumat zon masa mesti diset dalam senario '{scenario}'.",
7
- "error.scenario instance not found": "Tidak dapat mencari instans senario '{instance}'. Mungkin telah berakhir.",
8
8
  "label.auth-key": "Kunci pengesahan",
9
+ "label.connectionTimeoutMillis": "Had Masa Sambungan (milisaat)",
10
+ "label.idleTimeoutMillis": "Had Masa Tanpa Aktiviti (milisaat)",
11
+ "label.login-path": "laluan log masuk",
12
+ "label.maxPoolConnection": "Sambungan Pool Maksimum",
13
+ "label.maximum-retries": "bilangan cubaan semula maksimum",
14
+ "label.password-selector": "pemilih kata laluan",
15
+ "label.shadow-dom-selectors": "pemilih Shadow-DOM",
16
+ "label.submit-selector": "pemilih serah",
9
17
  "label.subscription-handlers": "pengendali langganan",
18
+ "label.success-selector": "pemilih kejayaan",
10
19
  "label.trust-server-certificate": "percaya sijil pelayan",
11
- "label.maxPoolConnection": "Sambungan Pool Maksimum",
12
- "label.idleTimeoutMillis": "Had Masa Tanpa Aktiviti (milisaat)",
13
- "label.connectionTimeoutMillis": "Had Masa Sambungan (milisaat)"
20
+ "label.username-selector": "pemilih nama pengguna"
14
21
  }
@@ -1,14 +1,21 @@
1
1
  {
2
+ "error.scenario instance not found": "无法找到场景实例 '{instance}'。它可能已经结束。",
2
3
  "error.scenario not found": "无法找到场景 '{scenario}'。",
3
4
  "error.scenario run error": "处理场景 '{scenario}' 请求时发生错误。请联系管理员。",
4
- "error.scenario run unauthorized": "没有执行场景 {scenario} 的权限。请联系管理员。",
5
+ "error.scenario run unauthorized": "没有执行场景 '{scenario}' 的权限。请联系管理员。",
5
6
  "error.schedule is not set": "要注册为计划,场景 '{scenario}' 需要设置计划信息。",
6
7
  "error.timezone is not set": "要注册为计划,场景 '{scenario}' 需要设置时区信息。",
7
- "error.scenario instance not found": "无法找到场景实例 '{instance}'。它可能已经结束。",
8
8
  "label.auth-key": "认证密钥",
9
+ "label.connectionTimeoutMillis": "连接超时时间(毫秒)",
10
+ "label.idleTimeoutMillis": "空闲超时时间(毫秒)",
11
+ "label.login-path": "登录路径",
12
+ "label.maxPoolConnection": "最大连接池连接数",
13
+ "label.maximum-retries": "最大重试次数",
14
+ "label.password-selector": "密码选择器",
15
+ "label.shadow-dom-selectors": "Shadow-DOM选择器",
16
+ "label.submit-selector": "提交选择器",
9
17
  "label.subscription-handlers": "订阅处理程序",
18
+ "label.success-selector": "成功选择器",
10
19
  "label.trust-server-certificate": "信任服务器证书",
11
- "label.maxPoolConnection": "最大连接池连接数",
12
- "label.idleTimeoutMillis": "空闲超时时间(毫秒)",
13
- "label.connectionTimeoutMillis": "连接超时时间(毫秒)"
20
+ "label.username-selector": "用户名选择器"
14
21
  }