dsh-github-router 0.3.1 → 0.4.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.
- package/CHANGELOG.md +79 -56
- package/CONTRIBUTING.md +17 -6
- package/README.md +26 -18
- package/README.zh.md +22 -17
- package/docs/design.md +51 -43
- package/lib/client.js +165 -116
- package/lib/config.js +167 -116
- package/lib/core/issue.js +212 -202
- package/lib/core/pr.js +454 -429
- package/lib/index.js +11 -6
- package/lib/routes/gh.js +120 -109
- package/lib/routes/html.js +266 -250
- package/lib/settings.js +23 -21
- package/lib/tunnel.js +363 -247
- package/package.json +11 -17
package/lib/client.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-github-router — client half (hand-written factory bundle, no build step).
|
|
3
3
|
*
|
|
4
|
-
* One
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* One configuration page for the plugin's Loader row, registered into the
|
|
5
|
+
* Plugins page's `plugins.row.config` slot under the row key
|
|
6
|
+
* `<package name>#<row id>` (DSH ≥ 0.1.7-rc.2). The Plugins page owns the
|
|
7
|
+
* frame — title, icon, crumb, and the row's Configure control — and asks this
|
|
8
|
+
* entry for `view: 'summary'` (the row's one-liner) or `view: 'page'` (the
|
|
9
|
+
* form). No plugin-owned HTTP route exists: reads and writes ride the shared
|
|
10
|
+
* configuration form the settings domain provides per Host entry
|
|
11
|
+
* (`ctx.configForms.get(namespace)`), whose writes are revision-fenced and
|
|
12
|
+
* recovered by the framework.
|
|
11
13
|
*
|
|
12
14
|
* The form keeps the staged-editing model: edits are staged locally and
|
|
13
|
-
* written only on save;
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* describe mirror's secret slot list.
|
|
15
|
+
* written only on save; leaving the page drops them. The card verifies the
|
|
16
|
+
* user layer after a write and keeps drafts that did not land. Secret fields
|
|
17
|
+
* (the GitHub token) never ride a response, so the token control starts blank
|
|
18
|
+
* and a blank draft writes nothing — the describe mirror's secret-slot list is
|
|
19
|
+
* the only signal that one is configured, and an explicit clear control stages
|
|
20
|
+
* the `unset` that removes it.
|
|
20
21
|
*
|
|
21
|
-
* The
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* namespace.
|
|
22
|
+
* The bundle requires only `react` and `@deepseek-ai/dsh-client-store`, both
|
|
23
|
+
* members of the shell's frozen platform module table, so it declares no
|
|
24
|
+
* `dsh.client.inject` rows. All copy goes through the framework locale service
|
|
25
|
+
* under the `dsh-github-router` namespace.
|
|
26
26
|
*/
|
|
27
27
|
window.__ModuleLoader__.load({
|
|
28
28
|
id: 'dsh-github-router',
|
|
@@ -34,6 +34,10 @@ window.__ModuleLoader__.load({
|
|
|
34
34
|
|
|
35
35
|
var NAMESPACE = 'dsh-github-router';
|
|
36
36
|
var NS = 'dsh-github-router';
|
|
37
|
+
// `plugins.row.config` is keyed by `<package name>#<row id>`; this bundle's
|
|
38
|
+
// cordis.patch.yml declares both as `dsh-github-router`, so the row page
|
|
39
|
+
// and the settings namespace address the same entry.
|
|
40
|
+
var ROW_CONFIG_KEY = 'dsh-github-router#dsh-github-router';
|
|
37
41
|
|
|
38
42
|
// ------------------------------------------------------------ specs
|
|
39
43
|
function textSpec(field) {
|
|
@@ -114,17 +118,18 @@ window.__ModuleLoader__.load({
|
|
|
114
118
|
|
|
115
119
|
// ------------------------------------------------------------- i18n
|
|
116
120
|
var zhDict = {
|
|
117
|
-
'nav': 'GitHub 路由',
|
|
118
121
|
'description': 'GitHub 读取的路由与缓存配置。',
|
|
119
122
|
'advanced': '高级设置',
|
|
120
123
|
'save': '保存',
|
|
121
124
|
'saving': '保存中…',
|
|
122
|
-
'discard': '放弃修改',
|
|
123
125
|
'failed': '保存失败,请重试。',
|
|
124
126
|
'readOnly': '当前部署不允许修改设置。',
|
|
127
|
+
'unavailable': '该插件当前未加载,暂时无法配置。',
|
|
125
128
|
'invalid': '无效值',
|
|
126
129
|
'overridden': '已覆盖',
|
|
127
130
|
'configured': '已配置',
|
|
131
|
+
'clearToken': '清除已保存的 token',
|
|
132
|
+
'willClear': '保存后清除',
|
|
128
133
|
'field.token': 'GitHub Token',
|
|
129
134
|
'field.tokenEnv': 'Token 环境变量',
|
|
130
135
|
'field.proxy': '代理地址',
|
|
@@ -142,7 +147,7 @@ window.__ModuleLoader__.load({
|
|
|
142
147
|
'field.mirrors': '镜像基址(逗号分隔)',
|
|
143
148
|
'field.repos': '本地仓库白名单(逗号分隔)',
|
|
144
149
|
'field.gitCacheDir': 'git 缓存目录',
|
|
145
|
-
'hint.token': '
|
|
150
|
+
'hint.token': '只写字段:留空保存表示保持当前 token 不变;用「清除」按钮删除已保存的 token。优先使用 tokenEnv。',
|
|
146
151
|
'hint.tokenEnv': '指向 token 的环境变量名,例如 GITHUB_TOKEN。',
|
|
147
152
|
'hint.proxy': '空 = 继承环境代理;direct = 永不代理;其余为代理 URL。',
|
|
148
153
|
'hint.maxBytes': '每个响应体的字节上限(16384..8388608)。',
|
|
@@ -154,17 +159,18 @@ window.__ModuleLoader__.load({
|
|
|
154
159
|
'hint.gitCacheDir': '空 = <DSH_HOME>/storages/dsh-github-router/git。',
|
|
155
160
|
};
|
|
156
161
|
var enDict = {
|
|
157
|
-
'nav': 'GitHub Router',
|
|
158
162
|
'description': 'Routing and cache configuration for GitHub reads.',
|
|
159
163
|
'advanced': 'Advanced settings',
|
|
160
164
|
'save': 'Save',
|
|
161
165
|
'saving': 'Saving…',
|
|
162
|
-
'discard': 'Discard',
|
|
163
166
|
'failed': 'The save failed. Please retry.',
|
|
164
167
|
'readOnly': 'This deployment does not allow settings edits.',
|
|
168
|
+
'unavailable': 'This plugin is not loaded, so it cannot be configured right now.',
|
|
165
169
|
'invalid': 'invalid',
|
|
166
170
|
'overridden': 'overridden',
|
|
167
171
|
'configured': 'configured',
|
|
172
|
+
'clearToken': 'Clear stored token',
|
|
173
|
+
'willClear': 'clears on save',
|
|
168
174
|
'field.token': 'GitHub token',
|
|
169
175
|
'field.tokenEnv': 'Token env var',
|
|
170
176
|
'field.proxy': 'Proxy URL',
|
|
@@ -182,7 +188,7 @@ window.__ModuleLoader__.load({
|
|
|
182
188
|
'field.mirrors': 'Mirror bases (comma-separated)',
|
|
183
189
|
'field.repos': 'Local repo allowlist (comma-separated)',
|
|
184
190
|
'field.gitCacheDir': 'Git cache directory',
|
|
185
|
-
'hint.token': 'Write-only: a blank
|
|
191
|
+
'hint.token': 'Write-only: a blank draft keeps the stored token; the Clear control removes it. Prefer tokenEnv.',
|
|
186
192
|
'hint.tokenEnv': 'Environment variable naming the token, e.g. GITHUB_TOKEN.',
|
|
187
193
|
'hint.proxy': "Empty = inherit ambient proxy env; 'direct' = never proxy; anything else is a proxy URL.",
|
|
188
194
|
'hint.maxBytes': 'Byte cap per response body (16384..8388608).',
|
|
@@ -201,16 +207,20 @@ window.__ModuleLoader__.load({
|
|
|
201
207
|
}
|
|
202
208
|
|
|
203
209
|
/**
|
|
204
|
-
* Stages one
|
|
205
|
-
* them on save.
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
210
|
+
* Stages one page's edits over the plugin's settings namespace and writes
|
|
211
|
+
* them on save.
|
|
212
|
+
*
|
|
213
|
+
* The scope is the shared configuration form the settings domain provides
|
|
214
|
+
* for this Host entry (`ctx.configForms.get(namespace)`): its snapshot
|
|
215
|
+
* carries the redacted resolved section
|
|
216
|
+
* (`status`/`value`/`base`/`user`/`revision`/`writable`/`mode`), and
|
|
217
|
+
* `set`/`unset` queue one revision-fenced, recovered write each. The
|
|
218
|
+
* describe mirror supplements the snapshot with the secret-slot list, the
|
|
219
|
+
* only signal that a redacted field (the token) is configured.
|
|
220
|
+
* @param scope - the shared configuration form of this entry.
|
|
221
|
+
* @param describe - the deployment-wide describe face, or null when absent.
|
|
212
222
|
*/
|
|
213
|
-
function GithubRouterForm(scope,
|
|
223
|
+
function GithubRouterForm(scope, describe) {
|
|
214
224
|
var staged = new Map(); // field -> { text, clear }
|
|
215
225
|
var saving = false;
|
|
216
226
|
var failed = false;
|
|
@@ -237,14 +247,25 @@ window.__ModuleLoader__.load({
|
|
|
237
247
|
var s = snap();
|
|
238
248
|
return !!(s.user && typeof s.user === 'object' && Object.prototype.hasOwnProperty.call(s.user, field));
|
|
239
249
|
}
|
|
240
|
-
/**
|
|
250
|
+
/**
|
|
251
|
+
* Whether a redacted secret slot holds a value. Secret fields never ride
|
|
252
|
+
* the namespace value, so the describe mirror's per-namespace `secrets`
|
|
253
|
+
* list is the only source for this fact.
|
|
254
|
+
*/
|
|
241
255
|
function secretSet(field) {
|
|
242
|
-
if (
|
|
243
|
-
var
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
256
|
+
if (describe === null || typeof describe.getSnapshot !== 'function') return false;
|
|
257
|
+
var mirror = describe.getSnapshot();
|
|
258
|
+
var view = mirror === null || mirror === undefined ? undefined : mirror.view;
|
|
259
|
+
var rows = view !== undefined && view !== null && Array.isArray(view.namespaces) ? view.namespaces : [];
|
|
260
|
+
for (var i = 0; i < rows.length; i++) {
|
|
261
|
+
var row = rows[i];
|
|
262
|
+
if (row === null || row === undefined || row.ns !== NAMESPACE) continue;
|
|
263
|
+
var secrets = Array.isArray(row.secrets) ? row.secrets : [];
|
|
264
|
+
for (var j = 0; j < secrets.length; j++) {
|
|
265
|
+
var slot = secrets[j];
|
|
266
|
+
if (Array.isArray(slot.path) && slot.path.length === 1 && slot.path[0] === field) return slot.set === true;
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
248
269
|
}
|
|
249
270
|
return false;
|
|
250
271
|
}
|
|
@@ -257,10 +278,12 @@ window.__ModuleLoader__.load({
|
|
|
257
278
|
var stagedEntry = staged.get(f.field);
|
|
258
279
|
var text;
|
|
259
280
|
var overridden;
|
|
281
|
+
var clearing = false;
|
|
260
282
|
var invalidField = false;
|
|
261
283
|
if (stagedEntry !== undefined) {
|
|
262
284
|
text = stagedEntry.text;
|
|
263
|
-
|
|
285
|
+
clearing = stagedEntry.clear === true;
|
|
286
|
+
if (clearing) {
|
|
264
287
|
overridden = false;
|
|
265
288
|
} else {
|
|
266
289
|
var parsedStaged = f.spec.parse(stagedEntry.text);
|
|
@@ -271,14 +294,15 @@ window.__ModuleLoader__.load({
|
|
|
271
294
|
}
|
|
272
295
|
} else {
|
|
273
296
|
text = f.kind === 'secret' ? '' : f.spec.format(sectionValue(f.field));
|
|
274
|
-
overridden = f.kind === 'secret' ?
|
|
297
|
+
overridden = f.kind === 'secret' ? false : stored(f.field);
|
|
275
298
|
}
|
|
276
299
|
if (invalidField) invalid = true;
|
|
277
300
|
fields[f.field] = {
|
|
278
301
|
text: text,
|
|
279
302
|
overridden: overridden,
|
|
280
303
|
invalid: invalidField,
|
|
281
|
-
configured: f.kind === 'secret'
|
|
304
|
+
configured: f.kind === 'secret' ? secretSet(f.field) : false,
|
|
305
|
+
clearing: clearing,
|
|
282
306
|
};
|
|
283
307
|
});
|
|
284
308
|
store.set({
|
|
@@ -291,12 +315,16 @@ window.__ModuleLoader__.load({
|
|
|
291
315
|
fields: fields,
|
|
292
316
|
});
|
|
293
317
|
}
|
|
294
|
-
scope.subscribe(publish);
|
|
295
|
-
|
|
318
|
+
var offScope = scope.subscribe(publish);
|
|
319
|
+
var offDescribe = describe !== null && typeof describe.subscribe === 'function' ? describe.subscribe(publish) : null;
|
|
296
320
|
publish();
|
|
297
321
|
|
|
298
322
|
function edit(field, text) {
|
|
299
|
-
|
|
323
|
+
var value = String(text);
|
|
324
|
+
// A blank secret draft carries no write: the stored secret stays, and
|
|
325
|
+
// the explicit clear control is what removes it.
|
|
326
|
+
if (field === 'token' && value.trim() === '') staged.delete(field);
|
|
327
|
+
else staged.set(field, { text: value, clear: false });
|
|
300
328
|
failed = false;
|
|
301
329
|
publish();
|
|
302
330
|
}
|
|
@@ -317,29 +345,10 @@ window.__ModuleLoader__.load({
|
|
|
317
345
|
var blocked = false;
|
|
318
346
|
staged.forEach(function (stagedEntry, field) {
|
|
319
347
|
var spec = SPECS[field];
|
|
320
|
-
if (stagedEntry.clear) {
|
|
321
|
-
if (field === 'token') {
|
|
322
|
-
// A blank draft clears the token only when one is configured.
|
|
323
|
-
if (secretSet(field)) {
|
|
324
|
-
writes.push({
|
|
325
|
-
field: field,
|
|
326
|
-
run: function () { return scope.unset(field).then(function () { return !secretSet(field); }); },
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
if (stored(field)) {
|
|
332
|
-
writes.push({
|
|
333
|
-
field: field,
|
|
334
|
-
run: function () { return scope.unset(field).then(function () { return !stored(field); }); },
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
348
|
if (field === 'token') {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
349
|
+
// A blank draft never clears a configured token; only the clear
|
|
350
|
+
// control stages the `unset`, and only while one is configured.
|
|
351
|
+
if (stagedEntry.clear) {
|
|
343
352
|
if (secretSet(field)) {
|
|
344
353
|
writes.push({
|
|
345
354
|
field: field,
|
|
@@ -348,14 +357,24 @@ window.__ModuleLoader__.load({
|
|
|
348
357
|
}
|
|
349
358
|
return;
|
|
350
359
|
}
|
|
360
|
+
if (stagedEntry.text.trim() === '') return;
|
|
351
361
|
writes.push({
|
|
352
362
|
field: field,
|
|
353
363
|
run: (function (value) {
|
|
354
364
|
return function () { return scope.set(field, value).then(function () { return secretSet(field); }); };
|
|
355
|
-
})(
|
|
365
|
+
})(stagedEntry.text.trim()),
|
|
356
366
|
});
|
|
357
367
|
return;
|
|
358
368
|
}
|
|
369
|
+
if (stagedEntry.clear) {
|
|
370
|
+
if (stored(field)) {
|
|
371
|
+
writes.push({
|
|
372
|
+
field: field,
|
|
373
|
+
run: function () { return scope.unset(field).then(function () { return !stored(field); }); },
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
359
378
|
if (stagedEntry.text === spec.format(sectionValue(field))) return;
|
|
360
379
|
var write = spec.parse(stagedEntry.text);
|
|
361
380
|
if (write === undefined) {
|
|
@@ -363,10 +382,12 @@ window.__ModuleLoader__.load({
|
|
|
363
382
|
return;
|
|
364
383
|
}
|
|
365
384
|
if (write.kind === 'clear') {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
385
|
+
if (stored(field)) {
|
|
386
|
+
writes.push({
|
|
387
|
+
field: field,
|
|
388
|
+
run: function () { return scope.unset(field).then(function () { return !stored(field); }); },
|
|
389
|
+
});
|
|
390
|
+
}
|
|
370
391
|
} else {
|
|
371
392
|
writes.push({
|
|
372
393
|
field: field,
|
|
@@ -399,25 +420,28 @@ window.__ModuleLoader__.load({
|
|
|
399
420
|
publish();
|
|
400
421
|
});
|
|
401
422
|
}
|
|
423
|
+
function dispose() {
|
|
424
|
+
if (typeof offScope === 'function') offScope();
|
|
425
|
+
if (typeof offDescribe === 'function') offDescribe();
|
|
426
|
+
}
|
|
402
427
|
|
|
403
|
-
function inject(
|
|
428
|
+
function inject() {
|
|
404
429
|
return {
|
|
405
|
-
// The slots runtime synthesizes a `use<Key>` hook for every entry
|
|
406
|
-
//
|
|
407
|
-
// `
|
|
408
|
-
hooks: {
|
|
409
|
-
t: t,
|
|
430
|
+
// The slots runtime synthesizes a `use<Key>` hook for every entry of
|
|
431
|
+
// this `hooks` object (the shipped plugin pages' `hooks` →
|
|
432
|
+
// `useShellCard` contract).
|
|
433
|
+
hooks: { gitHubRouterCard: store },
|
|
410
434
|
edit: edit,
|
|
411
435
|
resetField: resetField,
|
|
412
436
|
save: save,
|
|
413
437
|
discard: discard,
|
|
414
438
|
};
|
|
415
439
|
}
|
|
416
|
-
return { inject: inject, store: store,
|
|
440
|
+
return { inject: inject, store: store, dispose: dispose };
|
|
417
441
|
}
|
|
418
442
|
|
|
419
|
-
// -------------------------------------------------------------
|
|
420
|
-
var
|
|
443
|
+
// ------------------------------------------------------------- page
|
|
444
|
+
var pageStyle = { padding: '4px 0 16px' };
|
|
421
445
|
var gridStyle = {
|
|
422
446
|
display: 'grid',
|
|
423
447
|
gridTemplateColumns: 'minmax(180px, 1fr) minmax(180px, 1fr)',
|
|
@@ -433,6 +457,7 @@ window.__ModuleLoader__.load({
|
|
|
433
457
|
color: 'inherit',
|
|
434
458
|
};
|
|
435
459
|
var boolRowStyle = { display: 'flex', alignItems: 'center', gap: '6px', fontSize: '13px' };
|
|
460
|
+
var markerStyle = { marginLeft: '6px', opacity: 0.6 };
|
|
436
461
|
var footerStyle = { display: 'flex', alignItems: 'center', gap: '8px', marginTop: '12px' };
|
|
437
462
|
var buttonStyle = {
|
|
438
463
|
padding: '4px 12px',
|
|
@@ -442,17 +467,23 @@ window.__ModuleLoader__.load({
|
|
|
442
467
|
color: 'inherit',
|
|
443
468
|
cursor: 'pointer',
|
|
444
469
|
};
|
|
470
|
+
var clearButtonStyle = Object.assign({}, buttonStyle, { padding: '2px 8px', fontSize: '12px' });
|
|
445
471
|
|
|
446
472
|
function GithubRouterCard(props) {
|
|
447
|
-
var state = props.useSettings(function (s) { return s; });
|
|
448
473
|
var t = props.t;
|
|
449
|
-
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
|
|
474
|
+
// Hooks run before the view branches so one occupied slot keeps a
|
|
475
|
+
// constant hook count whether it renders the one-liner or the page.
|
|
476
|
+
var state = props.useGitHubRouterCard(function (s) { return s; });
|
|
477
|
+
// Leaving the page drops every staged edit, the same posture the shipped
|
|
478
|
+
// settings pages follow.
|
|
479
|
+
React.useEffect(function () {
|
|
480
|
+
return function () { props.discard(); };
|
|
481
|
+
}, []);
|
|
482
|
+
// The Plugins page asks for the row's one-liner before it asks for the page.
|
|
483
|
+
if (props.view === 'summary') return t('description');
|
|
453
484
|
|
|
454
485
|
function renderField(f) {
|
|
455
|
-
var fieldState = state.fields[f.field] || { text: '', overridden: false, invalid: false, configured: false };
|
|
486
|
+
var fieldState = state.fields[f.field] || { text: '', overridden: false, invalid: false, configured: false, clearing: false };
|
|
456
487
|
if (f.kind === 'bool') {
|
|
457
488
|
return React.createElement(
|
|
458
489
|
'label',
|
|
@@ -468,34 +499,46 @@ window.__ModuleLoader__.load({
|
|
|
468
499
|
}
|
|
469
500
|
var marker;
|
|
470
501
|
if (f.kind === 'secret') {
|
|
471
|
-
marker = fieldState.
|
|
472
|
-
? React.createElement('span', { style:
|
|
473
|
-
:
|
|
502
|
+
marker = fieldState.clearing
|
|
503
|
+
? React.createElement('span', { style: markerStyle }, t('willClear'))
|
|
504
|
+
: fieldState.configured
|
|
505
|
+
? React.createElement('span', { style: markerStyle }, t('configured'))
|
|
506
|
+
: null;
|
|
474
507
|
} else {
|
|
475
508
|
marker = fieldState.overridden
|
|
476
|
-
? React.createElement('span', { style:
|
|
509
|
+
? React.createElement('span', { style: markerStyle }, t('overridden'))
|
|
477
510
|
: null;
|
|
478
511
|
}
|
|
512
|
+
var clearControl = f.kind === 'secret' && fieldState.configured && !fieldState.clearing
|
|
513
|
+
? React.createElement('button', {
|
|
514
|
+
type: 'button',
|
|
515
|
+
style: clearButtonStyle,
|
|
516
|
+
disabled: !state.writable || state.saving,
|
|
517
|
+
onClick: function () { props.resetField(f.field); },
|
|
518
|
+
}, t('clearToken'))
|
|
519
|
+
: null;
|
|
479
520
|
return React.createElement(
|
|
480
521
|
'label',
|
|
481
522
|
{ key: f.field, style: rowStyle, title: f.hintKey ? t(f.hintKey) : undefined },
|
|
482
523
|
React.createElement('span', { style: labelStyle }, t(f.labelKey), marker),
|
|
483
524
|
React.createElement('input', {
|
|
484
525
|
type: f.kind === 'number' ? 'number' : f.kind === 'secret' ? 'password' : 'text',
|
|
526
|
+
autoComplete: f.kind === 'secret' ? 'new-password' : undefined,
|
|
485
527
|
value: fieldState.text,
|
|
486
528
|
disabled: !state.writable,
|
|
487
529
|
style: inputStyle,
|
|
488
530
|
onChange: function (event) { props.edit(f.field, event.target.value); },
|
|
489
531
|
}),
|
|
490
532
|
fieldState.invalid ? React.createElement('span', { style: { color: '#c00' } }, t('invalid')) : null,
|
|
533
|
+
clearControl,
|
|
491
534
|
);
|
|
492
535
|
}
|
|
493
536
|
|
|
494
537
|
return React.createElement(
|
|
495
538
|
'section',
|
|
496
|
-
{ style:
|
|
497
|
-
React.createElement('p', { style:
|
|
498
|
-
!state.writable ? React.createElement('
|
|
539
|
+
{ style: pageStyle },
|
|
540
|
+
!state.available ? React.createElement('p', { style: labelStyle }, t('unavailable')) : null,
|
|
541
|
+
!state.writable ? React.createElement('p', { style: { color: '#c00', fontSize: '13px', marginBottom: '8px' } }, t('readOnly')) : null,
|
|
499
542
|
React.createElement('div', { style: gridStyle }, PRIMARY_FIELDS.map(renderField)),
|
|
500
543
|
React.createElement(
|
|
501
544
|
'details',
|
|
@@ -506,22 +549,17 @@ window.__ModuleLoader__.load({
|
|
|
506
549
|
React.createElement('footer', { style: footerStyle },
|
|
507
550
|
React.createElement('button', {
|
|
508
551
|
style: buttonStyle,
|
|
509
|
-
disabled: !state.dirty || state.invalid || state.saving,
|
|
552
|
+
disabled: !state.writable || !state.dirty || state.invalid || state.saving,
|
|
510
553
|
onClick: props.save,
|
|
511
554
|
}, state.saving ? t('saving') : t('save')),
|
|
512
|
-
React.createElement('button', {
|
|
513
|
-
style: buttonStyle,
|
|
514
|
-
disabled: !state.dirty || state.saving,
|
|
515
|
-
onClick: props.discard,
|
|
516
|
-
}, t('discard')),
|
|
517
555
|
state.failed ? React.createElement('span', { style: { color: '#c00', fontSize: '13px' } }, t('failed')) : null,
|
|
518
556
|
),
|
|
519
557
|
);
|
|
520
558
|
}
|
|
521
559
|
|
|
522
|
-
//
|
|
560
|
+
// ------------------------------------------------------------ plugin
|
|
523
561
|
var name = 'dsh-github-router';
|
|
524
|
-
var inject = ['slots', 'locale', '
|
|
562
|
+
var inject = ['slots', 'locale', 'configForms'];
|
|
525
563
|
function apply(ctx) {
|
|
526
564
|
console.info('[dsh-github-router] client bundle loaded');
|
|
527
565
|
try {
|
|
@@ -529,18 +567,29 @@ window.__ModuleLoader__.load({
|
|
|
529
567
|
ctx.effect(function () {
|
|
530
568
|
ctx.locale.register(NS, { zh: zhDict, en: enDict });
|
|
531
569
|
}, 'dsh-github-router: locale dictionaries');
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
var
|
|
535
|
-
ctx.
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
570
|
+
// The shared form for this Host entry, plus the deployment-wide describe
|
|
571
|
+
// face the secret-slot state is read from.
|
|
572
|
+
var scope = ctx.configForms.get(NAMESPACE);
|
|
573
|
+
var describe = typeof ctx.configForms.describe === 'function' ? ctx.configForms.describe() : null;
|
|
574
|
+
var form = new GithubRouterForm(scope, describe);
|
|
575
|
+
ctx.effect(function () {
|
|
576
|
+
return function () { form.dispose(); };
|
|
577
|
+
}, 'dsh-github-router: form subscriptions');
|
|
578
|
+
// The page exists while the Host serves this namespace, so a
|
|
579
|
+
// deployment whose host half is missing shows no trace of it.
|
|
580
|
+
ctx.effect(function () {
|
|
581
|
+
return ctx.configForms.whileServed([NAMESPACE], function () {
|
|
582
|
+
return ctx.slots.inject('plugins.row.config', function () {
|
|
583
|
+
return ctx.slots.register({
|
|
584
|
+
name: 'plugins.row.config',
|
|
585
|
+
key: ROW_CONFIG_KEY,
|
|
586
|
+
locale: NS,
|
|
587
|
+
inject: function () { return form.inject(); },
|
|
588
|
+
}, GithubRouterCard);
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
}, 'dsh-github-router: settings page');
|
|
592
|
+
console.info('[dsh-github-router] settings page registered');
|
|
544
593
|
} catch (error) {
|
|
545
594
|
console.error('[dsh-github-router] client apply failed', error);
|
|
546
595
|
}
|