dsh-app-deploy 0.2.0 → 0.3.1
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/README.md +6 -2
- package/client.js +346 -117
- package/index.js +9 -2
- package/package.json +2 -2
- package/server/config.js +87 -28
- package/server/deployment.js +151 -36
- package/server/local-tools.js +71 -1
- package/server/rpc.js +11 -4
- package/server/tools.js +138 -0
package/client.js
CHANGED
|
@@ -74,7 +74,7 @@ window.__ModuleLoader__.load({
|
|
|
74
74
|
while (modules.some((m) => m.id === 'mod' + n)) n++
|
|
75
75
|
return {
|
|
76
76
|
id: 'mod' + n, name: '新模块', kind: 'front', enabled: true,
|
|
77
|
-
|
|
77
|
+
buildCmd: '', sshId: '', localPath: '', remotePath: '',
|
|
78
78
|
postCommand: '', healthCheckUrl: '', frontEnv: '',
|
|
79
79
|
}
|
|
80
80
|
}
|
|
@@ -82,14 +82,21 @@ window.__ModuleLoader__.load({
|
|
|
82
82
|
return {
|
|
83
83
|
id: m.id || '', name: m.name || '', kind: m.kind === 'server' ? 'server' : 'front',
|
|
84
84
|
enabled: m.enabled !== false,
|
|
85
|
-
|
|
85
|
+
buildCmd: m.buildCmd || '', sshId: m.sshId || '',
|
|
86
86
|
localPath: m.localPath || '', remotePath: m.remotePath || '', postCommand: m.postCommand || '',
|
|
87
87
|
healthCheckUrl: m.healthCheckUrl || '', frontEnv: m.frontEnv || '',
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
+
// 模块的发布目标(与服务端 config.js targetFor 同规则:sshId 落空→第一个目标)
|
|
91
|
+
function targetOf(env, m) {
|
|
92
|
+
const list = (env && env.sshs) || []
|
|
93
|
+
return list.find((s) => s.id === (m && m.sshId)) || list[0] || {}
|
|
94
|
+
}
|
|
95
|
+
|
|
90
96
|
function normalizeEnv(env) {
|
|
91
97
|
const e = clone(env || {})
|
|
92
|
-
|
|
98
|
+
// 旧格式 env.ssh 单对象自动迁移为 sshs 数组
|
|
99
|
+
const rawSshs = Array.isArray(e.sshs) && e.sshs.length ? e.sshs : (e.ssh ? [e.ssh] : [])
|
|
93
100
|
return {
|
|
94
101
|
id: e.id || '',
|
|
95
102
|
name: e.name || '',
|
|
@@ -97,11 +104,17 @@ window.__ModuleLoader__.load({
|
|
|
97
104
|
group: e.group || '',
|
|
98
105
|
projectPath: e.projectPath || '',
|
|
99
106
|
jdkHome: e.jdkHome || '',
|
|
107
|
+
nodeHome: e.nodeHome || '',
|
|
108
|
+
nodeOptions: e.nodeOptions || '',
|
|
109
|
+
pmPath: e.pmPath || '',
|
|
110
|
+
buildEnv: e.buildEnv || '',
|
|
100
111
|
expTime: e.expTime || '',
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
sshs: rawSshs.map((s, i) => ({
|
|
113
|
+
id: s.id || (i === 0 ? 'default' : 'ssh' + (i + 1)),
|
|
114
|
+
name: s.name || (i === 0 ? '默认' : 'SSH ' + (i + 1)),
|
|
115
|
+
host: s.host || '', port: Number(s.port) || 22, user: s.user || 'root',
|
|
116
|
+
password: s.password || '', keyFile: s.keyFile || '', options: s.options || '',
|
|
117
|
+
})),
|
|
105
118
|
modules: (e.modules || []).map(normalizeModule),
|
|
106
119
|
}
|
|
107
120
|
}
|
|
@@ -127,9 +140,40 @@ window.__ModuleLoader__.load({
|
|
|
127
140
|
children)
|
|
128
141
|
}
|
|
129
142
|
|
|
143
|
+
// 路径下拉选择器(JDK / Node 共用):输入框 + 候选项弹层
|
|
144
|
+
function PathPicker({ value, aria, items, open, wrapRef, onFocus, onChange, onPick, emptyText }) {
|
|
145
|
+
return h('div', { className: 'dad-jdkwrap', ref: wrapRef },
|
|
146
|
+
h('input', {
|
|
147
|
+
className: 'dad-input', type: 'text', value, 'aria-label': aria, onFocus,
|
|
148
|
+
onChange: (ev) => onChange(ev.target.value),
|
|
149
|
+
}),
|
|
150
|
+
open ? h('div', { className: 'dad-jdkdd' },
|
|
151
|
+
items.length
|
|
152
|
+
? items.map((j) => h('button', {
|
|
153
|
+
key: j.path, type: 'button', className: 'dad-jitem',
|
|
154
|
+
onClick: () => onPick(j.path),
|
|
155
|
+
},
|
|
156
|
+
h('span', { className: 'dad-jlbl' }, j.label),
|
|
157
|
+
h('span', { className: 'dad-jfull' }, j.path)))
|
|
158
|
+
: h('div', { className: 'dad-jempty' }, emptyText)) : null)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 构建工具预设:选中即填充构建命令(可再手动编辑)
|
|
162
|
+
const BUILD_PRESETS = [
|
|
163
|
+
{ tool: 'Gradle', cmd: './gradlew' },
|
|
164
|
+
{ tool: 'Maven', cmd: 'mvn -DskipTests package' },
|
|
165
|
+
{ tool: 'npm', cmd: 'npm run build' },
|
|
166
|
+
{ tool: 'pnpm', cmd: 'pnpm run build' },
|
|
167
|
+
{ tool: 'yarn', cmd: 'yarn build' },
|
|
168
|
+
]
|
|
169
|
+
|
|
130
170
|
function EnvCard({ env, selected, lastBuild, onSelect, onCopy, onDelete }) {
|
|
131
171
|
const lb = lastBuild[env.id]
|
|
132
|
-
const
|
|
172
|
+
const sshList = env.sshs || []
|
|
173
|
+
const ssh = sshList[0] || {}
|
|
174
|
+
const sshText = ssh.host
|
|
175
|
+
? (ssh.user || 'root') + '@' + ssh.host + ':' + (ssh.port || 22) + (sshList.length > 1 ? ' 等 ' + sshList.length + ' 个目标' : '')
|
|
176
|
+
: 'SSH 未配置'
|
|
133
177
|
return h('div', {
|
|
134
178
|
className: 'dad-env',
|
|
135
179
|
'data-sel': selected ? '1' : '0',
|
|
@@ -142,7 +186,7 @@ window.__ModuleLoader__.load({
|
|
|
142
186
|
h('i', { className: 'dad-dot' + (lb ? ' st-' + lb.status : ''), 'aria-hidden': true }),
|
|
143
187
|
env.name || env.id),
|
|
144
188
|
h('span', { className: 'dad-sub' }, env.id + (env.note ? ' · ' + env.note : '')),
|
|
145
|
-
h('span', { className: 'dad-sub' },
|
|
189
|
+
h('span', { className: 'dad-sub' }, sshText),
|
|
146
190
|
h('span', { className: 'dad-sub dad-build' + (lb ? ' ' + lb.status : '') },
|
|
147
191
|
h('i', { className: 'dad-dot', 'aria-hidden': true }),
|
|
148
192
|
lb ? '最后构建: ' + fmtTime(lb.time) + '(' + (HSTATUS[lb.status] || lb.status) + ')' : '最后构建: 从未')),
|
|
@@ -205,11 +249,11 @@ window.__ModuleLoader__.load({
|
|
|
205
249
|
nodes))
|
|
206
250
|
}
|
|
207
251
|
|
|
208
|
-
function ModuleCard({ mod, onChange, onDelete }) {
|
|
252
|
+
function ModuleCard({ mod, onChange, onDelete, sshs }) {
|
|
209
253
|
const envFile = mod.id === 'bsFront' ? 'client/client-front/.env'
|
|
210
254
|
: mod.id === 'adminFront' ? 'admin/admin-front/.env' : ''
|
|
211
255
|
const feLabel = 'frontEnv(KEY=VALUE 多行,构建时注入'
|
|
212
|
-
+ (envFile ? ' ' + envFile
|
|
256
|
+
+ (envFile ? ' ' + envFile : '') + ')'
|
|
213
257
|
return h('div', { className: 'dad-mcard', 'data-off': mod.enabled ? '0' : '1' },
|
|
214
258
|
h('button', {
|
|
215
259
|
type: 'button', className: 'dad-btndel', title: '删除模块', 'aria-label': '删除模块',
|
|
@@ -239,21 +283,19 @@ window.__ModuleLoader__.load({
|
|
|
239
283
|
onChange: (ev) => onChange('enabled', ev.target.checked),
|
|
240
284
|
}), '启用')),
|
|
241
285
|
h('div', { className: 'dad-row' },
|
|
242
|
-
h(Field, { label: '
|
|
243
|
-
h('
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
onChange: (ev) => onChange('extraArgs', ev.target.value),
|
|
256
|
-
}))),
|
|
286
|
+
h(Field, { label: '构建命令(可左侧选工具自动填充,再手动补任务/参数)', wide: true },
|
|
287
|
+
h('div', { className: 'dad-inline' },
|
|
288
|
+
h('select', {
|
|
289
|
+
className: 'dad-select', 'aria-label': '构建工具',
|
|
290
|
+
value: (BUILD_PRESETS.find((p) => p.cmd === (mod.buildCmd || '').trim()) || {}).cmd || '',
|
|
291
|
+
onChange: (ev) => { if (ev.target.value) onChange('buildCmd', ev.target.value) },
|
|
292
|
+
},
|
|
293
|
+
h('option', { value: '' }, '构建工具…'),
|
|
294
|
+
BUILD_PRESETS.map((p) => h('option', { key: p.cmd, value: p.cmd }, p.tool))),
|
|
295
|
+
h('input', {
|
|
296
|
+
className: 'dad-input', type: 'text', value: mod.buildCmd, 'aria-label': '构建命令',
|
|
297
|
+
onChange: (ev) => onChange('buildCmd', ev.target.value),
|
|
298
|
+
})))),
|
|
257
299
|
h('div', { className: 'dad-row' },
|
|
258
300
|
h(Field, { label: '本地产物路径' },
|
|
259
301
|
h('input', {
|
|
@@ -264,7 +306,13 @@ window.__ModuleLoader__.load({
|
|
|
264
306
|
h('input', {
|
|
265
307
|
className: 'dad-input', type: 'text', value: mod.remotePath, 'aria-label': '远程目标路径',
|
|
266
308
|
onChange: (ev) => onChange('remotePath', ev.target.value),
|
|
267
|
-
}))
|
|
309
|
+
})),
|
|
310
|
+
h(Field, { label: 'SSH 目标' },
|
|
311
|
+
h('select', {
|
|
312
|
+
className: 'dad-select', value: mod.sshId || '', 'aria-label': 'SSH 目标',
|
|
313
|
+
onChange: (ev) => onChange('sshId', ev.target.value),
|
|
314
|
+
},
|
|
315
|
+
(sshs || []).map((s) => h('option', { key: s.id, value: s.id }, s.name || s.id))))),
|
|
268
316
|
h('div', { className: 'dad-row' },
|
|
269
317
|
h(Field, { label: '部署后远程命令(可空)' },
|
|
270
318
|
h('input', {
|
|
@@ -292,12 +340,15 @@ window.__ModuleLoader__.load({
|
|
|
292
340
|
function EnvForm({
|
|
293
341
|
env, isNew, groups, message, open, onToggle,
|
|
294
342
|
onPatch, onPatchSsh, onPatchModule, onAddModule, onDeleteModule,
|
|
295
|
-
onSave, onDelete, onBrowse,
|
|
343
|
+
onSave, onDelete, onBrowse, onBrowseKey,
|
|
344
|
+
onAddSsh, onDeleteSsh, onTestSsh, sshTest,
|
|
296
345
|
jdks, jdkOpen, jdkRef, onJdkFocus, onJdkPick,
|
|
346
|
+
nodes, nodeOpen, nodeRef, nodeErr, onNodeFocus, onNodePick,
|
|
347
|
+
pms, pmOpen, pmRef, pmErr, onPmFocus, onPmPick,
|
|
297
348
|
}) {
|
|
298
349
|
const title = env ? '环境配置' : '选择左侧环境进行编辑'
|
|
299
350
|
const groupOptions = groups.map((g) => h('option', { key: g, value: g }))
|
|
300
|
-
const
|
|
351
|
+
const sshList = env ? (env.sshs || []) : []
|
|
301
352
|
return h('div', { className: 'dad-card' },
|
|
302
353
|
h('div', { className: 'dad-card-head' },
|
|
303
354
|
h('span', { className: 'dad-card-title', title }, title),
|
|
@@ -345,20 +396,37 @@ window.__ModuleLoader__.load({
|
|
|
345
396
|
}),
|
|
346
397
|
h('button', { type: 'button', className: 'dad-btn', onClick: onBrowse }, '浏览…'))),
|
|
347
398
|
h(Field, { label: 'JDK 路径(JAVA_HOME,可留空)', wide: true },
|
|
348
|
-
h(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
399
|
+
h(PathPicker, {
|
|
400
|
+
value: env.jdkHome, aria: 'JDK 路径', items: jdks, open: jdkOpen, wrapRef: jdkRef,
|
|
401
|
+
onFocus: onJdkFocus, onChange: (v) => onPatch({ jdkHome: v }), onPick: onJdkPick,
|
|
402
|
+
emptyText: '未发现可用 JDK',
|
|
403
|
+
}))),
|
|
404
|
+
h('div', { className: 'dad-row' },
|
|
405
|
+
h(Field, { label: 'Node 路径(构建时加入 PATH,可留空)', wide: true },
|
|
406
|
+
h(PathPicker, {
|
|
407
|
+
value: env.nodeHome || '', aria: 'Node 路径', items: nodes, open: nodeOpen, wrapRef: nodeRef,
|
|
408
|
+
onFocus: onNodeFocus, onChange: (v) => onPatch({ nodeHome: v }), onPick: onNodePick,
|
|
409
|
+
emptyText: nodeErr || '未发现已装 Node(支持 nvm / n / fnm / volta)',
|
|
410
|
+
})),
|
|
411
|
+
h(Field, { label: 'Node options(NODE_OPTIONS,可留空)' },
|
|
412
|
+
h('input', {
|
|
413
|
+
className: 'dad-input', type: 'text', value: env.nodeOptions || '', 'aria-label': 'Node options',
|
|
414
|
+
onChange: (ev) => onPatch({ nodeOptions: ev.target.value }),
|
|
415
|
+
}))),
|
|
416
|
+
h('div', { className: 'dad-row' },
|
|
417
|
+
h(Field, { label: '包管理器路径(如 …/pnpm,构建时加入 PATH,可留空)', wide: true },
|
|
418
|
+
h(PathPicker, {
|
|
419
|
+
value: env.pmPath || '', aria: '包管理器路径', items: pms, open: pmOpen, wrapRef: pmRef,
|
|
420
|
+
onFocus: onPmFocus, onChange: (v) => onPatch({ pmPath: v }), onPick: onPmPick,
|
|
421
|
+
emptyText: pmErr || '未发现 pnpm / yarn(npm 随 Node 自动可用)',
|
|
422
|
+
}))),
|
|
423
|
+
h('div', { className: 'dad-row' },
|
|
424
|
+
h(Field, { label: '构建环境变量(KEY=VALUE 多行,构建时注入所有模块,可留空)', wide: true },
|
|
425
|
+
h('textarea', {
|
|
426
|
+
className: 'dad-textarea', rows: 3, spellCheck: false, value: env.buildEnv || '',
|
|
427
|
+
'aria-label': '构建环境变量',
|
|
428
|
+
onChange: (ev) => onPatch({ buildEnv: ev.target.value }),
|
|
429
|
+
}))),
|
|
362
430
|
h('div', { className: 'dad-row' },
|
|
363
431
|
h(Field, { label: '过期时间 expTime(YYYY-MM-DD,可留空)' },
|
|
364
432
|
h('input', {
|
|
@@ -366,44 +434,81 @@ window.__ModuleLoader__.load({
|
|
|
366
434
|
onChange: (ev) => onPatch({ expTime: ev.target.value }),
|
|
367
435
|
})))),
|
|
368
436
|
h('div', { className: 'dad-sec' },
|
|
369
|
-
h('div', { className: 'dad-sec-title' }, 'SSH'),
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
437
|
+
h('div', { className: 'dad-sec-title' }, 'SSH 目标(模块可选发布到任一目标)'),
|
|
438
|
+
sshList.map((s, i) => {
|
|
439
|
+
const t = (sshTest || {})[i]
|
|
440
|
+
return h('div', { key: s.id || i, className: 'dad-sshtarget' },
|
|
441
|
+
h('div', { className: 'dad-row' },
|
|
442
|
+
h(Field, { label: '目标名称' },
|
|
443
|
+
h('input', {
|
|
444
|
+
className: 'dad-input', type: 'text', value: s.name, 'aria-label': 'SSH 目标名称',
|
|
445
|
+
onChange: (ev) => onPatchSsh(i, { name: ev.target.value }),
|
|
446
|
+
})),
|
|
447
|
+
h(Field, { label: 'ID(字母/数字/连字符)' },
|
|
448
|
+
h('input', {
|
|
449
|
+
className: 'dad-input', type: 'text', value: s.id, 'aria-label': 'SSH 目标 ID',
|
|
450
|
+
onChange: (ev) => onPatchSsh(i, { id: ev.target.value }),
|
|
451
|
+
})),
|
|
452
|
+
sshList.length > 1
|
|
453
|
+
? h('button', {
|
|
454
|
+
type: 'button', className: 'dad-btndel', title: '删除目标', 'aria-label': '删除 SSH 目标',
|
|
455
|
+
onClick: () => onDeleteSsh(i),
|
|
456
|
+
}, '×')
|
|
457
|
+
: null),
|
|
458
|
+
h('div', { className: 'dad-row' },
|
|
459
|
+
h(Field, { label: 'Host' },
|
|
460
|
+
h('input', {
|
|
461
|
+
className: 'dad-input', type: 'text', value: s.host, 'aria-label': 'SSH Host',
|
|
462
|
+
onChange: (ev) => onPatchSsh(i, { host: ev.target.value }),
|
|
463
|
+
})),
|
|
464
|
+
h(Field, { label: 'Port' },
|
|
465
|
+
h('input', {
|
|
466
|
+
className: 'dad-input', type: 'number', value: s.port, 'aria-label': 'SSH Port',
|
|
467
|
+
onChange: (ev) => onPatchSsh(i, { port: ev.target.value }),
|
|
468
|
+
})),
|
|
469
|
+
h(Field, { label: 'User' },
|
|
470
|
+
h('input', {
|
|
471
|
+
className: 'dad-input', type: 'text', value: s.user, 'aria-label': 'SSH User',
|
|
472
|
+
onChange: (ev) => onPatchSsh(i, { user: ev.target.value }),
|
|
473
|
+
}))),
|
|
474
|
+
h('div', { className: 'dad-row' },
|
|
475
|
+
h(Field, { label: '密码(留空=ssh-agent/密钥)' },
|
|
476
|
+
h('input', {
|
|
477
|
+
className: 'dad-input', type: 'password', value: s.password, 'aria-label': 'SSH 密码',
|
|
478
|
+
autoComplete: 'new-password',
|
|
479
|
+
onChange: (ev) => onPatchSsh(i, { password: ev.target.value }),
|
|
480
|
+
})),
|
|
481
|
+
h(Field, { label: '私钥路径(留空=默认密钥/agent,优先于选项里的 -i)', wide: true },
|
|
482
|
+
h('div', { className: 'dad-inline' },
|
|
483
|
+
h('input', {
|
|
484
|
+
className: 'dad-input', type: 'text', value: s.keyFile || '', 'aria-label': '私钥路径',
|
|
485
|
+
onChange: (ev) => onPatchSsh(i, { keyFile: ev.target.value }),
|
|
486
|
+
}),
|
|
487
|
+
h('button', { type: 'button', className: 'dad-btn', onClick: () => onBrowseKey(i) }, '浏览…')))),
|
|
488
|
+
h('div', { className: 'dad-row' },
|
|
489
|
+
h(Field, { label: '自定义 ssh 选项(如 -o StrictHostKeyChecking=no)', wide: true },
|
|
490
|
+
h('input', {
|
|
491
|
+
className: 'dad-input', type: 'text', value: s.options, 'aria-label': '自定义 ssh 选项',
|
|
492
|
+
onChange: (ev) => onPatchSsh(i, { options: ev.target.value }),
|
|
493
|
+
}))),
|
|
494
|
+
h('div', { className: 'dad-inline' },
|
|
495
|
+
h('button', {
|
|
496
|
+
type: 'button', className: 'dad-btn', onClick: () => onTestSsh(i),
|
|
497
|
+
disabled: t && t.state === 'run',
|
|
498
|
+
}, t && t.state === 'run' ? '测试中…' : '测试连接'),
|
|
499
|
+
t && t.state !== 'run'
|
|
500
|
+
? h('span', { className: 'dad-msg ' + (t.state === 'ok' ? 'ok' : 'err') }, t.text)
|
|
501
|
+
: null))
|
|
502
|
+
}),
|
|
386
503
|
h('div', { className: 'dad-row' },
|
|
387
|
-
h(
|
|
388
|
-
h('input', {
|
|
389
|
-
className: 'dad-input', type: 'password', value: ssh.password, 'aria-label': 'SSH 密码',
|
|
390
|
-
autoComplete: 'new-password',
|
|
391
|
-
onChange: (ev) => onPatchSsh({ password: ev.target.value }),
|
|
392
|
-
})),
|
|
393
|
-
h(Field, { label: '自定义 ssh 选项(如 -o StrictHostKeyChecking=no)', wide: true },
|
|
394
|
-
h('input', {
|
|
395
|
-
className: 'dad-input', type: 'text', value: ssh.options, 'aria-label': '自定义 ssh 选项',
|
|
396
|
-
onChange: (ev) => onPatchSsh({ options: ev.target.value }),
|
|
397
|
-
}))),
|
|
504
|
+
h('button', { type: 'button', className: 'dad-btn', onClick: onAddSsh }, '+ 添加 SSH 目标')),
|
|
398
505
|
h('div', { className: 'dad-hint' },
|
|
399
|
-
ssh
|
|
400
|
-
? '已配置密码,部署时将使用 sshpass(密码不会写入日志)'
|
|
401
|
-
: '密码留空时使用本机 ssh-agent / 密钥认证')),
|
|
506
|
+
'密码留空时使用本机 ssh-agent / 密钥认证;密码只经 sshpass 环境变量传递,不会写入日志')),
|
|
402
507
|
h('div', { className: 'dad-sec' },
|
|
403
508
|
h('div', { className: 'dad-sec-title' }, '构建 / 发布模块'),
|
|
404
509
|
env.modules.length === 0 ? h('div', { className: 'dad-hint' }, '(暂无模块)') : null,
|
|
405
510
|
env.modules.map((m, i) => h(ModuleCard, {
|
|
406
|
-
key: i, mod: m,
|
|
511
|
+
key: i, mod: m, sshs: sshList,
|
|
407
512
|
onChange: (field, value) => onPatchModule(i, field, value),
|
|
408
513
|
onDelete: () => onDeleteModule(i),
|
|
409
514
|
})),
|
|
@@ -470,14 +575,14 @@ window.__ModuleLoader__.load({
|
|
|
470
575
|
}, state.confirmLabel || '确定'))))
|
|
471
576
|
}
|
|
472
577
|
|
|
473
|
-
function BrowseModal({ state, onClose, onNavigate, onUp, onPick, onToggleHidden }) {
|
|
578
|
+
function BrowseModal({ state, onClose, onNavigate, onUp, onPick, onSelectFile, onToggleHidden }) {
|
|
474
579
|
return h('div', { className: 'dad-mask', role: 'presentation', onClick: onClose },
|
|
475
580
|
h('div', {
|
|
476
|
-
className: 'dad-modal', role: 'dialog', 'aria-modal': 'true', 'aria-label': '选择目录',
|
|
581
|
+
className: 'dad-modal', role: 'dialog', 'aria-modal': 'true', 'aria-label': state.file ? '选择文件' : '选择目录',
|
|
477
582
|
onClick: (ev) => ev.stopPropagation(),
|
|
478
583
|
},
|
|
479
584
|
h('div', { className: 'dad-modal-head' },
|
|
480
|
-
h('strong', null, '选择目录'),
|
|
585
|
+
h('strong', null, state.file ? '选择文件' : '选择目录'),
|
|
481
586
|
h('label', { className: 'dad-chk dad-autoflex' },
|
|
482
587
|
h('input', {
|
|
483
588
|
type: 'checkbox', checked: !!state.hidden,
|
|
@@ -506,16 +611,29 @@ window.__ModuleLoader__.load({
|
|
|
506
611
|
(state.dirs || []).map((dir) => h('button', {
|
|
507
612
|
key: dir, type: 'button', className: 'dad-dir',
|
|
508
613
|
onClick: () => onNavigate(dir),
|
|
509
|
-
}, dir)))
|
|
614
|
+
}, dir))),
|
|
615
|
+
state.file
|
|
616
|
+
? h('div', { className: 'dad-dirs' },
|
|
617
|
+
(state.files || []).length ? null : h('div', { className: 'dad-hint' }, '(无文件)'),
|
|
618
|
+
(state.files || []).map((f) => h('button', {
|
|
619
|
+
key: f, type: 'button', className: 'dad-dir', 'data-sel': state.sel === f ? '1' : '0',
|
|
620
|
+
onClick: () => onSelectFile(f),
|
|
621
|
+
}, f)))
|
|
622
|
+
: null),
|
|
510
623
|
h('div', { className: 'dad-modal-foot' },
|
|
511
624
|
h('button', {
|
|
512
625
|
type: 'button', className: 'dad-btn', onClick: onUp,
|
|
513
626
|
disabled: state.loading || !state.path || state.path === '/',
|
|
514
627
|
}, '上级'),
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
628
|
+
state.file
|
|
629
|
+
? h('button', {
|
|
630
|
+
type: 'button', className: 'dad-btn dad-btn-primary', onClick: onPick,
|
|
631
|
+
disabled: state.loading || !state.path || !state.sel,
|
|
632
|
+
}, '选择此文件')
|
|
633
|
+
: h('button', {
|
|
634
|
+
type: 'button', className: 'dad-btn dad-btn-primary', onClick: onPick,
|
|
635
|
+
disabled: state.loading || !state.path,
|
|
636
|
+
}, '选择当前目录'))))
|
|
519
637
|
}
|
|
520
638
|
|
|
521
639
|
// ---------- 主面板 ----------
|
|
@@ -535,6 +653,17 @@ window.__ModuleLoader__.load({
|
|
|
535
653
|
const [jdkOpen, setJdkOpen] = useState(false)
|
|
536
654
|
const jdkRef = useRef(null)
|
|
537
655
|
|
|
656
|
+
const [nodes, setNodes] = useState([])
|
|
657
|
+
const [nodeErr, setNodeErr] = useState('')
|
|
658
|
+
|
|
659
|
+
const [pms, setPms] = useState([])
|
|
660
|
+
const [pmErr, setPmErr] = useState('')
|
|
661
|
+
const [pmOpen, setPmOpen] = useState(false)
|
|
662
|
+
const pmRef = useRef(null)
|
|
663
|
+
const [sshTest, setSshTest] = useState({}) // { [目标index]: { state: 'run'|'ok'|'err', text } }
|
|
664
|
+
const [nodeOpen, setNodeOpen] = useState(false)
|
|
665
|
+
const nodeRef = useRef(null)
|
|
666
|
+
|
|
538
667
|
const [browse, setBrowse] = useState(null)
|
|
539
668
|
|
|
540
669
|
const [selected, setSelected] = useState([])
|
|
@@ -552,6 +681,7 @@ window.__ModuleLoader__.load({
|
|
|
552
681
|
const logRef = useRef(null)
|
|
553
682
|
const esRef = useRef(null)
|
|
554
683
|
const importRef = useRef(null)
|
|
684
|
+
const keyPickRef = useRef(null) // 文件浏览弹窗的选中回调(私钥选择用)
|
|
555
685
|
const dragIdRef = useRef(null)
|
|
556
686
|
const [draggingId, setDraggingId] = useState('')
|
|
557
687
|
const [dragOverId, setDragOverId] = useState('')
|
|
@@ -681,16 +811,17 @@ window.__ModuleLoader__.load({
|
|
|
681
811
|
if (el) el.scrollTop = el.scrollHeight
|
|
682
812
|
}, [logText])
|
|
683
813
|
|
|
684
|
-
// JDK 下拉:点击空白处关闭
|
|
814
|
+
// JDK/Node/包管理器 下拉:点击空白处关闭
|
|
685
815
|
useEffect(() => {
|
|
686
|
-
if (!jdkOpen) return undefined
|
|
816
|
+
if (!jdkOpen && !nodeOpen && !pmOpen) return undefined
|
|
687
817
|
function onDocDown(ev) {
|
|
688
|
-
|
|
689
|
-
if (
|
|
818
|
+
if (jdkOpen && jdkRef.current && !jdkRef.current.contains(ev.target)) setJdkOpen(false)
|
|
819
|
+
if (nodeOpen && nodeRef.current && !nodeRef.current.contains(ev.target)) setNodeOpen(false)
|
|
820
|
+
if (pmOpen && pmRef.current && !pmRef.current.contains(ev.target)) setPmOpen(false)
|
|
690
821
|
}
|
|
691
822
|
document.addEventListener('mousedown', onDocDown)
|
|
692
823
|
return () => document.removeEventListener('mousedown', onDocDown)
|
|
693
|
-
}, [jdkOpen])
|
|
824
|
+
}, [jdkOpen, nodeOpen, pmOpen])
|
|
694
825
|
|
|
695
826
|
// ---------- 环境操作 ----------
|
|
696
827
|
|
|
@@ -702,8 +833,8 @@ window.__ModuleLoader__.load({
|
|
|
702
833
|
function addEnv() {
|
|
703
834
|
const env = {
|
|
704
835
|
id: newEnvId(envs), name: '新环境', note: '', group: '',
|
|
705
|
-
projectPath: defProject, jdkHome: '', expTime: '',
|
|
706
|
-
|
|
836
|
+
projectPath: defProject, jdkHome: '', nodeHome: '', nodeOptions: '', pmPath: '', buildEnv: '', expTime: '',
|
|
837
|
+
sshs: [{ id: 'default', name: '默认', host: '', port: 22, user: 'root', password: '', keyFile: '', options: '' }],
|
|
707
838
|
modules: [],
|
|
708
839
|
}
|
|
709
840
|
setDraft(env)
|
|
@@ -836,8 +967,43 @@ window.__ModuleLoader__.load({
|
|
|
836
967
|
function patchEnv(patch) {
|
|
837
968
|
setDraft((prev) => (prev ? { ...prev, ...patch } : prev))
|
|
838
969
|
}
|
|
839
|
-
function patchSsh(patch) {
|
|
840
|
-
setDraft((prev) => (prev
|
|
970
|
+
function patchSsh(index, patch) {
|
|
971
|
+
setDraft((prev) => (prev
|
|
972
|
+
? { ...prev, sshs: (prev.sshs || []).map((s, i) => (i === index ? { ...s, ...patch } : s)) }
|
|
973
|
+
: prev))
|
|
974
|
+
}
|
|
975
|
+
function addSsh() {
|
|
976
|
+
setDraft((prev) => {
|
|
977
|
+
if (!prev) return prev
|
|
978
|
+
const n = (prev.sshs || []).length + 1
|
|
979
|
+
return {
|
|
980
|
+
...prev,
|
|
981
|
+
sshs: [...(prev.sshs || []), { id: 'ssh-' + Date.now().toString(36), name: 'SSH ' + n, host: '', port: 22, user: 'root', password: '', keyFile: '', options: '' }],
|
|
982
|
+
}
|
|
983
|
+
})
|
|
984
|
+
}
|
|
985
|
+
function deleteSsh(index) {
|
|
986
|
+
setDraft((prev) => (prev && (prev.sshs || []).length > 1
|
|
987
|
+
? { ...prev, sshs: prev.sshs.filter((_, i) => i !== index) }
|
|
988
|
+
: prev))
|
|
989
|
+
}
|
|
990
|
+
// 测试连接:直接用表单当前值(无需先保存环境)
|
|
991
|
+
function runSshTest(index) {
|
|
992
|
+
const t = draft && draft.sshs && draft.sshs[index]
|
|
993
|
+
if (!t) return
|
|
994
|
+
setSshTest((p) => ({ ...p, [index]: { state: 'run', text: '测试中…' } }))
|
|
995
|
+
rpc('sshTest', {
|
|
996
|
+
target: {
|
|
997
|
+
host: (t.host || '').trim(), port: Number(t.port) || 22,
|
|
998
|
+
user: (t.user || '').trim() || 'root', password: t.password || '',
|
|
999
|
+
keyFile: (t.keyFile || '').trim(), options: (t.options || '').trim(),
|
|
1000
|
+
},
|
|
1001
|
+
})
|
|
1002
|
+
.then((r) => setSshTest((p) => ({
|
|
1003
|
+
...p,
|
|
1004
|
+
[index]: r && r.ok ? { state: 'ok', text: '连接成功(' + r.ms + ' ms)' } : { state: 'err', text: '失败:' + ((r && r.error) || '未知错误') },
|
|
1005
|
+
})))
|
|
1006
|
+
.catch((e) => setSshTest((p) => ({ ...p, [index]: { state: 'err', text: '失败:' + String(e?.message || e) } })))
|
|
841
1007
|
}
|
|
842
1008
|
function patchModule(index, field, value) {
|
|
843
1009
|
setDraft((prev) => {
|
|
@@ -879,22 +1045,28 @@ window.__ModuleLoader__.load({
|
|
|
879
1045
|
group: (draft.group || '').trim(),
|
|
880
1046
|
projectPath: (draft.projectPath || '').trim() || defProject,
|
|
881
1047
|
jdkHome: (draft.jdkHome || '').trim(),
|
|
1048
|
+
nodeHome: (draft.nodeHome || '').trim(),
|
|
1049
|
+
nodeOptions: (draft.nodeOptions || '').trim(),
|
|
1050
|
+
pmPath: (draft.pmPath || '').trim(),
|
|
1051
|
+
buildEnv: draft.buildEnv || '',
|
|
882
1052
|
expTime: draft.expTime || '',
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
1053
|
+
sshs: (draft.sshs || []).map((s) => ({
|
|
1054
|
+
id: (s.id || '').trim(),
|
|
1055
|
+
name: (s.name || '').trim(),
|
|
1056
|
+
host: (s.host || '').trim(),
|
|
1057
|
+
port: Number(s.port) || 22,
|
|
1058
|
+
user: (s.user || '').trim() || 'root',
|
|
1059
|
+
password: s.password || '',
|
|
1060
|
+
keyFile: (s.keyFile || '').trim(),
|
|
1061
|
+
options: (s.options || '').trim(),
|
|
1062
|
+
})),
|
|
890
1063
|
modules: draft.modules.map((m) => ({
|
|
891
1064
|
id: (m.id || '').trim(),
|
|
892
1065
|
name: (m.name || '').trim() || '新模块',
|
|
893
1066
|
kind: m.kind === 'server' ? 'server' : 'front',
|
|
894
1067
|
enabled: !!m.enabled,
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
extraArgs: (m.extraArgs || '').trim(),
|
|
1068
|
+
buildCmd: (m.buildCmd || '').trim(),
|
|
1069
|
+
sshId: (m.sshId || '').trim(),
|
|
898
1070
|
localPath: (m.localPath || '').trim(),
|
|
899
1071
|
remotePath: (m.remotePath || '').trim(),
|
|
900
1072
|
postCommand: (m.postCommand || '').trim(),
|
|
@@ -916,30 +1088,36 @@ window.__ModuleLoader__.load({
|
|
|
916
1088
|
|
|
917
1089
|
// ---------- 路径浏览 ----------
|
|
918
1090
|
|
|
919
|
-
function loadBrowse(path, hidden) {
|
|
1091
|
+
function loadBrowse(path, hidden, file) {
|
|
920
1092
|
setBrowse((prev) => ({
|
|
921
1093
|
path: prev ? prev.path : '',
|
|
922
1094
|
parent: prev ? prev.parent : '',
|
|
923
1095
|
dirs: prev ? prev.dirs : [],
|
|
924
1096
|
worktrees: prev ? prev.worktrees : [],
|
|
1097
|
+
files: prev && prev.file === !!file ? prev.files : [],
|
|
1098
|
+
file: !!file, sel: '',
|
|
925
1099
|
hidden: !!hidden, loading: true, error: '',
|
|
926
1100
|
}))
|
|
927
1101
|
const args = {}
|
|
928
1102
|
if (path) args.path = path
|
|
929
1103
|
if (hidden) args.hidden = true
|
|
1104
|
+
if (file) args.file = true
|
|
930
1105
|
rpc('browse', args).then((d) => {
|
|
931
1106
|
setBrowse({
|
|
932
1107
|
path: (d && d.path) || '',
|
|
933
1108
|
parent: (d && d.parent) || parentPath((d && d.path) || ''),
|
|
934
1109
|
dirs: Array.isArray(d && d.dirs) ? d.dirs : [],
|
|
1110
|
+
files: Array.isArray(d && d.files) ? d.files : [],
|
|
935
1111
|
worktrees: Array.isArray(d && d.worktrees) ? d.worktrees : [],
|
|
1112
|
+
file: !!file, sel: '',
|
|
936
1113
|
hidden: !!hidden, loading: false, error: '',
|
|
937
1114
|
})
|
|
938
1115
|
}).catch((error) => {
|
|
939
1116
|
setBrowse((prev) => ({
|
|
940
1117
|
path: prev ? prev.path : '',
|
|
941
1118
|
parent: prev ? prev.parent : '',
|
|
942
|
-
dirs: [], worktrees: [],
|
|
1119
|
+
dirs: [], worktrees: [], files: [],
|
|
1120
|
+
file: !!file, sel: '',
|
|
943
1121
|
hidden: !!hidden, loading: false,
|
|
944
1122
|
error: error.message || String(error),
|
|
945
1123
|
}))
|
|
@@ -947,24 +1125,39 @@ window.__ModuleLoader__.load({
|
|
|
947
1125
|
}
|
|
948
1126
|
function openBrowse() {
|
|
949
1127
|
if (!draft) return
|
|
950
|
-
|
|
1128
|
+
keyPickRef.current = null
|
|
1129
|
+
loadBrowse((draft.projectPath || '').trim(), false, false)
|
|
1130
|
+
}
|
|
1131
|
+
// 选择私钥文件:从当前 keyFile 所在目录(或 ~/.ssh)起步,默认显示隐藏文件(密钥多为点开头无,但 .ssh 是隐藏目录)
|
|
1132
|
+
function openKeyBrowse(index) {
|
|
1133
|
+
const t = draft && draft.sshs && draft.sshs[index]
|
|
1134
|
+
keyPickRef.current = (p) => patchSsh(index, { keyFile: p })
|
|
1135
|
+
loadBrowse(t && t.keyFile ? parentPath(t.keyFile) : '~/.ssh', true, true)
|
|
951
1136
|
}
|
|
952
1137
|
function navigateBrowse(target) {
|
|
953
1138
|
if (!browse) return
|
|
954
|
-
loadBrowse(joinPath(browse.path, target), browse.hidden)
|
|
1139
|
+
loadBrowse(joinPath(browse.path, target), browse.hidden, browse.file)
|
|
955
1140
|
}
|
|
956
1141
|
function browseUp() {
|
|
957
1142
|
if (!browse) return
|
|
958
|
-
loadBrowse(browse.parent || parentPath(browse.path), browse.hidden)
|
|
1143
|
+
loadBrowse(browse.parent || parentPath(browse.path), browse.hidden, browse.file)
|
|
959
1144
|
}
|
|
960
1145
|
function browsePick() {
|
|
961
1146
|
if (!browse || !browse.path) return
|
|
962
|
-
|
|
1147
|
+
if (browse.file) {
|
|
1148
|
+
if (!browse.sel) return
|
|
1149
|
+
if (keyPickRef.current) keyPickRef.current(joinPath(browse.path, browse.sel))
|
|
1150
|
+
} else {
|
|
1151
|
+
patchEnv({ projectPath: browse.path })
|
|
1152
|
+
}
|
|
963
1153
|
setBrowse(null)
|
|
964
1154
|
}
|
|
1155
|
+
function browseSelectFile(name) {
|
|
1156
|
+
setBrowse((prev) => (prev ? { ...prev, sel: name } : prev))
|
|
1157
|
+
}
|
|
965
1158
|
function browseToggleHidden(hidden) {
|
|
966
1159
|
if (!browse) return
|
|
967
|
-
loadBrowse(browse.path, hidden)
|
|
1160
|
+
loadBrowse(browse.path, hidden, browse.file)
|
|
968
1161
|
}
|
|
969
1162
|
|
|
970
1163
|
// ---------- JDK ----------
|
|
@@ -980,6 +1173,32 @@ window.__ModuleLoader__.load({
|
|
|
980
1173
|
setJdkOpen(false)
|
|
981
1174
|
}
|
|
982
1175
|
|
|
1176
|
+
// ---------- Node ----------
|
|
1177
|
+
|
|
1178
|
+
function openNode() {
|
|
1179
|
+
setNodeOpen(true)
|
|
1180
|
+
if (!nodes.length) {
|
|
1181
|
+
rpc('nodes').then((list) => setNodes(Array.isArray(list) ? list : [])).catch((e) => setNodeErr(String(e?.message || e)))
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
function pickNode(path) {
|
|
1185
|
+
patchEnv({ nodeHome: path })
|
|
1186
|
+
setNodeOpen(false)
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// ---------- 包管理器 ----------
|
|
1190
|
+
|
|
1191
|
+
function openPm() {
|
|
1192
|
+
setPmOpen(true)
|
|
1193
|
+
if (!pms.length) {
|
|
1194
|
+
rpc('packageManagers').then((list) => setPms(Array.isArray(list) ? list : [])).catch((e) => setPmErr(String(e?.message || e)))
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
function pickPm(path) {
|
|
1198
|
+
patchEnv({ pmPath: path })
|
|
1199
|
+
setPmOpen(false)
|
|
1200
|
+
}
|
|
1201
|
+
|
|
983
1202
|
// ---------- 部署 ----------
|
|
984
1203
|
|
|
985
1204
|
function toggleSel(id) {
|
|
@@ -1015,11 +1234,13 @@ window.__ModuleLoader__.load({
|
|
|
1015
1234
|
lines.push('')
|
|
1016
1235
|
lines.push('将依次执行:')
|
|
1017
1236
|
for (const m of mods) {
|
|
1018
|
-
|
|
1237
|
+
// expTime 追加规则与服务端一致:仅首 token 是 gradle 的 server 模块
|
|
1238
|
+
const exp = m.kind === 'server' && env.expTime && ((m.buildCmd || '').trim().split(/\s+/)[0] || '').includes('gradle') ? ' -PexpTime=' + env.expTime : ''
|
|
1019
1239
|
lines.push('')
|
|
1020
1240
|
lines.push('[' + (m.name || m.id) + ']')
|
|
1021
|
-
|
|
1022
|
-
lines.push('
|
|
1241
|
+
const target = targetOf(env, m)
|
|
1242
|
+
lines.push(' 构建: ' + m.buildCmd + exp)
|
|
1243
|
+
lines.push(' rsync: ' + m.localPath + ' → ' + (target.user || 'root') + '@' + (target.host || '?') + ':' + m.remotePath + (env.sshs && env.sshs.length > 1 ? '(目标: ' + (target.name || target.id) + ')' : ''))
|
|
1023
1244
|
if (m.postCommand) lines.push(' ssh: ' + m.postCommand)
|
|
1024
1245
|
}
|
|
1025
1246
|
return lines.join('\n')
|
|
@@ -1028,8 +1249,12 @@ window.__ModuleLoader__.load({
|
|
|
1028
1249
|
function requestDeploy() {
|
|
1029
1250
|
if (!draft || !selected.length) return
|
|
1030
1251
|
if (isNew) { setDeployState({ running: false, text: '请先保存环境再执行部署', kind: 'err' }); return }
|
|
1031
|
-
|
|
1032
|
-
|
|
1252
|
+
const deployTargets = [...new Set(selected.map((id) => {
|
|
1253
|
+
const m = moduleById[id]
|
|
1254
|
+
return m && targetOf(draft, m)
|
|
1255
|
+
}).filter(Boolean))]
|
|
1256
|
+
if (!deployTargets.length || deployTargets.some((t) => !t.host || !t.user)) {
|
|
1257
|
+
setDeployState({ running: false, text: '所选模块的 SSH 目标配置不完整,请先保存 SSH 配置', kind: 'err' })
|
|
1033
1258
|
return
|
|
1034
1259
|
}
|
|
1035
1260
|
const mods = selected.map((id) => moduleById[id]).filter((m) => m && m.enabled)
|
|
@@ -1126,7 +1351,7 @@ window.__ModuleLoader__.load({
|
|
|
1126
1351
|
const canRun = !!draft && !isNew && selected.length > 0 && !deployState.running
|
|
1127
1352
|
|
|
1128
1353
|
// 标题行:对象名 + 一句「现状」(ID / 最近部署结果 / SSH 目标),全部来自已有数据
|
|
1129
|
-
const selSsh = draft && draft.
|
|
1354
|
+
const selSsh = draft && draft.sshs && draft.sshs[0] ? draft.sshs[0] : {}
|
|
1130
1355
|
const selSshText = selSsh.host ? (selSsh.user || 'root') + '@' + selSsh.host + ':' + (selSsh.port || 22) : ''
|
|
1131
1356
|
const selBuild = draft ? lastBuild[draft.id] : null
|
|
1132
1357
|
let titleSub = '选择左侧环境,或新增一个环境'
|
|
@@ -1285,13 +1510,16 @@ window.__ModuleLoader__.load({
|
|
|
1285
1510
|
onToggle: () => setFormOpen((v) => !v),
|
|
1286
1511
|
onPatch: patchEnv, onPatchSsh: patchSsh, onPatchModule: patchModule,
|
|
1287
1512
|
onAddModule: addModule, onDeleteModule: askDeleteModule,
|
|
1288
|
-
|
|
1513
|
+
onAddSsh: addSsh, onDeleteSsh: deleteSsh, onTestSsh: runSshTest, sshTest,
|
|
1514
|
+
onSave: saveEnv, onDelete: deleteCurrentEnv, onBrowse: openBrowse, onBrowseKey: openKeyBrowse,
|
|
1289
1515
|
jdks, jdkOpen, jdkRef, onJdkFocus: openJdk, onJdkPick: pickJdk,
|
|
1516
|
+
nodes, nodeOpen, nodeRef, nodeErr, onNodeFocus: openNode, onNodePick: pickNode,
|
|
1517
|
+
pms, pmOpen, pmRef, pmErr, onPmFocus: openPm, onPmPick: pickPm,
|
|
1290
1518
|
})),
|
|
1291
1519
|
confirmState ? h(ConfirmDialog, { state: confirmState, onClose: () => setConfirmState(null) }) : null,
|
|
1292
1520
|
browse ? h(BrowseModal, {
|
|
1293
1521
|
state: browse, onClose: () => setBrowse(null),
|
|
1294
|
-
onNavigate: navigateBrowse, onUp: browseUp, onPick: browsePick,
|
|
1522
|
+
onNavigate: navigateBrowse, onUp: browseUp, onPick: browsePick, onSelectFile: browseSelectFile,
|
|
1295
1523
|
onToggleHidden: browseToggleHidden,
|
|
1296
1524
|
}) : null)
|
|
1297
1525
|
}
|
|
@@ -1378,6 +1606,7 @@ window.__ModuleLoader__.load({
|
|
|
1378
1606
|
'.dad-btn-danger-solid:hover:not(:disabled){background:var(--dsw-alias-state-error-primary)}',
|
|
1379
1607
|
'.dad-formfoot{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:4px}',
|
|
1380
1608
|
'.dad-mcard{position:relative;min-width:0;padding:10px 12px;margin-bottom:10px;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-layer-2)}',
|
|
1609
|
+
'.dad-sshtarget{position:relative;min-width:0;padding:8px 10px 10px;margin-bottom:8px;border:.5px solid var(--dsw-alias-border-l3);border-radius:var(--dsw-radius-lg);background:var(--dsw-alias-bg-layer-1)}',
|
|
1381
1610
|
'.dad-mcard[data-off="1"]{opacity:.6}',
|
|
1382
1611
|
'.dad-btndel{position:absolute;top:4px;right:6px;border:0;background:none;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:1;padding:2px 4px;cursor:pointer}',
|
|
1383
1612
|
'.dad-btndel:hover{color:var(--dsw-alias-state-error-primary)}',
|