@things-factory/integration-ui 10.0.0-beta.90 → 10.0.0-beta.95
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/client/pages/connection.ts +101 -43
- package/client/pages/scenario-detail.ts +41 -3
- package/client/pages/scenario.ts +1 -0
- package/client/viewparts/env-var-action-injector.ts +239 -0
- package/client/viewparts/env-var-quick-editor.ts +347 -0
- package/dist-client/pages/connection.d.ts +7 -1
- package/dist-client/pages/connection.js +88 -41
- package/dist-client/pages/connection.js.map +1 -1
- package/dist-client/pages/scenario-detail.js +29 -3
- package/dist-client/pages/scenario-detail.js.map +1 -1
- package/dist-client/pages/scenario.js +1 -0
- package/dist-client/pages/scenario.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/viewparts/env-var-action-injector.d.ts +38 -0
- package/dist-client/viewparts/env-var-action-injector.js +196 -0
- package/dist-client/viewparts/env-var-action-injector.js.map +1 -0
- package/dist-client/viewparts/env-var-quick-editor.d.ts +54 -0
- package/dist-client/viewparts/env-var-quick-editor.js +351 -0
- package/dist-client/viewparts/env-var-quick-editor.js.map +1 -0
- package/package.json +6 -6
- package/translations/en.json +18 -6
- package/translations/ja.json +18 -6
- package/translations/ko.json +18 -6
- package/translations/ms.json +18 -6
- package/translations/zh.json +18 -6
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js';
|
|
2
|
+
import './env-var-quick-editor.js';
|
|
3
|
+
import { PropertySpec } from '../types.js';
|
|
4
|
+
export interface EnvVarResolution {
|
|
5
|
+
key: string;
|
|
6
|
+
status: 'local' | 'inherited' | 'absent';
|
|
7
|
+
envVarId?: string;
|
|
8
|
+
sourceDomainId?: string;
|
|
9
|
+
sourceDomainName?: string;
|
|
10
|
+
value?: string;
|
|
11
|
+
hasValue?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* useDomainAttribute 속성의 EnvVar 4-상태 칩 + 인라인 편집기 팝업.
|
|
15
|
+
* Connection / Step params 화면이 공통으로 사용.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveEnvVars(keys: string[]): Promise<Map<string, EnvVarResolution>>;
|
|
18
|
+
/**
|
|
19
|
+
* actionInjector 팩토리.
|
|
20
|
+
*
|
|
21
|
+
* @param keyBuilder 속성 이름을 받아 EnvVar 키를 생성. 예:
|
|
22
|
+
* Connection 화면: `(p) => 'Connection::' + connName + '::' + p`
|
|
23
|
+
* Step 화면: `(p) => 'Step::' + scenarioName + '::' + stepName + '::' + p`
|
|
24
|
+
* @param resolutions 사전 조회한 해소 상태 맵 (key → resolution)
|
|
25
|
+
* @param refresh 저장·삭제 후 부모 grist 새로고침 콜백
|
|
26
|
+
*/
|
|
27
|
+
export declare function createEnvVarActionInjector(keyBuilder: (propName: string) => string, resolutions: Map<string, EnvVarResolution>, refresh: () => void): (propName: string, propSpec: PropertySpec) => HTMLElement | null;
|
|
28
|
+
/**
|
|
29
|
+
* 한 화면(Connection 한 행, Scenario 한 step 셋) 의 진단 라벨.
|
|
30
|
+
* - 사용 준비됨 (n/n) : 모든 useDomainAttribute 속성이 local 또는 inherited
|
|
31
|
+
* - 미완성 (m absent / n total): 하나라도 absent
|
|
32
|
+
*/
|
|
33
|
+
export declare function diagnoseReadiness(resolutions: Map<string, EnvVarResolution>, expectedKeys: string[]): {
|
|
34
|
+
ready: boolean;
|
|
35
|
+
total: number;
|
|
36
|
+
absent: number;
|
|
37
|
+
label: string;
|
|
38
|
+
};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import '@material/web/icon/icon.js';
|
|
2
|
+
import './env-var-quick-editor.js';
|
|
3
|
+
import gql from 'graphql-tag';
|
|
4
|
+
import { html } from 'lit';
|
|
5
|
+
import { client } from '@operato/graphql';
|
|
6
|
+
import { notify, openPopup } from '@operato/layout';
|
|
7
|
+
import { i18next } from '@operato/i18n';
|
|
8
|
+
/**
|
|
9
|
+
* useDomainAttribute 속성의 EnvVar 4-상태 칩 + 인라인 편집기 팝업.
|
|
10
|
+
* Connection / Step params 화면이 공통으로 사용.
|
|
11
|
+
*/
|
|
12
|
+
export async function resolveEnvVars(keys) {
|
|
13
|
+
const map = new Map();
|
|
14
|
+
if (!keys || keys.length === 0)
|
|
15
|
+
return map;
|
|
16
|
+
try {
|
|
17
|
+
const response = await client.query({
|
|
18
|
+
query: gql `
|
|
19
|
+
query ($keys: [String!]!) {
|
|
20
|
+
envVarResolutions(keys: $keys) {
|
|
21
|
+
key
|
|
22
|
+
status
|
|
23
|
+
envVarId
|
|
24
|
+
sourceDomainId
|
|
25
|
+
sourceDomainName
|
|
26
|
+
value
|
|
27
|
+
hasValue
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
`,
|
|
31
|
+
variables: { keys },
|
|
32
|
+
fetchPolicy: 'network-only'
|
|
33
|
+
});
|
|
34
|
+
for (const r of response.data?.envVarResolutions || []) {
|
|
35
|
+
map.set(r.key, r);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
console.warn('envVarResolutions failed; falling back to absent', e);
|
|
40
|
+
}
|
|
41
|
+
return map;
|
|
42
|
+
}
|
|
43
|
+
async function copyToClipboard(text) {
|
|
44
|
+
try {
|
|
45
|
+
await navigator.clipboard.writeText(text);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
const textArea = document.createElement('textarea');
|
|
49
|
+
textArea.value = text;
|
|
50
|
+
document.body.appendChild(textArea);
|
|
51
|
+
textArea.select();
|
|
52
|
+
document.execCommand('copy');
|
|
53
|
+
document.body.removeChild(textArea);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function buildStatusChip(key, resolution, onClick, onCopy) {
|
|
57
|
+
const chip = document.createElement('span');
|
|
58
|
+
chip.style.cssText = [
|
|
59
|
+
'display:inline-flex',
|
|
60
|
+
'align-items:center',
|
|
61
|
+
'gap:4px',
|
|
62
|
+
'padding:2px 8px',
|
|
63
|
+
'font-size:11px',
|
|
64
|
+
'line-height:1.4',
|
|
65
|
+
'border-radius:10px',
|
|
66
|
+
'cursor:pointer',
|
|
67
|
+
'user-select:none',
|
|
68
|
+
'border:1px solid'
|
|
69
|
+
].join(';');
|
|
70
|
+
const status = resolution?.status || 'absent';
|
|
71
|
+
let icon = 'help';
|
|
72
|
+
let label = '미설정';
|
|
73
|
+
let fg = '';
|
|
74
|
+
let bg = '';
|
|
75
|
+
if (status === 'local') {
|
|
76
|
+
icon = 'check_circle';
|
|
77
|
+
label = '이 도메인';
|
|
78
|
+
fg = 'var(--md-sys-color-on-tertiary-container)';
|
|
79
|
+
bg = 'var(--md-sys-color-tertiary-container)';
|
|
80
|
+
}
|
|
81
|
+
else if (status === 'inherited') {
|
|
82
|
+
icon = 'inventory';
|
|
83
|
+
label = `상속${resolution.sourceDomainName ? ' · ' + resolution.sourceDomainName : ''}`;
|
|
84
|
+
fg = 'var(--md-sys-color-on-primary-container)';
|
|
85
|
+
bg = 'var(--md-sys-color-primary-container)';
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
icon = 'warning';
|
|
89
|
+
label = '미설정';
|
|
90
|
+
fg = 'var(--md-sys-color-on-error-container)';
|
|
91
|
+
bg = 'var(--md-sys-color-error-container)';
|
|
92
|
+
}
|
|
93
|
+
chip.style.color = fg;
|
|
94
|
+
chip.style.background = bg;
|
|
95
|
+
chip.style.borderColor = fg;
|
|
96
|
+
chip.title = `${key} — 클릭: 편집, ⌥ 클릭: 키 복사`;
|
|
97
|
+
const iconEl = document.createElement('md-icon');
|
|
98
|
+
iconEl.textContent = icon;
|
|
99
|
+
iconEl.style.cssText = `font-size:13px; --md-icon-size:13px; color:${fg};`;
|
|
100
|
+
chip.appendChild(iconEl);
|
|
101
|
+
const text = document.createElement('span');
|
|
102
|
+
text.textContent = label;
|
|
103
|
+
chip.appendChild(text);
|
|
104
|
+
chip.addEventListener('click', (e) => {
|
|
105
|
+
if (e.altKey)
|
|
106
|
+
onCopy();
|
|
107
|
+
else
|
|
108
|
+
onClick();
|
|
109
|
+
});
|
|
110
|
+
return chip;
|
|
111
|
+
}
|
|
112
|
+
function openEnvVarEditor(key, propName, propSpec, resolution, refresh) {
|
|
113
|
+
const propLabel = (propSpec.label && i18next.t('label.' + propSpec.label)) || propName;
|
|
114
|
+
const popup = openPopup(html `
|
|
115
|
+
<env-var-quick-editor
|
|
116
|
+
.key_=${key}
|
|
117
|
+
.propSpec=${propSpec}
|
|
118
|
+
.resolution=${resolution}
|
|
119
|
+
.propLabel=${propLabel}
|
|
120
|
+
@saved=${() => {
|
|
121
|
+
popup.close?.();
|
|
122
|
+
refresh();
|
|
123
|
+
}}
|
|
124
|
+
@cancel=${() => popup.close?.()}
|
|
125
|
+
></env-var-quick-editor>
|
|
126
|
+
`, {
|
|
127
|
+
backdrop: true,
|
|
128
|
+
size: 'small',
|
|
129
|
+
title: `${propLabel} — ${key}`
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* actionInjector 팩토리.
|
|
134
|
+
*
|
|
135
|
+
* @param keyBuilder 속성 이름을 받아 EnvVar 키를 생성. 예:
|
|
136
|
+
* Connection 화면: `(p) => 'Connection::' + connName + '::' + p`
|
|
137
|
+
* Step 화면: `(p) => 'Step::' + scenarioName + '::' + stepName + '::' + p`
|
|
138
|
+
* @param resolutions 사전 조회한 해소 상태 맵 (key → resolution)
|
|
139
|
+
* @param refresh 저장·삭제 후 부모 grist 새로고침 콜백
|
|
140
|
+
*/
|
|
141
|
+
export function createEnvVarActionInjector(keyBuilder, resolutions, refresh) {
|
|
142
|
+
return (propName, propSpec) => {
|
|
143
|
+
if (!propSpec.useDomainAttribute)
|
|
144
|
+
return null;
|
|
145
|
+
const key = keyBuilder(propName);
|
|
146
|
+
let currentResolution = resolutions.get(key) || { key, status: 'absent' };
|
|
147
|
+
// 안정 컨테이너 — DOM 상의 위치/참조는 유지하고 내부 chip 만 교체.
|
|
148
|
+
// chip 직접 replaceWith 시 shadow DOM 경계나 grist 재렌더에 의해
|
|
149
|
+
// 칩이 사라지는 케이스가 있어 컨테이너로 한 단계 감쌈.
|
|
150
|
+
const host = document.createElement('span');
|
|
151
|
+
host.style.cssText = 'display:inline-flex; align-items:center;';
|
|
152
|
+
const openCopy = () => copyToClipboard(key).then(() => notify({ message: `복사됨: ${key}` }));
|
|
153
|
+
const renderChip = () => {
|
|
154
|
+
const chip = buildStatusChip(key, currentResolution, openEdit, openCopy);
|
|
155
|
+
host.replaceChildren(chip);
|
|
156
|
+
};
|
|
157
|
+
// 저장·삭제 후 자기 chip 만 즉시 갱신. 부모 grist 의 fetch 는 호출하지 않는다 —
|
|
158
|
+
// grist.fetch() 가 parameters 편집기(popover) 자체를 닫아 칩들이 통째로 사라지는
|
|
159
|
+
// 사고를 막기 위함. 다음 cell edit 저장 시점에 자연스럽게 grid 가 새로고침됨.
|
|
160
|
+
const onSaved = async () => {
|
|
161
|
+
try {
|
|
162
|
+
const updated = await resolveEnvVars([key]);
|
|
163
|
+
currentResolution = updated.get(key) || { key, status: 'absent' };
|
|
164
|
+
renderChip();
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
console.warn('chip 갱신 실패', e);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
const openEdit = () => openEnvVarEditor(key, propName, propSpec, currentResolution, onSaved);
|
|
171
|
+
renderChip();
|
|
172
|
+
return host;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 한 화면(Connection 한 행, Scenario 한 step 셋) 의 진단 라벨.
|
|
177
|
+
* - 사용 준비됨 (n/n) : 모든 useDomainAttribute 속성이 local 또는 inherited
|
|
178
|
+
* - 미완성 (m absent / n total): 하나라도 absent
|
|
179
|
+
*/
|
|
180
|
+
export function diagnoseReadiness(resolutions, expectedKeys) {
|
|
181
|
+
const total = expectedKeys.length;
|
|
182
|
+
let absent = 0;
|
|
183
|
+
for (const k of expectedKeys) {
|
|
184
|
+
const r = resolutions.get(k);
|
|
185
|
+
if (!r || r.status === 'absent')
|
|
186
|
+
absent++;
|
|
187
|
+
}
|
|
188
|
+
const ready = absent === 0 && total > 0;
|
|
189
|
+
const label = total === 0
|
|
190
|
+
? '도메인 속성 없음'
|
|
191
|
+
: ready
|
|
192
|
+
? `사용 준비됨 (${total}/${total})`
|
|
193
|
+
: `미완성 — ${absent} 미설정 / ${total}`;
|
|
194
|
+
return { ready, total, absent, label };
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=env-var-action-injector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-var-action-injector.js","sourceRoot":"","sources":["../../client/viewparts/env-var-action-injector.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport './env-var-quick-editor.js'\n\nimport gql from 'graphql-tag'\nimport { html } from 'lit'\nimport { client } from '@operato/graphql'\nimport { notify, openPopup } from '@operato/layout'\nimport { i18next } from '@operato/i18n'\nimport { PropertySpec } from '../types.js'\n\nexport interface EnvVarResolution {\n key: string\n status: 'local' | 'inherited' | 'absent'\n envVarId?: string\n sourceDomainId?: string\n sourceDomainName?: string\n value?: string\n hasValue?: boolean\n}\n\n/**\n * useDomainAttribute 속성의 EnvVar 4-상태 칩 + 인라인 편집기 팝업.\n * Connection / Step params 화면이 공통으로 사용.\n */\nexport async function resolveEnvVars(keys: string[]): Promise<Map<string, EnvVarResolution>> {\n const map = new Map<string, EnvVarResolution>()\n if (!keys || keys.length === 0) return map\n\n try {\n const response = await client.query({\n query: gql`\n query ($keys: [String!]!) {\n envVarResolutions(keys: $keys) {\n key\n status\n envVarId\n sourceDomainId\n sourceDomainName\n value\n hasValue\n }\n }\n `,\n variables: { keys },\n fetchPolicy: 'network-only'\n })\n for (const r of response.data?.envVarResolutions || []) {\n map.set(r.key, r)\n }\n } catch (e) {\n console.warn('envVarResolutions failed; falling back to absent', e)\n }\n return map\n}\n\nasync function copyToClipboard(text: string): Promise<void> {\n try {\n await navigator.clipboard.writeText(text)\n } catch {\n const textArea = document.createElement('textarea')\n textArea.value = text\n document.body.appendChild(textArea)\n textArea.select()\n document.execCommand('copy')\n document.body.removeChild(textArea)\n }\n}\n\nfunction buildStatusChip(\n key: string,\n resolution: EnvVarResolution | undefined,\n onClick: () => void,\n onCopy: () => void\n): HTMLElement {\n const chip = document.createElement('span')\n chip.style.cssText = [\n 'display:inline-flex',\n 'align-items:center',\n 'gap:4px',\n 'padding:2px 8px',\n 'font-size:11px',\n 'line-height:1.4',\n 'border-radius:10px',\n 'cursor:pointer',\n 'user-select:none',\n 'border:1px solid'\n ].join(';')\n\n const status = resolution?.status || 'absent'\n let icon = 'help'\n let label = '미설정'\n let fg = ''\n let bg = ''\n\n if (status === 'local') {\n icon = 'check_circle'\n label = '이 도메인'\n fg = 'var(--md-sys-color-on-tertiary-container)'\n bg = 'var(--md-sys-color-tertiary-container)'\n } else if (status === 'inherited') {\n icon = 'inventory'\n label = `상속${resolution!.sourceDomainName ? ' · ' + resolution!.sourceDomainName : ''}`\n fg = 'var(--md-sys-color-on-primary-container)'\n bg = 'var(--md-sys-color-primary-container)'\n } else {\n icon = 'warning'\n label = '미설정'\n fg = 'var(--md-sys-color-on-error-container)'\n bg = 'var(--md-sys-color-error-container)'\n }\n\n chip.style.color = fg\n chip.style.background = bg\n chip.style.borderColor = fg\n chip.title = `${key} — 클릭: 편집, ⌥ 클릭: 키 복사`\n\n const iconEl = document.createElement('md-icon')\n iconEl.textContent = icon\n iconEl.style.cssText = `font-size:13px; --md-icon-size:13px; color:${fg};`\n chip.appendChild(iconEl)\n\n const text = document.createElement('span')\n text.textContent = label\n chip.appendChild(text)\n\n chip.addEventListener('click', (e: MouseEvent) => {\n if (e.altKey) onCopy()\n else onClick()\n })\n\n return chip\n}\n\nfunction openEnvVarEditor(\n key: string,\n propName: string,\n propSpec: PropertySpec,\n resolution: EnvVarResolution,\n refresh: () => void\n): void {\n const propLabel = (propSpec.label && i18next.t('label.' + propSpec.label)) || propName\n\n const popup = openPopup(\n html`\n <env-var-quick-editor\n .key_=${key}\n .propSpec=${propSpec}\n .resolution=${resolution}\n .propLabel=${propLabel}\n @saved=${() => {\n popup.close?.()\n refresh()\n }}\n @cancel=${() => popup.close?.()}\n ></env-var-quick-editor>\n `,\n {\n backdrop: true,\n size: 'small',\n title: `${propLabel} — ${key}`\n }\n )\n}\n\n/**\n * actionInjector 팩토리.\n *\n * @param keyBuilder 속성 이름을 받아 EnvVar 키를 생성. 예:\n * Connection 화면: `(p) => 'Connection::' + connName + '::' + p`\n * Step 화면: `(p) => 'Step::' + scenarioName + '::' + stepName + '::' + p`\n * @param resolutions 사전 조회한 해소 상태 맵 (key → resolution)\n * @param refresh 저장·삭제 후 부모 grist 새로고침 콜백\n */\nexport function createEnvVarActionInjector(\n keyBuilder: (propName: string) => string,\n resolutions: Map<string, EnvVarResolution>,\n refresh: () => void\n): (propName: string, propSpec: PropertySpec) => HTMLElement | null {\n return (propName, propSpec) => {\n if (!propSpec.useDomainAttribute) return null\n const key = keyBuilder(propName)\n let currentResolution = resolutions.get(key) || { key, status: 'absent' as const }\n\n // 안정 컨테이너 — DOM 상의 위치/참조는 유지하고 내부 chip 만 교체.\n // chip 직접 replaceWith 시 shadow DOM 경계나 grist 재렌더에 의해\n // 칩이 사라지는 케이스가 있어 컨테이너로 한 단계 감쌈.\n const host = document.createElement('span')\n host.style.cssText = 'display:inline-flex; align-items:center;'\n\n const openCopy = () => copyToClipboard(key).then(() => notify({ message: `복사됨: ${key}` }))\n\n const renderChip = () => {\n const chip = buildStatusChip(key, currentResolution, openEdit, openCopy)\n host.replaceChildren(chip)\n }\n\n // 저장·삭제 후 자기 chip 만 즉시 갱신. 부모 grist 의 fetch 는 호출하지 않는다 —\n // grist.fetch() 가 parameters 편집기(popover) 자체를 닫아 칩들이 통째로 사라지는\n // 사고를 막기 위함. 다음 cell edit 저장 시점에 자연스럽게 grid 가 새로고침됨.\n const onSaved = async () => {\n try {\n const updated = await resolveEnvVars([key])\n currentResolution = updated.get(key) || { key, status: 'absent' as const }\n renderChip()\n } catch (e) {\n console.warn('chip 갱신 실패', e)\n }\n }\n\n const openEdit = () => openEnvVarEditor(key, propName, propSpec, currentResolution, onSaved)\n\n renderChip()\n return host\n }\n}\n\n/**\n * 한 화면(Connection 한 행, Scenario 한 step 셋) 의 진단 라벨.\n * - 사용 준비됨 (n/n) : 모든 useDomainAttribute 속성이 local 또는 inherited\n * - 미완성 (m absent / n total): 하나라도 absent\n */\nexport function diagnoseReadiness(\n resolutions: Map<string, EnvVarResolution>,\n expectedKeys: string[]\n): { ready: boolean; total: number; absent: number; label: string } {\n const total = expectedKeys.length\n let absent = 0\n for (const k of expectedKeys) {\n const r = resolutions.get(k)\n if (!r || r.status === 'absent') absent++\n }\n const ready = absent === 0 && total > 0\n const label = total === 0\n ? '도메인 속성 없음'\n : ready\n ? `사용 준비됨 (${total}/${total})`\n : `미완성 — ${absent} 미설정 / ${total}`\n return { ready, total, absent, label }\n}\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import '@material/web/button/filled-button.js';
|
|
2
|
+
import '@material/web/button/outlined-button.js';
|
|
3
|
+
import '@material/web/button/text-button.js';
|
|
4
|
+
import '@material/web/textfield/outlined-text-field.js';
|
|
5
|
+
import '@material/web/icon/icon.js';
|
|
6
|
+
import { LitElement } from 'lit';
|
|
7
|
+
interface Resolution {
|
|
8
|
+
key: string;
|
|
9
|
+
status: 'local' | 'inherited' | 'absent';
|
|
10
|
+
envVarId?: string;
|
|
11
|
+
sourceDomainName?: string;
|
|
12
|
+
value?: string;
|
|
13
|
+
hasValue?: boolean;
|
|
14
|
+
}
|
|
15
|
+
declare const EnvVarQuickEditor_base: (new (...args: any[]) => LitElement) & typeof LitElement;
|
|
16
|
+
/**
|
|
17
|
+
* Connection / Step 파라미터 화면에서 useDomainAttribute 속성의 EnvVar 를
|
|
18
|
+
* 한 자리에서 확인·편집·삭제하는 인라인 편집기.
|
|
19
|
+
*
|
|
20
|
+
* 동작:
|
|
21
|
+
* - 현 도메인에 값이 있으면 update(M) / delete
|
|
22
|
+
* - 상속만 있는 경우(또는 없는 경우) "이 도메인에 등록" (create)
|
|
23
|
+
* - 부모 값을 덮어쓰면 closest-wins 로 자식 값이 적용됨을 안내
|
|
24
|
+
*
|
|
25
|
+
* 저장/삭제 성공 시 `saved` 이벤트 발생.
|
|
26
|
+
*/
|
|
27
|
+
export declare class EnvVarQuickEditor extends EnvVarQuickEditor_base {
|
|
28
|
+
static styles: import("lit").CSSResult;
|
|
29
|
+
/** EnvVar 키 (예: `Connection::kiscon-conn::password`) */
|
|
30
|
+
key_: string;
|
|
31
|
+
/** 속성 사양 (type/secret 여부 표시 등에 사용) */
|
|
32
|
+
propSpec: any;
|
|
33
|
+
/** 사전 조회한 해소 상태 */
|
|
34
|
+
resolution: Resolution;
|
|
35
|
+
/** 표시용 라벨 (속성 이름) */
|
|
36
|
+
propLabel: string;
|
|
37
|
+
private editedValue;
|
|
38
|
+
private busy;
|
|
39
|
+
connectedCallback(): void;
|
|
40
|
+
private get isSecret();
|
|
41
|
+
/**
|
|
42
|
+
* 저장 시 영향 범위 (scope) 안내 패널 — 운영자 의사결정 보조.
|
|
43
|
+
* - absent : 처음 등록. 자손 트리 전체에 inherit.
|
|
44
|
+
* - local : 이미 등록됨. 자손이 inherit (자손에 자기 override 있으면 그것 우선).
|
|
45
|
+
* - inherited: 상위에서 받고있음. 여기 등록 시 이 도메인 + 자손에만 한정 override.
|
|
46
|
+
*/
|
|
47
|
+
private _renderScopePanel;
|
|
48
|
+
private get statusLabel();
|
|
49
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
50
|
+
private _cancel;
|
|
51
|
+
private _save;
|
|
52
|
+
private _delete;
|
|
53
|
+
}
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { __decorate, __metadata } from "tslib";
|
|
2
|
+
import '@material/web/button/filled-button.js';
|
|
3
|
+
import '@material/web/button/outlined-button.js';
|
|
4
|
+
import '@material/web/button/text-button.js';
|
|
5
|
+
import '@material/web/textfield/outlined-text-field.js';
|
|
6
|
+
import '@material/web/icon/icon.js';
|
|
7
|
+
import gql from 'graphql-tag';
|
|
8
|
+
import { LitElement, css, html } from 'lit';
|
|
9
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
10
|
+
import { client } from '@operato/graphql';
|
|
11
|
+
import { notify } from '@operato/layout';
|
|
12
|
+
import { i18next, localize } from '@operato/i18n';
|
|
13
|
+
/**
|
|
14
|
+
* Connection / Step 파라미터 화면에서 useDomainAttribute 속성의 EnvVar 를
|
|
15
|
+
* 한 자리에서 확인·편집·삭제하는 인라인 편집기.
|
|
16
|
+
*
|
|
17
|
+
* 동작:
|
|
18
|
+
* - 현 도메인에 값이 있으면 update(M) / delete
|
|
19
|
+
* - 상속만 있는 경우(또는 없는 경우) "이 도메인에 등록" (create)
|
|
20
|
+
* - 부모 값을 덮어쓰면 closest-wins 로 자식 값이 적용됨을 안내
|
|
21
|
+
*
|
|
22
|
+
* 저장/삭제 성공 시 `saved` 이벤트 발생.
|
|
23
|
+
*/
|
|
24
|
+
let EnvVarQuickEditor = class EnvVarQuickEditor extends localize(i18next)(LitElement) {
|
|
25
|
+
constructor() {
|
|
26
|
+
super(...arguments);
|
|
27
|
+
/** 표시용 라벨 (속성 이름) */
|
|
28
|
+
this.propLabel = '';
|
|
29
|
+
this.editedValue = '';
|
|
30
|
+
this.busy = false;
|
|
31
|
+
}
|
|
32
|
+
static { this.styles = css `
|
|
33
|
+
:host {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
min-width: 460px;
|
|
37
|
+
max-height: 80vh;
|
|
38
|
+
padding: 20px 24px;
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
background: var(--md-sys-color-surface, #fff);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
h3 {
|
|
44
|
+
margin: 0 0 4px;
|
|
45
|
+
font-size: 16px;
|
|
46
|
+
flex: 0 0 auto;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* 스크롤 가능 본문 — flex column 안에서 actions 위쪽 영역만 흘러내림 */
|
|
50
|
+
.body {
|
|
51
|
+
flex: 1 1 auto;
|
|
52
|
+
overflow: auto;
|
|
53
|
+
min-height: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.key {
|
|
57
|
+
font-family: var(--md-sys-typescale-body-medium-font-family, monospace);
|
|
58
|
+
font-size: 12px;
|
|
59
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
60
|
+
background: var(--md-sys-color-surface-variant);
|
|
61
|
+
padding: 4px 8px;
|
|
62
|
+
border-radius: 6px;
|
|
63
|
+
display: inline-block;
|
|
64
|
+
margin-bottom: 16px;
|
|
65
|
+
word-break: break-all;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.status {
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
gap: 8px;
|
|
72
|
+
margin-bottom: 14px;
|
|
73
|
+
font-size: 14px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.status[data-status='local'] {
|
|
77
|
+
color: var(--md-sys-color-tertiary);
|
|
78
|
+
}
|
|
79
|
+
.status[data-status='inherited'] {
|
|
80
|
+
color: var(--md-sys-color-primary);
|
|
81
|
+
}
|
|
82
|
+
.status[data-status='absent'] {
|
|
83
|
+
color: var(--md-sys-color-error);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
md-outlined-text-field {
|
|
87
|
+
width: 100%;
|
|
88
|
+
margin-bottom: 12px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.actions {
|
|
92
|
+
display: flex;
|
|
93
|
+
justify-content: flex-end;
|
|
94
|
+
gap: 8px;
|
|
95
|
+
flex: 0 0 auto;
|
|
96
|
+
padding-top: 12px;
|
|
97
|
+
margin-top: 4px;
|
|
98
|
+
border-top: 1px solid var(--md-sys-color-outline-variant, #e0e0e0);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.info {
|
|
102
|
+
font-size: 12px;
|
|
103
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
104
|
+
margin-bottom: 12px;
|
|
105
|
+
line-height: 1.5;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.scope {
|
|
109
|
+
font-size: 12px;
|
|
110
|
+
line-height: 1.6;
|
|
111
|
+
padding: 10px 12px;
|
|
112
|
+
margin-bottom: 14px;
|
|
113
|
+
border-radius: 8px;
|
|
114
|
+
background: var(--md-sys-color-surface-container-low, #f5f5f5);
|
|
115
|
+
border-left: 3px solid var(--md-sys-color-primary, #3457d5);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.scope .scope-title {
|
|
119
|
+
font-weight: 600;
|
|
120
|
+
color: var(--md-sys-color-on-surface);
|
|
121
|
+
margin-bottom: 4px;
|
|
122
|
+
display: flex;
|
|
123
|
+
align-items: center;
|
|
124
|
+
gap: 4px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.scope .scope-title md-icon {
|
|
128
|
+
font-size: 14px;
|
|
129
|
+
--md-icon-size: 14px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.scope ul {
|
|
133
|
+
margin: 4px 0 0;
|
|
134
|
+
padding-left: 18px;
|
|
135
|
+
color: var(--md-sys-color-on-surface-variant);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.scope li {
|
|
139
|
+
margin-bottom: 2px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.scope code {
|
|
143
|
+
font-family: var(--md-sys-typescale-body-medium-font-family, monospace);
|
|
144
|
+
font-size: 11px;
|
|
145
|
+
background: var(--md-sys-color-surface-variant);
|
|
146
|
+
padding: 1px 4px;
|
|
147
|
+
border-radius: 3px;
|
|
148
|
+
}
|
|
149
|
+
`; }
|
|
150
|
+
connectedCallback() {
|
|
151
|
+
super.connectedCallback();
|
|
152
|
+
this.editedValue = this.resolution?.value ?? '';
|
|
153
|
+
}
|
|
154
|
+
get isSecret() {
|
|
155
|
+
return this.propSpec?.type === 'secret' || /password|secret|token|key$/i.test(this.propLabel || '');
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 저장 시 영향 범위 (scope) 안내 패널 — 운영자 의사결정 보조.
|
|
159
|
+
* - absent : 처음 등록. 자손 트리 전체에 inherit.
|
|
160
|
+
* - local : 이미 등록됨. 자손이 inherit (자손에 자기 override 있으면 그것 우선).
|
|
161
|
+
* - inherited: 상위에서 받고있음. 여기 등록 시 이 도메인 + 자손에만 한정 override.
|
|
162
|
+
*/
|
|
163
|
+
_renderScopePanel(isLocal, isInherited) {
|
|
164
|
+
if (isLocal) {
|
|
165
|
+
return html `
|
|
166
|
+
<div class="scope">
|
|
167
|
+
<div class="scope-title">
|
|
168
|
+
<md-icon>info</md-icon>
|
|
169
|
+
저장 위치 / 영향 범위
|
|
170
|
+
</div>
|
|
171
|
+
이 도메인에 이미 등록된 값이 자손 도메인 전체에 inherit 됩니다 (closest-wins).
|
|
172
|
+
<ul>
|
|
173
|
+
<li>여기서 값을 바꾸면 → 이 도메인 + 자손 트리 전체에 즉시 반영</li>
|
|
174
|
+
<li>특정 자손에 자기 override 가 등록되어 있으면 그쪽은 자기 값 우선</li>
|
|
175
|
+
<li>"이 도메인에서 삭제" → 부모(있다면) 값으로 fallback</li>
|
|
176
|
+
</ul>
|
|
177
|
+
</div>
|
|
178
|
+
`;
|
|
179
|
+
}
|
|
180
|
+
if (isInherited) {
|
|
181
|
+
return html `
|
|
182
|
+
<div class="scope">
|
|
183
|
+
<div class="scope-title">
|
|
184
|
+
<md-icon>inventory</md-icon>
|
|
185
|
+
저장 위치 / 영향 범위
|
|
186
|
+
</div>
|
|
187
|
+
현재 <code>${this.resolution.sourceDomainName || '상위 도메인'}</code> 에서 상속된 값을 사용 중입니다.
|
|
188
|
+
<ul>
|
|
189
|
+
<li>여기서 등록하면 → 이 도메인 + 그 자손에만 우선 적용 (다른 형제 도메인은 영향 없음)</li>
|
|
190
|
+
<li>나머지 다른 자손들은 여전히
|
|
191
|
+
<code>${this.resolution.sourceDomainName || '상위 도메인'}</code> 의 값을 그대로 사용</li>
|
|
192
|
+
</ul>
|
|
193
|
+
</div>
|
|
194
|
+
`;
|
|
195
|
+
}
|
|
196
|
+
return html `
|
|
197
|
+
<div class="scope">
|
|
198
|
+
<div class="scope-title">
|
|
199
|
+
<md-icon>add_circle</md-icon>
|
|
200
|
+
저장 위치 / 영향 범위
|
|
201
|
+
</div>
|
|
202
|
+
이 도메인에 처음 등록합니다.
|
|
203
|
+
<ul>
|
|
204
|
+
<li>저장 후 → 이 도메인 + 자손 트리 전체에서 사용</li>
|
|
205
|
+
<li>특정 자손이 자기 값을 별도 등록하면 그쪽은 자기 값 우선 (closest-wins)</li>
|
|
206
|
+
<li>잘못된 도메인에 저장하지 않도록 — 현재 컨텍스트 도메인을 한번 확인하세요</li>
|
|
207
|
+
</ul>
|
|
208
|
+
</div>
|
|
209
|
+
`;
|
|
210
|
+
}
|
|
211
|
+
get statusLabel() {
|
|
212
|
+
const s = this.resolution?.status;
|
|
213
|
+
if (s === 'local')
|
|
214
|
+
return `✓ ${i18next.t('text.set-on-this-domain') || '이 도메인에 등록됨'}`;
|
|
215
|
+
if (s === 'inherited')
|
|
216
|
+
return `🔗 ${i18next.t('text.inherited-from') || '상속'} (${this.resolution.sourceDomainName || '부모 도메인'})`;
|
|
217
|
+
return `⚠ ${i18next.t('text.not-set') || '미설정'}`;
|
|
218
|
+
}
|
|
219
|
+
render() {
|
|
220
|
+
const isLocal = this.resolution?.status === 'local';
|
|
221
|
+
const isInherited = this.resolution?.status === 'inherited';
|
|
222
|
+
return html `
|
|
223
|
+
<h3>${i18next.t('text.domain-attribute') || '도메인 속성'} · ${this.propLabel || this.key_}</h3>
|
|
224
|
+
|
|
225
|
+
<div class="body">
|
|
226
|
+
<div class="key">${this.key_}</div>
|
|
227
|
+
|
|
228
|
+
<div class="status" data-status=${this.resolution?.status || 'absent'}>${this.statusLabel}</div>
|
|
229
|
+
|
|
230
|
+
${this._renderScopePanel(isLocal, isInherited)}
|
|
231
|
+
|
|
232
|
+
<md-outlined-text-field
|
|
233
|
+
label=${i18next.t('field.value')}
|
|
234
|
+
type=${this.isSecret ? 'password' : 'text'}
|
|
235
|
+
.value=${this.editedValue}
|
|
236
|
+
@input=${(e) => (this.editedValue = e.target.value)}
|
|
237
|
+
?disabled=${this.busy}
|
|
238
|
+
></md-outlined-text-field>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<div class="actions">
|
|
242
|
+
${isLocal
|
|
243
|
+
? html `<md-text-button @click=${this._delete} ?disabled=${this.busy}>
|
|
244
|
+
${i18next.t('button.delete-from-this-domain') || '이 도메인에서 삭제'}
|
|
245
|
+
</md-text-button>`
|
|
246
|
+
: ''}
|
|
247
|
+
<md-outlined-button @click=${this._cancel} ?disabled=${this.busy}>
|
|
248
|
+
${i18next.t('button.cancel')}
|
|
249
|
+
</md-outlined-button>
|
|
250
|
+
<md-filled-button @click=${this._save} ?disabled=${this.busy || !this.editedValue}>
|
|
251
|
+
${isLocal ? i18next.t('button.update') || '갱신' : i18next.t('button.set') || '등록'}
|
|
252
|
+
</md-filled-button>
|
|
253
|
+
</div>
|
|
254
|
+
`;
|
|
255
|
+
}
|
|
256
|
+
_cancel() {
|
|
257
|
+
this.dispatchEvent(new CustomEvent('cancel', { bubbles: true, composed: true }));
|
|
258
|
+
}
|
|
259
|
+
async _save() {
|
|
260
|
+
this.busy = true;
|
|
261
|
+
try {
|
|
262
|
+
const isUpdate = this.resolution?.status === 'local' && this.resolution?.envVarId;
|
|
263
|
+
const patches = isUpdate
|
|
264
|
+
? [{ id: this.resolution.envVarId, cuFlag: 'M', value: this.editedValue, active: true }]
|
|
265
|
+
: [
|
|
266
|
+
{
|
|
267
|
+
cuFlag: '+',
|
|
268
|
+
name: this.key_,
|
|
269
|
+
value: this.editedValue,
|
|
270
|
+
active: true,
|
|
271
|
+
description: this.propLabel || ''
|
|
272
|
+
}
|
|
273
|
+
];
|
|
274
|
+
const response = await client.mutate({
|
|
275
|
+
mutation: gql `
|
|
276
|
+
mutation ($patches: [EnvVarPatch!]!) {
|
|
277
|
+
updateMultipleEnvVars(patches: $patches) {
|
|
278
|
+
name
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
`,
|
|
282
|
+
variables: { patches }
|
|
283
|
+
});
|
|
284
|
+
if (response.errors)
|
|
285
|
+
throw new Error(response.errors.map((e) => e.message).join('\n'));
|
|
286
|
+
notify({ message: i18next.t('text.saved-successfully') || '저장되었습니다' });
|
|
287
|
+
this.dispatchEvent(new CustomEvent('saved', { bubbles: true, composed: true }));
|
|
288
|
+
}
|
|
289
|
+
catch (e) {
|
|
290
|
+
notify({ level: 'error', message: e.message || String(e) });
|
|
291
|
+
}
|
|
292
|
+
finally {
|
|
293
|
+
this.busy = false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async _delete() {
|
|
297
|
+
if (!this.resolution?.envVarId)
|
|
298
|
+
return;
|
|
299
|
+
// 도메인 환경변수 삭제는 확인 없이 즉시 처리.
|
|
300
|
+
this.busy = true;
|
|
301
|
+
try {
|
|
302
|
+
const response = await client.mutate({
|
|
303
|
+
mutation: gql `
|
|
304
|
+
mutation ($id: String!) {
|
|
305
|
+
deleteEnvVar(id: $id)
|
|
306
|
+
}
|
|
307
|
+
`,
|
|
308
|
+
variables: { id: this.resolution.envVarId }
|
|
309
|
+
});
|
|
310
|
+
if (response.errors)
|
|
311
|
+
throw new Error(response.errors.map((e) => e.message).join('\n'));
|
|
312
|
+
notify({ message: i18next.t('text.deleted-successfully') || '삭제되었습니다' });
|
|
313
|
+
this.dispatchEvent(new CustomEvent('saved', { bubbles: true, composed: true }));
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
notify({ level: 'error', message: e.message || String(e) });
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
this.busy = false;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
__decorate([
|
|
324
|
+
property({ type: String }),
|
|
325
|
+
__metadata("design:type", String)
|
|
326
|
+
], EnvVarQuickEditor.prototype, "key_", void 0);
|
|
327
|
+
__decorate([
|
|
328
|
+
property({ type: Object }),
|
|
329
|
+
__metadata("design:type", Object)
|
|
330
|
+
], EnvVarQuickEditor.prototype, "propSpec", void 0);
|
|
331
|
+
__decorate([
|
|
332
|
+
property({ type: Object }),
|
|
333
|
+
__metadata("design:type", Object)
|
|
334
|
+
], EnvVarQuickEditor.prototype, "resolution", void 0);
|
|
335
|
+
__decorate([
|
|
336
|
+
property({ type: String }),
|
|
337
|
+
__metadata("design:type", String)
|
|
338
|
+
], EnvVarQuickEditor.prototype, "propLabel", void 0);
|
|
339
|
+
__decorate([
|
|
340
|
+
state(),
|
|
341
|
+
__metadata("design:type", String)
|
|
342
|
+
], EnvVarQuickEditor.prototype, "editedValue", void 0);
|
|
343
|
+
__decorate([
|
|
344
|
+
state(),
|
|
345
|
+
__metadata("design:type", Boolean)
|
|
346
|
+
], EnvVarQuickEditor.prototype, "busy", void 0);
|
|
347
|
+
EnvVarQuickEditor = __decorate([
|
|
348
|
+
customElement('env-var-quick-editor')
|
|
349
|
+
], EnvVarQuickEditor);
|
|
350
|
+
export { EnvVarQuickEditor };
|
|
351
|
+
//# sourceMappingURL=env-var-quick-editor.js.map
|