@ddtcorex/dsh-maestro-config 0.3.0 → 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/lib/client.js +106 -16
- package/lib/types/client/MaestroSettings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/MaestroSettings.tsx +115 -42
- package/lib/types/client/maestro-card.d.ts +0 -10
- package/lib/types/client/maestro-card.d.ts.map +0 -1
- package/src/client/maestro-card.jsx +0 -976
package/lib/client.js
CHANGED
|
@@ -2906,12 +2906,96 @@ function ProjectMappingsEditor({ mappings, onChange, catalog, globalReviewModel
|
|
|
2906
2906
|
label: null
|
|
2907
2907
|
})
|
|
2908
2908
|
)
|
|
2909
|
+
),
|
|
2910
|
+
// Row for per-project trigger overrides — tri-state: inherit the global toggle or force on/off.
|
|
2911
|
+
(0, import_react.createElement)(
|
|
2912
|
+
"div",
|
|
2913
|
+
{ "data-maestro-project-triggers-row": "", style: { display: "flex", gap: 12, flexWrap: "wrap", alignItems: "flex-start" } },
|
|
2914
|
+
(0, import_react.createElement)(
|
|
2915
|
+
"label",
|
|
2916
|
+
{ style: { ...fieldLabelStyle, flex: "1 1 160px", minWidth: 0 } },
|
|
2917
|
+
"Re-review on push",
|
|
2918
|
+
(0, import_react.createElement)(
|
|
2919
|
+
"select",
|
|
2920
|
+
{
|
|
2921
|
+
value: row.rereviewOnPush === void 0 ? "inherit" : row.rereviewOnPush ? "on" : "off",
|
|
2922
|
+
onChange: (e) => updateRow(i, "rereviewOnPush", e.target.value === "inherit" ? void 0 : e.target.value === "on"),
|
|
2923
|
+
"aria-label": `Re-review on push ${i + 1}`,
|
|
2924
|
+
style: {
|
|
2925
|
+
height: 36,
|
|
2926
|
+
width: "100%",
|
|
2927
|
+
padding: "0 14px",
|
|
2928
|
+
border: "none",
|
|
2929
|
+
borderRadius: 18,
|
|
2930
|
+
background: "var(--dsw-alias-bg-module-platform, #F5F6F7)",
|
|
2931
|
+
color: t.labelPrimary,
|
|
2932
|
+
font: "inherit",
|
|
2933
|
+
fontSize: 13
|
|
2934
|
+
}
|
|
2935
|
+
},
|
|
2936
|
+
(0, import_react.createElement)("option", { value: "inherit" }, "Inherit (global)"),
|
|
2937
|
+
(0, import_react.createElement)("option", { value: "on" }, "On"),
|
|
2938
|
+
(0, import_react.createElement)("option", { value: "off" }, "Off")
|
|
2939
|
+
)
|
|
2940
|
+
),
|
|
2941
|
+
(0, import_react.createElement)(
|
|
2942
|
+
"label",
|
|
2943
|
+
{ style: { ...fieldLabelStyle, flex: "1 1 160px", minWidth: 0 } },
|
|
2944
|
+
"Review on assign",
|
|
2945
|
+
(0, import_react.createElement)(
|
|
2946
|
+
"select",
|
|
2947
|
+
{
|
|
2948
|
+
value: row.reviewOnAssign === void 0 ? "inherit" : row.reviewOnAssign ? "on" : "off",
|
|
2949
|
+
onChange: (e) => updateRow(i, "reviewOnAssign", e.target.value === "inherit" ? void 0 : e.target.value === "on"),
|
|
2950
|
+
"aria-label": `Review on assign ${i + 1}`,
|
|
2951
|
+
style: {
|
|
2952
|
+
height: 36,
|
|
2953
|
+
width: "100%",
|
|
2954
|
+
padding: "0 14px",
|
|
2955
|
+
border: "none",
|
|
2956
|
+
borderRadius: 18,
|
|
2957
|
+
background: "var(--dsw-alias-bg-module-platform, #F5F6F7)",
|
|
2958
|
+
color: t.labelPrimary,
|
|
2959
|
+
font: "inherit",
|
|
2960
|
+
fontSize: 13
|
|
2961
|
+
}
|
|
2962
|
+
},
|
|
2963
|
+
(0, import_react.createElement)("option", { value: "inherit" }, "Inherit (global)"),
|
|
2964
|
+
(0, import_react.createElement)("option", { value: "on" }, "On"),
|
|
2965
|
+
(0, import_react.createElement)("option", { value: "off" }, "Off")
|
|
2966
|
+
)
|
|
2967
|
+
)
|
|
2909
2968
|
)
|
|
2910
2969
|
)
|
|
2911
2970
|
)
|
|
2912
2971
|
)
|
|
2913
2972
|
);
|
|
2914
2973
|
}
|
|
2974
|
+
function SecretField({ placeholder, hasSaved, onSave, width }) {
|
|
2975
|
+
const [draft, setDraft] = (0, import_react.useState)("");
|
|
2976
|
+
(0, import_react.useEffect)(() => {
|
|
2977
|
+
if (!hasSaved) setDraft("");
|
|
2978
|
+
}, [hasSaved]);
|
|
2979
|
+
const commit = () => {
|
|
2980
|
+
if (draft !== "") {
|
|
2981
|
+
onSave(draft);
|
|
2982
|
+
setDraft("");
|
|
2983
|
+
}
|
|
2984
|
+
};
|
|
2985
|
+
return (0, import_react.createElement)(FieldInput, {
|
|
2986
|
+
placeholder: hasSaved === true ? "saved \u2014 type new value to replace" : placeholder,
|
|
2987
|
+
type: "password",
|
|
2988
|
+
autoComplete: "off",
|
|
2989
|
+
value: draft,
|
|
2990
|
+
onChange: (e) => setDraft(e.target.value),
|
|
2991
|
+
onBlur: commit,
|
|
2992
|
+
onKeyDown: (e) => {
|
|
2993
|
+
if (e.key === "Enter") e.target.blur();
|
|
2994
|
+
},
|
|
2995
|
+
"aria-label": placeholder,
|
|
2996
|
+
style: { width: width ?? 200 }
|
|
2997
|
+
});
|
|
2998
|
+
}
|
|
2915
2999
|
function LanAccess({ proxyStatus, lanPin }) {
|
|
2916
3000
|
const urls = proxyStatus?.lanUrls ?? [];
|
|
2917
3001
|
const [selected, setSelected] = (0, import_react.useState)(0);
|
|
@@ -3304,10 +3388,6 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
3304
3388
|
call(MAESTRO_ENDPOINTS.getConfig, {}).then((saved) => setConfig((prev) => ({ ...prev, ...saved }))).catch(() => {
|
|
3305
3389
|
});
|
|
3306
3390
|
if (configRpcCall) {
|
|
3307
|
-
configRpcCall("get", { domain: "supervisor" }).then((res) => {
|
|
3308
|
-
if (res?.ok && res.value?.model) setConfig((prev) => ({ ...prev, supervisorModel: res.value.model }));
|
|
3309
|
-
}).catch(() => {
|
|
3310
|
-
});
|
|
3311
3391
|
Promise.all([cfgGet("guard").catch(() => ({})), cfgGet("guardBlacklist").catch(() => ({ patterns: [], placeholders: {} })), cfgGet("supervisor").catch(() => ({})), cfgGet("notifier").catch(() => ({}))]).then(([g, bl, sup, not]) => {
|
|
3312
3392
|
setGuard(g ?? {});
|
|
3313
3393
|
const pats = Array.isArray(bl?.patterns) ? bl.patterns : [];
|
|
@@ -3413,18 +3493,28 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
3413
3493
|
setBusy(false);
|
|
3414
3494
|
}
|
|
3415
3495
|
};
|
|
3496
|
+
const SECRET_SAVE_FLAGS = { gitlabToken: "hasGitlabToken", webhookSecret: "hasWebhookSecret" };
|
|
3416
3497
|
const saveField = async (field, value) => {
|
|
3417
3498
|
setError(null);
|
|
3418
|
-
setConfig((prev) =>
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3499
|
+
setConfig((prev) => {
|
|
3500
|
+
const next = { ...prev, [field]: value };
|
|
3501
|
+
const flag = SECRET_SAVE_FLAGS[field];
|
|
3502
|
+
if (flag) {
|
|
3503
|
+
delete next[field];
|
|
3504
|
+
next[flag] = value !== "";
|
|
3424
3505
|
}
|
|
3425
|
-
|
|
3506
|
+
return next;
|
|
3507
|
+
});
|
|
3426
3508
|
try {
|
|
3427
|
-
await call(MAESTRO_ENDPOINTS.saveConfig, { [field]: value });
|
|
3509
|
+
const saved = await call(MAESTRO_ENDPOINTS.saveConfig, { [field]: value });
|
|
3510
|
+
if (saved && typeof saved === "object") {
|
|
3511
|
+
setConfig((prev) => {
|
|
3512
|
+
const next = { ...prev, ...saved };
|
|
3513
|
+
const flag = SECRET_SAVE_FLAGS[field];
|
|
3514
|
+
if (flag) delete next[field];
|
|
3515
|
+
return next;
|
|
3516
|
+
});
|
|
3517
|
+
}
|
|
3428
3518
|
} catch (err) {
|
|
3429
3519
|
setError(err.message);
|
|
3430
3520
|
}
|
|
@@ -3459,9 +3549,9 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
3459
3549
|
"div",
|
|
3460
3550
|
{ style: { display: "flex", flexDirection: "column" } },
|
|
3461
3551
|
(0, import_react.createElement)(SettingRow, { title: "GitLab base URL", description: "e.g. https://gitlab.example.com", control: (0, import_react.createElement)(FieldInput, { placeholder: "https://gitlab.example.com", value: config.gitlabBaseUrl ?? "", onChange: (e) => saveField("gitlabBaseUrl", e.target.value), style: { width: 260 } }) }),
|
|
3462
|
-
(0, import_react.createElement)(SettingRow, { title: "GitLab token", description: "Personal access token with api scope.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(
|
|
3552
|
+
(0, import_react.createElement)(SettingRow, { title: "GitLab token", description: "Personal access token with api scope.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(SecretField, { placeholder: "GitLab token", hasSaved: config.hasGitlabToken === true, width: 200, onSave: (v) => saveField("gitlabToken", v) }), config.hasGitlabToken ? (0, import_react.createElement)(Button, { variant: "outline", size: "md", onClick: () => saveField("gitlabToken", "") }, "Clear") : null) }),
|
|
3463
3553
|
(0, import_react.createElement)(SettingRow, { title: "Bot username", description: "Username of the bot that posts reviews.", control: (0, import_react.createElement)(FieldInput, { placeholder: "maestro-bot", value: config.botUsername ?? "", onChange: (e) => saveField("botUsername", e.target.value), style: { width: 220 } }) }),
|
|
3464
|
-
(0, import_react.createElement)(SettingRow, { title: "Webhook secret", description: "Secret token for GitLab webhooks.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(
|
|
3554
|
+
(0, import_react.createElement)(SettingRow, { title: "Webhook secret", description: "Secret token for GitLab webhooks.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(SecretField, { placeholder: "Webhook secret", hasSaved: config.hasWebhookSecret === true, width: 200, onSave: (v) => saveField("webhookSecret", v) }), (0, import_react.createElement)(Button, { variant: "outline", size: "md", onClick: () => saveField("webhookSecret", generateWebhookSecret()) }, "Generate")) }),
|
|
3465
3555
|
(0, import_react.createElement)(
|
|
3466
3556
|
"div",
|
|
3467
3557
|
{ style: { padding: "16px 0", display: "flex", flexDirection: "column", gap: 6 } },
|
|
@@ -3473,8 +3563,8 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
3473
3563
|
"div",
|
|
3474
3564
|
{ style: { display: "flex", flexDirection: "column" } },
|
|
3475
3565
|
(0, import_react.createElement)(ToggleRow, { title: "Re-review on push", description: "When new commits are pushed, trigger an automatic re-review.", checked: config.autoRereviewOnPush === true, onChange: (v) => saveField("autoRereviewOnPush", v) }),
|
|
3566
|
+
(0, import_react.createElement)(ToggleRow, { title: "Review on assign", description: "When the bot is assigned as reviewer, trigger an automatic review.", checked: config.autoReviewOnAssign !== false, onChange: (v) => saveField("autoReviewOnAssign", v) }),
|
|
3476
3567
|
(0, import_react.createElement)(SettingRow, { title: "Global review model", description: "Model for automated reviews. Empty = DSH default.", control: (0, import_react.createElement)(ReviewModelSelector, { value: config.reviewModel ?? null, catalog, fallbackValue: catalog?.current ?? null, fallbackLabel: "Use DSH default", onChange: (v) => saveField("reviewModel", v), label: null }) }),
|
|
3477
|
-
(0, import_react.createElement)(SettingRow, { title: "Supervisor model", description: "Model for auto-fixing DSH Web crashes.", control: (0, import_react.createElement)(ReviewModelSelector, { value: config.supervisorModel ?? null, catalog, fallbackValue: catalog?.current ?? null, fallbackLabel: "Use DSH default", onChange: (v) => saveField("supervisorModel", v), label: null }) }),
|
|
3478
3568
|
(0, import_react.createElement)(ProjectMappingsEditor, { mappings: config.projectMappings ?? [], onChange: (mappings) => saveField("projectMappings", mappings), catalog, globalReviewModel: config.reviewModel ?? null })
|
|
3479
3569
|
),
|
|
3480
3570
|
guard: (0, import_react.createElement)(
|
|
@@ -3531,7 +3621,7 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
3531
3621
|
"div",
|
|
3532
3622
|
{ style: { display: "flex", flexDirection: "column" } },
|
|
3533
3623
|
(0, import_react.createElement)("div", { style: { padding: "12px 0", borderBottom: `1px solid ${t.borderL2}` } }, (0, import_react.createElement)("p", { style: captionStyle }, "Telegram bot settings for notifications: startup PIN, review digests, PIN rotation. Leave blank to disable.")),
|
|
3534
|
-
(0, import_react.createElement)(SettingRow, { title: "Bot token", description: "Telegram bot token from @BotFather.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(
|
|
3624
|
+
(0, import_react.createElement)(SettingRow, { title: "Bot token", description: "Telegram bot token from @BotFather.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(SecretField, { placeholder: "123456:ABC-DEF...", hasSaved: (notifierCfg.telegram?.botToken ?? "") !== "", width: 220, onSave: (v) => saveNotifierCfg({ telegram: { botToken: v } }) }), notifierCfg.telegram?.botToken ? (0, import_react.createElement)(Button, { variant: "outline", size: "md", onClick: () => saveNotifierCfg({ telegram: { botToken: "" } }) }, "Clear") : null) }),
|
|
3535
3625
|
(0, import_react.createElement)(SettingRow, { title: "Chat ID", description: "Target chat, e.g. -1001234567890.", control: (0, import_react.createElement)(FieldInput, { value: notifierCfg.telegram?.chatId ?? "", placeholder: "-1001234567890", onChange: (e) => saveNotifierCfg({ telegram: { chatId: e.target.value } }), style: { width: 220 } }) }),
|
|
3536
3626
|
(0, import_react.createElement)(ToggleRow, { title: "Review notifications", description: "Also notify about finished reviews.", checked: notifierCfg.policy?.reviewNotifications === true, onChange: (v) => saveNotifierCfg({ policy: { reviewNotifications: v } }) })
|
|
3537
3627
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MaestroSettings.d.ts","sourceRoot":"","sources":["../../../src/client/MaestroSettings.tsx"],"names":[],"mappings":"AACA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"MaestroSettings.d.ts","sourceRoot":"","sources":["../../../src/client/MaestroSettings.tsx"],"names":[],"mappings":"AACA;;;;;;;;GAQG;AAgwCH,wBAAgB,kBAAkB,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE;IAAE,OAAO,EAAE,GAAG,CAAC;IAAC,aAAa,CAAC,EAAE,GAAG,CAAA;CAAE;;;;;;;;;mBA4ZmE,GAAG;;gBAoBzK"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ddtcorex/dsh-maestro-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Maestro Config — shared settings service for the dsh-maestro-* suite over the single namespaced store (~/.dsh/maestro/settings.json)",
|
|
6
6
|
"type": "module",
|
|
@@ -941,39 +941,102 @@ function ProjectMappingsEditor({ mappings, onChange, catalog, globalReviewModel
|
|
|
941
941
|
}),
|
|
942
942
|
),
|
|
943
943
|
),
|
|
944
|
+
// Row for per-project trigger overrides — tri-state: inherit the global toggle or force on/off.
|
|
945
|
+
h(
|
|
946
|
+
'div',
|
|
947
|
+
{ 'data-maestro-project-triggers-row': '', style: { display: 'flex', gap: 12, flexWrap: 'wrap' as const, alignItems: 'flex-start' } },
|
|
948
|
+
h(
|
|
949
|
+
'label',
|
|
950
|
+
{ style: { ...fieldLabelStyle, flex: '1 1 160px', minWidth: 0 } },
|
|
951
|
+
'Re-review on push',
|
|
952
|
+
h(
|
|
953
|
+
'select',
|
|
954
|
+
{
|
|
955
|
+
value: row.rereviewOnPush === undefined ? 'inherit' : row.rereviewOnPush ? 'on' : 'off',
|
|
956
|
+
onChange: (e: any) => updateRow(i, 'rereviewOnPush', e.target.value === 'inherit' ? undefined : e.target.value === 'on'),
|
|
957
|
+
'aria-label': `Re-review on push ${i + 1}`,
|
|
958
|
+
style: {
|
|
959
|
+
height: 36,
|
|
960
|
+
width: '100%',
|
|
961
|
+
padding: '0 14px',
|
|
962
|
+
border: 'none',
|
|
963
|
+
borderRadius: 18,
|
|
964
|
+
background: 'var(--dsw-alias-bg-module-platform, #F5F6F7)' as string,
|
|
965
|
+
color: t.labelPrimary as string,
|
|
966
|
+
font: 'inherit',
|
|
967
|
+
fontSize: 13,
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
h('option', { value: 'inherit' }, 'Inherit (global)'),
|
|
971
|
+
h('option', { value: 'on' }, 'On'),
|
|
972
|
+
h('option', { value: 'off' }, 'Off'),
|
|
973
|
+
),
|
|
974
|
+
),
|
|
975
|
+
h(
|
|
976
|
+
'label',
|
|
977
|
+
{ style: { ...fieldLabelStyle, flex: '1 1 160px', minWidth: 0 } },
|
|
978
|
+
'Review on assign',
|
|
979
|
+
h(
|
|
980
|
+
'select',
|
|
981
|
+
{
|
|
982
|
+
value: row.reviewOnAssign === undefined ? 'inherit' : row.reviewOnAssign ? 'on' : 'off',
|
|
983
|
+
onChange: (e: any) => updateRow(i, 'reviewOnAssign', e.target.value === 'inherit' ? undefined : e.target.value === 'on'),
|
|
984
|
+
'aria-label': `Review on assign ${i + 1}`,
|
|
985
|
+
style: {
|
|
986
|
+
height: 36,
|
|
987
|
+
width: '100%',
|
|
988
|
+
padding: '0 14px',
|
|
989
|
+
border: 'none',
|
|
990
|
+
borderRadius: 18,
|
|
991
|
+
background: 'var(--dsw-alias-bg-module-platform, #F5F6F7)' as string,
|
|
992
|
+
color: t.labelPrimary as string,
|
|
993
|
+
font: 'inherit',
|
|
994
|
+
fontSize: 13,
|
|
995
|
+
},
|
|
996
|
+
},
|
|
997
|
+
h('option', { value: 'inherit' }, 'Inherit (global)'),
|
|
998
|
+
h('option', { value: 'on' }, 'On'),
|
|
999
|
+
h('option', { value: 'off' }, 'Off'),
|
|
1000
|
+
),
|
|
1001
|
+
),
|
|
1002
|
+
),
|
|
944
1003
|
),
|
|
945
1004
|
),
|
|
946
1005
|
),
|
|
947
1006
|
)
|
|
948
1007
|
}
|
|
949
1008
|
|
|
950
|
-
|
|
1009
|
+
// SecretField — control-sized secret input for SettingRow controls (no label).
|
|
1010
|
+
// The server never echoes secrets back (getConfig masks them to has* flags),
|
|
1011
|
+
// so the field stays EMPTY and commits a typed draft on blur / Enter; an
|
|
1012
|
+
// untouched field keeps the stored secret and Clear writes '' to erase it.
|
|
1013
|
+
// Never render mask bullets ('••••') as the input value: a controlled input
|
|
1014
|
+
// locked to a constant string swallows keystrokes and saves bullet-contaminated
|
|
1015
|
+
// text on the first change (the GitLab-token-not-editable regression).
|
|
1016
|
+
function SecretField({ placeholder, hasSaved, onSave, width }: { placeholder: string; hasSaved?: boolean; onSave: (v: string) => void; width?: number }) {
|
|
951
1017
|
const [draft, setDraft] = useState('')
|
|
952
|
-
|
|
953
|
-
setDraft('')
|
|
954
|
-
|
|
1018
|
+
useEffect(() => {
|
|
1019
|
+
if (!hasSaved) setDraft('')
|
|
1020
|
+
}, [hasSaved])
|
|
1021
|
+
const commit = () => {
|
|
1022
|
+
if (draft !== '') {
|
|
1023
|
+
onSave(draft)
|
|
1024
|
+
setDraft('')
|
|
1025
|
+
}
|
|
955
1026
|
}
|
|
956
|
-
return h(
|
|
957
|
-
'
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
onBlur: () => {
|
|
970
|
-
if (draft !== '') onSave(draft)
|
|
971
|
-
},
|
|
972
|
-
style: { flex: 1 } as any,
|
|
973
|
-
}),
|
|
974
|
-
hasSaved === true ? h(Button as any, { variant: 'outline', size: 'sm', onClick: clear }, 'Clear') : null,
|
|
975
|
-
),
|
|
976
|
-
)
|
|
1027
|
+
return h(FieldInput as any, {
|
|
1028
|
+
placeholder: hasSaved === true ? 'saved — type new value to replace' : placeholder,
|
|
1029
|
+
type: 'password',
|
|
1030
|
+
autoComplete: 'off',
|
|
1031
|
+
value: draft,
|
|
1032
|
+
onChange: (e: any) => setDraft(e.target.value),
|
|
1033
|
+
onBlur: commit,
|
|
1034
|
+
onKeyDown: (e: any) => {
|
|
1035
|
+
if (e.key === 'Enter') (e.target as HTMLInputElement).blur()
|
|
1036
|
+
},
|
|
1037
|
+
'aria-label': placeholder,
|
|
1038
|
+
style: { width: width ?? 200 } as any,
|
|
1039
|
+
})
|
|
977
1040
|
}
|
|
978
1041
|
|
|
979
1042
|
function ToggleField({ label, caption, checked, onChange }: { label: string; caption?: string; checked?: boolean; onChange: (v: boolean) => void }) {
|
|
@@ -1400,11 +1463,6 @@ export function MaestroSettingsTab({ rpcCall, configRpcCall }: { rpcCall: any; c
|
|
|
1400
1463
|
.then((saved) => setConfig((prev: any) => ({ ...prev, ...saved })))
|
|
1401
1464
|
.catch(() => {})
|
|
1402
1465
|
if (configRpcCall) {
|
|
1403
|
-
configRpcCall('get', { domain: 'supervisor' })
|
|
1404
|
-
.then((res: any) => {
|
|
1405
|
-
if (res?.ok && res.value?.model) setConfig((prev: any) => ({ ...prev, supervisorModel: res.value.model }))
|
|
1406
|
-
})
|
|
1407
|
-
.catch(() => {})
|
|
1408
1466
|
Promise.all([cfgGet('guard').catch(() => ({})), cfgGet('guardBlacklist').catch(() => ({ patterns: [], placeholders: {} })), cfgGet('supervisor').catch(() => ({})), cfgGet('notifier').catch(() => ({}))])
|
|
1409
1467
|
.then(([g, bl, sup, not]) => {
|
|
1410
1468
|
setGuard(g ?? {})
|
|
@@ -1513,17 +1571,32 @@ export function MaestroSettingsTab({ rpcCall, configRpcCall }: { rpcCall: any; c
|
|
|
1513
1571
|
setBusy(false)
|
|
1514
1572
|
}
|
|
1515
1573
|
}
|
|
1574
|
+
const SECRET_SAVE_FLAGS: Record<string, string> = { gitlabToken: 'hasGitlabToken', webhookSecret: 'hasWebhookSecret' }
|
|
1516
1575
|
const saveField = async (field: string, value: unknown) => {
|
|
1517
1576
|
setError(null)
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1577
|
+
// Optimistic update — for secrets only flip the has* presence flag, never
|
|
1578
|
+
// stash the raw secret in state (the server never echoes it back).
|
|
1579
|
+
setConfig((prev: any) => {
|
|
1580
|
+
const next = { ...prev, [field]: value }
|
|
1581
|
+
const flag = SECRET_SAVE_FLAGS[field]
|
|
1582
|
+
if (flag) {
|
|
1583
|
+
delete next[field]
|
|
1584
|
+
next[flag] = value !== ''
|
|
1585
|
+
}
|
|
1586
|
+
return next
|
|
1587
|
+
})
|
|
1525
1588
|
try {
|
|
1526
|
-
|
|
1589
|
+
// saveConfig returns the masked config — merge it so has* flags (and a
|
|
1590
|
+
// cleared secret) sync without waiting for a reload.
|
|
1591
|
+
const saved = await call(MAESTRO_ENDPOINTS.saveConfig, { [field]: value })
|
|
1592
|
+
if (saved && typeof saved === 'object') {
|
|
1593
|
+
setConfig((prev: any) => {
|
|
1594
|
+
const next = { ...prev, ...(saved as object) }
|
|
1595
|
+
const flag = SECRET_SAVE_FLAGS[field]
|
|
1596
|
+
if (flag) delete next[field]
|
|
1597
|
+
return next
|
|
1598
|
+
})
|
|
1599
|
+
}
|
|
1527
1600
|
} catch (err: any) {
|
|
1528
1601
|
setError(err.message)
|
|
1529
1602
|
}
|
|
@@ -1561,9 +1634,9 @@ export function MaestroSettingsTab({ rpcCall, configRpcCall }: { rpcCall: any; c
|
|
|
1561
1634
|
'div',
|
|
1562
1635
|
{ style: { display: 'flex', flexDirection: 'column' } },
|
|
1563
1636
|
h(SettingRow as any, { title: 'GitLab base URL', description: 'e.g. https://gitlab.example.com', control: h(FieldInput as any, { placeholder: 'https://gitlab.example.com', value: config.gitlabBaseUrl ?? '', onChange: (e: any) => saveField('gitlabBaseUrl', e.target.value), style: { width: 260 } as any }) }),
|
|
1564
|
-
h(SettingRow as any, { title: 'GitLab token', description: 'Personal access token with api scope.', control: h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } }, h(
|
|
1637
|
+
h(SettingRow as any, { title: 'GitLab token', description: 'Personal access token with api scope.', control: h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } }, h(SecretField as any, { placeholder: 'GitLab token', hasSaved: config.hasGitlabToken === true, width: 200, onSave: (v: string) => saveField('gitlabToken', v) }), config.hasGitlabToken ? h(Button as any, { variant: 'outline', size: 'md', onClick: () => saveField('gitlabToken', '') }, 'Clear') : null) }),
|
|
1565
1638
|
h(SettingRow as any, { title: 'Bot username', description: 'Username of the bot that posts reviews.', control: h(FieldInput as any, { placeholder: 'maestro-bot', value: config.botUsername ?? '', onChange: (e: any) => saveField('botUsername', e.target.value), style: { width: 220 } as any }) }),
|
|
1566
|
-
h(SettingRow as any, { title: 'Webhook secret', description: 'Secret token for GitLab webhooks.', control: h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } }, h(
|
|
1639
|
+
h(SettingRow as any, { title: 'Webhook secret', description: 'Secret token for GitLab webhooks.', control: h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } }, h(SecretField as any, { placeholder: 'Webhook secret', hasSaved: config.hasWebhookSecret === true, width: 200, onSave: (v: string) => saveField('webhookSecret', v) }), h(Button as any, { variant: 'outline', size: 'md', onClick: () => saveField('webhookSecret', generateWebhookSecret()) }, 'Generate')) }),
|
|
1567
1640
|
h('div', { style: { padding: '16px 0', display: 'flex', flexDirection: 'column', gap: 6 } },
|
|
1568
1641
|
h('p', { style: captionStyle }, 'In GitLab: Settings → Webhooks, Secret token = this value, enable Merge request events. Webhook URL:'),
|
|
1569
1642
|
h('div', { style: { fontFamily: 'ui-monospace, monospace', fontSize: 12, color: t.labelPrimary as string, wordBreak: 'break-all', padding: '10px 12px', borderRadius: 8, background: t.bgLayer3 as string, border: `1px solid ${t.borderL2}`, overflowWrap:'anywhere' as any } }, gitlabWebhookUrl(config.tunnelHostname)),
|
|
@@ -1573,8 +1646,8 @@ export function MaestroSettingsTab({ rpcCall, configRpcCall }: { rpcCall: any; c
|
|
|
1573
1646
|
'div',
|
|
1574
1647
|
{ style: { display: 'flex', flexDirection: 'column' } },
|
|
1575
1648
|
h(ToggleRow as any, { title: 'Re-review on push', description: 'When new commits are pushed, trigger an automatic re-review.', checked: config.autoRereviewOnPush === true, onChange: (v: boolean) => saveField('autoRereviewOnPush', v) }),
|
|
1649
|
+
h(ToggleRow as any, { title: 'Review on assign', description: 'When the bot is assigned as reviewer, trigger an automatic review.', checked: config.autoReviewOnAssign !== false, onChange: (v: boolean) => saveField('autoReviewOnAssign', v) }),
|
|
1576
1650
|
h(SettingRow as any, { title: 'Global review model', description: 'Model for automated reviews. Empty = DSH default.', control: h(ReviewModelSelector as any, { value: config.reviewModel ?? null, catalog, fallbackValue: catalog?.current ?? null, fallbackLabel: 'Use DSH default', onChange: (v: any) => saveField('reviewModel', v), label: null }) }),
|
|
1577
|
-
h(SettingRow as any, { title: 'Supervisor model', description: 'Model for auto-fixing DSH Web crashes.', control: h(ReviewModelSelector as any, { value: config.supervisorModel ?? null, catalog, fallbackValue: catalog?.current ?? null, fallbackLabel: 'Use DSH default', onChange: (v: any) => saveField('supervisorModel', v), label: null }) }),
|
|
1578
1651
|
h(ProjectMappingsEditor as any, { mappings: config.projectMappings ?? [], onChange: (mappings: any) => saveField('projectMappings', mappings), catalog, globalReviewModel: config.reviewModel ?? null }),
|
|
1579
1652
|
),
|
|
1580
1653
|
guard: h(
|
|
@@ -1618,7 +1691,7 @@ export function MaestroSettingsTab({ rpcCall, configRpcCall }: { rpcCall: any; c
|
|
|
1618
1691
|
'div',
|
|
1619
1692
|
{ style: { display: 'flex', flexDirection: 'column' } },
|
|
1620
1693
|
h('div', { style: { padding: '12px 0', borderBottom: `1px solid ${t.borderL2}` } }, h('p', { style: captionStyle }, 'Telegram bot settings for notifications: startup PIN, review digests, PIN rotation. Leave blank to disable.')),
|
|
1621
|
-
h(SettingRow as any, { title: 'Bot token', description: 'Telegram bot token from @BotFather.', control: h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } }, h(
|
|
1694
|
+
h(SettingRow as any, { title: 'Bot token', description: 'Telegram bot token from @BotFather.', control: h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } }, h(SecretField as any, { placeholder: '123456:ABC-DEF...', hasSaved: (notifierCfg.telegram?.botToken ?? '') !== '', width: 220, onSave: (v: string) => saveNotifierCfg({ telegram: { botToken: v } }) }), notifierCfg.telegram?.botToken ? h(Button as any, { variant: 'outline', size: 'md', onClick: () => saveNotifierCfg({ telegram: { botToken: '' } }) }, 'Clear') : null) }),
|
|
1622
1695
|
h(SettingRow as any, { title: 'Chat ID', description: 'Target chat, e.g. -1001234567890.', control: h(FieldInput as any, { value: notifierCfg.telegram?.chatId ?? '', placeholder: '-1001234567890', onChange: (e: any) => saveNotifierCfg({ telegram: { chatId: e.target.value } }), style: { width: 220 } as any }) }),
|
|
1623
1696
|
h(ToggleRow as any, { title: 'Review notifications', description: 'Also notify about finished reviews.', checked: notifierCfg.policy?.reviewNotifications === true, onChange: (v: boolean) => saveNotifierCfg({ policy: { reviewNotifications: v } }) }),
|
|
1624
1697
|
),
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export function MaestroSettingsTab({ rpcCall, configRpcCall }: {
|
|
2
|
-
rpcCall: any;
|
|
3
|
-
configRpcCall: any;
|
|
4
|
-
}): import("react").DetailedReactHTMLElement<{
|
|
5
|
-
'data-maestro-settings-card': string;
|
|
6
|
-
style: {
|
|
7
|
-
maxWidth: number;
|
|
8
|
-
};
|
|
9
|
-
}, HTMLElement>;
|
|
10
|
-
//# sourceMappingURL=maestro-card.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"maestro-card.d.ts","sourceRoot":"","sources":["../../../src/client/maestro-card.jsx"],"names":[],"mappings":"AAweA;;;;;;;;gBA0dC"}
|