@elinpf/dsh-ops-access-ui 0.1.7 → 0.2.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/lib/client.js +202 -18
- package/lib/types.d.ts +21 -0
- package/package.json +2 -5
package/lib/client.js
CHANGED
|
@@ -29,7 +29,6 @@ __export(client_exports, {
|
|
|
29
29
|
apiFetchResult: () => apiFetchResult,
|
|
30
30
|
apply: () => apply,
|
|
31
31
|
defaultTtlChoice: () => defaultTtlChoice,
|
|
32
|
-
delegatedAccessCount: () => delegatedAccessCount,
|
|
33
32
|
deleteEntry: () => deleteEntry,
|
|
34
33
|
fetchAdminList: () => fetchAdminList,
|
|
35
34
|
fetchKinds: () => fetchKinds,
|
|
@@ -40,7 +39,7 @@ __export(client_exports, {
|
|
|
40
39
|
inject: () => inject,
|
|
41
40
|
liveGrantFor: () => liveGrantFor,
|
|
42
41
|
name: () => name,
|
|
43
|
-
|
|
42
|
+
pendingRequestCount: () => pendingRequestCount,
|
|
44
43
|
postPanelDecide: () => postPanelDecide,
|
|
45
44
|
postPanelDeny: () => postPanelDeny,
|
|
46
45
|
postPanelExtend: () => postPanelExtend,
|
|
@@ -109,6 +108,32 @@ async function fetchEntry(kind, name2, tier) {
|
|
|
109
108
|
return null;
|
|
110
109
|
}
|
|
111
110
|
}
|
|
111
|
+
async function fetchAdminRequests(signal) {
|
|
112
|
+
try {
|
|
113
|
+
const res = await fetch("/ops-access/admin/requests", { signal });
|
|
114
|
+
if (!res.ok) return null;
|
|
115
|
+
const data = await res.json();
|
|
116
|
+
return Array.isArray(data) ? data : null;
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function fetchRequestDetail(id) {
|
|
122
|
+
try {
|
|
123
|
+
const res = await fetch("/ops-access/admin/requests/detail?id=" + encodeURIComponent(id));
|
|
124
|
+
if (!res.ok) return null;
|
|
125
|
+
return await res.json();
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function decideRegistrationRequest(id, approved) {
|
|
131
|
+
return apiFetchResult("/ops-access/admin/requests/decide", {
|
|
132
|
+
method: "POST",
|
|
133
|
+
headers: { "content-type": "application/json" },
|
|
134
|
+
body: JSON.stringify({ id, approved })
|
|
135
|
+
}, "decideRegistrationRequest");
|
|
136
|
+
}
|
|
112
137
|
async function fetchPanelGrants(sessionId, signal) {
|
|
113
138
|
try {
|
|
114
139
|
const res = await fetch("/ops-access/grants?session=" + encodeURIComponent(sessionId), { signal });
|
|
@@ -177,24 +202,19 @@ function postPanelDecide(id, approved, ttlMinutes) {
|
|
|
177
202
|
body: JSON.stringify(approved ? { id, approved, ttlMinutes } : { id, approved })
|
|
178
203
|
}, "postPanelDecide");
|
|
179
204
|
}
|
|
180
|
-
function
|
|
181
|
-
if (session === null || session === void 0 || !Array.isArray(session.runningCalls)) return 0;
|
|
182
|
-
return session.runningCalls.filter((c) => c.name === "request_access").length;
|
|
183
|
-
}
|
|
184
|
-
function delegatedAccessCount(requests, sessionId) {
|
|
205
|
+
function pendingRequestCount(requests) {
|
|
185
206
|
if (!Array.isArray(requests)) return 0;
|
|
186
|
-
return requests.
|
|
207
|
+
return requests.length;
|
|
187
208
|
}
|
|
188
209
|
function AccessBadge(props) {
|
|
189
|
-
const own = pendingAccessCount(props.session);
|
|
190
|
-
const [delegated, setDelegated] = (0, import_react.useState)(0);
|
|
191
210
|
const sid = props.sessionId;
|
|
211
|
+
const [count, setCount] = (0, import_react.useState)(0);
|
|
192
212
|
(0, import_react.useEffect)(() => {
|
|
193
213
|
if (sid === void 0) return;
|
|
194
214
|
let alive = true;
|
|
195
215
|
const poll = async () => {
|
|
196
216
|
const data = await fetchPanelRequests(sid);
|
|
197
|
-
if (alive && data !== null)
|
|
217
|
+
if (alive && data !== null) setCount(pendingRequestCount(data.requests));
|
|
198
218
|
};
|
|
199
219
|
void poll();
|
|
200
220
|
const timer = setInterval(() => {
|
|
@@ -205,13 +225,12 @@ function AccessBadge(props) {
|
|
|
205
225
|
clearInterval(timer);
|
|
206
226
|
};
|
|
207
227
|
}, [sid]);
|
|
208
|
-
const count = own + delegated;
|
|
209
228
|
if (count === 0 || sid === void 0) return null;
|
|
210
229
|
return (0, import_react.createElement)(
|
|
211
230
|
"button",
|
|
212
231
|
{
|
|
213
232
|
className: "ops-access-badge",
|
|
214
|
-
title:
|
|
233
|
+
title: "\u6709\u5F85\u5BA1\u6279\u7684\u63D0\u6743\u7533\u8BF7 \u2014 \u70B9\u51FB\u6253\u5F00\u6388\u6743\u9762\u677F",
|
|
215
234
|
onClick: () => props.openPanel(sid)
|
|
216
235
|
},
|
|
217
236
|
(0, import_react.createElement)("span", { className: "ops-access-badge-dot" }),
|
|
@@ -701,7 +720,7 @@ function TierBadge({ status }) {
|
|
|
701
720
|
}, (0, import_react.createElement)(CrossIcon));
|
|
702
721
|
}
|
|
703
722
|
function AdminListView(props) {
|
|
704
|
-
const { entries, loading, onRefresh, onAdd, onEdit, onDelete } = props;
|
|
723
|
+
const { entries, loading, requests, onReview, onRefresh, onAdd, onEdit, onDelete } = props;
|
|
705
724
|
const kindOrder = [];
|
|
706
725
|
const byKind = {};
|
|
707
726
|
for (const e of entries) {
|
|
@@ -734,6 +753,46 @@ function AdminListView(props) {
|
|
|
734
753
|
}, (0, import_react.createElement)(PlusIcon), "\u65B0\u589E")
|
|
735
754
|
)
|
|
736
755
|
),
|
|
756
|
+
requests && requests.length > 0 && (0, import_react.createElement)(
|
|
757
|
+
"div",
|
|
758
|
+
{ className: "ops-access-admin-card" },
|
|
759
|
+
(0, import_react.createElement)(
|
|
760
|
+
"div",
|
|
761
|
+
{ className: "ops-access-admin-card-header" },
|
|
762
|
+
(0, import_react.createElement)("span", { className: "ops-access-admin-card-title" }, "\u5F85\u5BA1\u6279\u6CE8\u518C\u7533\u8BF7"),
|
|
763
|
+
(0, import_react.createElement)("span", { className: "ops-access-admin-card-count" }, String(requests.length) + " \u6761")
|
|
764
|
+
),
|
|
765
|
+
...requests.map(
|
|
766
|
+
(request) => (0, import_react.createElement)(
|
|
767
|
+
"div",
|
|
768
|
+
{ key: request.id, className: "ops-access-admin-card-row" },
|
|
769
|
+
(0, import_react.createElement)(
|
|
770
|
+
"div",
|
|
771
|
+
{ className: "ops-access-admin-card-row-main" },
|
|
772
|
+
(0, import_react.createElement)(
|
|
773
|
+
"div",
|
|
774
|
+
{ className: "ops-access-admin-card-row-name" },
|
|
775
|
+
request.kind + "/" + request.name,
|
|
776
|
+
(0, import_react.createElement)("span", { className: "ops-access-admin-card-row-id" }, request.tier)
|
|
777
|
+
),
|
|
778
|
+
(0, import_react.createElement)(
|
|
779
|
+
"div",
|
|
780
|
+
{ className: "ops-access-admin-card-row-desc" },
|
|
781
|
+
(request.reason ? request.reason + " \xB7 " : "") + new Date(request.createdAt).toLocaleString()
|
|
782
|
+
)
|
|
783
|
+
),
|
|
784
|
+
(0, import_react.createElement)(
|
|
785
|
+
"div",
|
|
786
|
+
{ className: "ops-access-admin-row-actions" },
|
|
787
|
+
(0, import_react.createElement)("button", {
|
|
788
|
+
type: "button",
|
|
789
|
+
className: "ops-access-admin-btn ops-access-admin-btn-primary",
|
|
790
|
+
onClick: () => onReview?.(request)
|
|
791
|
+
}, "\u5BA1\u67E5")
|
|
792
|
+
)
|
|
793
|
+
)
|
|
794
|
+
)
|
|
795
|
+
),
|
|
737
796
|
entries.length === 0 ? (0, import_react.createElement)(
|
|
738
797
|
"div",
|
|
739
798
|
{ className: "ops-access-admin-empty" },
|
|
@@ -1145,6 +1204,88 @@ function AdminFormView(props) {
|
|
|
1145
1204
|
)
|
|
1146
1205
|
);
|
|
1147
1206
|
}
|
|
1207
|
+
function RequestReviewView(props) {
|
|
1208
|
+
const { detail, deciding, error, onDecide, onCancel } = props;
|
|
1209
|
+
const [confirming, setConfirming] = (0, import_react.useState)(null);
|
|
1210
|
+
const fieldEntries = Object.entries(detail.fields);
|
|
1211
|
+
return (0, import_react.createElement)(
|
|
1212
|
+
"div",
|
|
1213
|
+
{ className: "ops-access-admin-root" },
|
|
1214
|
+
(0, import_react.createElement)(
|
|
1215
|
+
"div",
|
|
1216
|
+
{ className: "ops-access-admin-header" },
|
|
1217
|
+
(0, import_react.createElement)(
|
|
1218
|
+
"span",
|
|
1219
|
+
{ className: "ops-access-admin-title" },
|
|
1220
|
+
"\u5BA1\u6279\u6CE8\u518C\u7533\u8BF7: " + detail.kind + "/" + detail.name + " (" + detail.tier + ")"
|
|
1221
|
+
)
|
|
1222
|
+
),
|
|
1223
|
+
(0, import_react.createElement)(
|
|
1224
|
+
"div",
|
|
1225
|
+
{ className: "ops-access-admin-card-row-desc" },
|
|
1226
|
+
(detail.reason ? "\u7406\u7531: " + detail.reason + " \xB7 " : "") + "\u63D0\u4EA4\u4E8E " + new Date(detail.createdAt).toLocaleString()
|
|
1227
|
+
),
|
|
1228
|
+
...fieldEntries.map(([fieldName, value]) => {
|
|
1229
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
1230
|
+
return (0, import_react.createElement)(
|
|
1231
|
+
"div",
|
|
1232
|
+
{ key: fieldName, style: { margin: "8px 0" } },
|
|
1233
|
+
(0, import_react.createElement)("div", { className: "ops-access-admin-card-row-name" }, fieldName),
|
|
1234
|
+
text.includes("\n") || text.length > 80 ? (0, import_react.createElement)("textarea", {
|
|
1235
|
+
readOnly: true,
|
|
1236
|
+
rows: Math.min(text.split("\n").length + 1, 12),
|
|
1237
|
+
style: { width: "100%", fontFamily: "monospace" },
|
|
1238
|
+
value: text
|
|
1239
|
+
}) : (0, import_react.createElement)("div", { style: { fontFamily: "monospace" } }, text)
|
|
1240
|
+
);
|
|
1241
|
+
}),
|
|
1242
|
+
fieldEntries.length === 0 && (0, import_react.createElement)("div", { className: "ops-access-admin-empty" }, "(\u65E0\u5B57\u6BB5)"),
|
|
1243
|
+
error && (0, import_react.createElement)("div", { className: "ops-access-admin-card-row-desc" }, error),
|
|
1244
|
+
confirming ? (0, import_react.createElement)(
|
|
1245
|
+
"div",
|
|
1246
|
+
{ className: "ops-access-admin-confirm" },
|
|
1247
|
+
(0, import_react.createElement)(
|
|
1248
|
+
"div",
|
|
1249
|
+
null,
|
|
1250
|
+
confirming === "approve" ? "\u786E\u8BA4\u6279\u51C6\uFF1F\u8BE5 " + detail.tier + " tier \u5C06\u7ACB\u5373\u5199\u5165\u51ED\u8BC1\u5E93\u5E76\u751F\u6548\u3002" : "\u786E\u8BA4\u62D2\u7EDD\uFF1F\u8BE5\u7533\u8BF7\u5C06\u88AB\u6807\u8BB0\u4E3A\u5DF2\u62D2\u7EDD,\u4E0D\u4F1A\u5199\u5165\u4EFB\u4F55\u5185\u5BB9\u3002"
|
|
1251
|
+
),
|
|
1252
|
+
(0, import_react.createElement)(
|
|
1253
|
+
"div",
|
|
1254
|
+
{ className: "ops-access-admin-actions" },
|
|
1255
|
+
(0, import_react.createElement)("button", {
|
|
1256
|
+
type: "button",
|
|
1257
|
+
className: "ops-access-admin-btn " + (confirming === "approve" ? "ops-access-admin-btn-primary" : "ops-access-admin-btn-danger"),
|
|
1258
|
+
disabled: deciding,
|
|
1259
|
+
onClick: () => onDecide(confirming === "approve")
|
|
1260
|
+
}, deciding ? "\u63D0\u4EA4\u4E2D\u2026" : confirming === "approve" ? "\u786E\u8BA4\u6279\u51C6" : "\u786E\u8BA4\u62D2\u7EDD"),
|
|
1261
|
+
(0, import_react.createElement)("button", {
|
|
1262
|
+
type: "button",
|
|
1263
|
+
className: "ops-access-admin-btn",
|
|
1264
|
+
disabled: deciding,
|
|
1265
|
+
onClick: () => setConfirming(null)
|
|
1266
|
+
}, "\u53D6\u6D88")
|
|
1267
|
+
)
|
|
1268
|
+
) : (0, import_react.createElement)(
|
|
1269
|
+
"div",
|
|
1270
|
+
{ className: "ops-access-admin-actions", style: { marginTop: "12px" } },
|
|
1271
|
+
(0, import_react.createElement)("button", {
|
|
1272
|
+
type: "button",
|
|
1273
|
+
className: "ops-access-admin-btn ops-access-admin-btn-primary",
|
|
1274
|
+
onClick: () => setConfirming("approve")
|
|
1275
|
+
}, "\u6279\u51C6\u5199\u5165"),
|
|
1276
|
+
(0, import_react.createElement)("button", {
|
|
1277
|
+
type: "button",
|
|
1278
|
+
className: "ops-access-admin-btn ops-access-admin-btn-danger",
|
|
1279
|
+
onClick: () => setConfirming("reject")
|
|
1280
|
+
}, "\u62D2\u7EDD"),
|
|
1281
|
+
(0, import_react.createElement)("button", {
|
|
1282
|
+
type: "button",
|
|
1283
|
+
className: "ops-access-admin-btn",
|
|
1284
|
+
onClick: onCancel
|
|
1285
|
+
}, "\u8FD4\u56DE")
|
|
1286
|
+
)
|
|
1287
|
+
);
|
|
1288
|
+
}
|
|
1148
1289
|
function AdminSection(_props) {
|
|
1149
1290
|
const [view, setView] = (0, import_react.useState)("list");
|
|
1150
1291
|
const [entries, setEntries] = (0, import_react.useState)([]);
|
|
@@ -1153,10 +1294,15 @@ function AdminSection(_props) {
|
|
|
1153
1294
|
const [editEntry, setEditEntry] = (0, import_react.useState)(null);
|
|
1154
1295
|
const [deleteTarget, setDeleteTarget] = (0, import_react.useState)(null);
|
|
1155
1296
|
const [deleting, setDeleting] = (0, import_react.useState)(false);
|
|
1297
|
+
const [requests, setRequests] = (0, import_react.useState)(null);
|
|
1298
|
+
const [reviewDetail, setReviewDetail] = (0, import_react.useState)(null);
|
|
1299
|
+
const [reviewError, setReviewError] = (0, import_react.useState)(null);
|
|
1300
|
+
const [deciding, setDeciding] = (0, import_react.useState)(false);
|
|
1156
1301
|
const refreshList = (0, import_react.useCallback)(async () => {
|
|
1157
1302
|
setListLoading(true);
|
|
1158
|
-
const
|
|
1159
|
-
setEntries(
|
|
1303
|
+
const [list, pending] = await Promise.all([fetchAdminList(), fetchAdminRequests()]);
|
|
1304
|
+
setEntries(list);
|
|
1305
|
+
setRequests(pending);
|
|
1160
1306
|
setListLoading(false);
|
|
1161
1307
|
}, []);
|
|
1162
1308
|
(0, import_react.useEffect)(() => {
|
|
@@ -1190,6 +1336,29 @@ function AdminSection(_props) {
|
|
|
1190
1336
|
setDeleteTarget(null);
|
|
1191
1337
|
refreshList();
|
|
1192
1338
|
}, [deleteTarget, refreshList]);
|
|
1339
|
+
const handleReview = (0, import_react.useCallback)(async (request) => {
|
|
1340
|
+
setReviewError(null);
|
|
1341
|
+
const detail = await fetchRequestDetail(request.id);
|
|
1342
|
+
if (!detail) {
|
|
1343
|
+
setReviewError("\u52A0\u8F7D\u7533\u8BF7\u8BE6\u60C5\u5931\u8D25 \u2014 \u7533\u8BF7\u53EF\u80FD\u5DF2\u88AB\u5904\u7406");
|
|
1344
|
+
refreshList();
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
setReviewDetail(detail);
|
|
1348
|
+
}, [refreshList]);
|
|
1349
|
+
const handleDecide = (0, import_react.useCallback)(async (approved) => {
|
|
1350
|
+
if (!reviewDetail) return;
|
|
1351
|
+
setDeciding(true);
|
|
1352
|
+
setReviewError(null);
|
|
1353
|
+
const result = await decideRegistrationRequest(reviewDetail.id, approved);
|
|
1354
|
+
setDeciding(false);
|
|
1355
|
+
if (!result.ok) {
|
|
1356
|
+
setReviewError(result.error ?? "\u64CD\u4F5C\u5931\u8D25");
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
setReviewDetail(null);
|
|
1360
|
+
refreshList();
|
|
1361
|
+
}, [reviewDetail, refreshList]);
|
|
1193
1362
|
if (deleteTarget) {
|
|
1194
1363
|
return (0, import_react.createElement)(
|
|
1195
1364
|
"div",
|
|
@@ -1217,6 +1386,15 @@ function AdminSection(_props) {
|
|
|
1217
1386
|
)
|
|
1218
1387
|
);
|
|
1219
1388
|
}
|
|
1389
|
+
if (reviewDetail) {
|
|
1390
|
+
return (0, import_react.createElement)(RequestReviewView, {
|
|
1391
|
+
detail: reviewDetail,
|
|
1392
|
+
deciding,
|
|
1393
|
+
error: reviewError,
|
|
1394
|
+
onDecide: handleDecide,
|
|
1395
|
+
onCancel: () => setReviewDetail(null)
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1220
1398
|
if (view === "form") {
|
|
1221
1399
|
return (0, import_react.createElement)(AdminFormView, {
|
|
1222
1400
|
error: formError,
|
|
@@ -1229,6 +1407,8 @@ function AdminSection(_props) {
|
|
|
1229
1407
|
return (0, import_react.createElement)(AdminListView, {
|
|
1230
1408
|
entries,
|
|
1231
1409
|
loading: listLoading,
|
|
1410
|
+
requests,
|
|
1411
|
+
onReview: handleReview,
|
|
1232
1412
|
onRefresh: refreshList,
|
|
1233
1413
|
onAdd: handleAdd,
|
|
1234
1414
|
onEdit: handleEdit,
|
|
@@ -1722,10 +1902,14 @@ function apply(ctx) {
|
|
|
1722
1902
|
() => slots.inject(
|
|
1723
1903
|
"conversation.input.dock",
|
|
1724
1904
|
() => slots.register(
|
|
1725
|
-
{
|
|
1905
|
+
{
|
|
1906
|
+
name: "conversation.input.dock",
|
|
1907
|
+
id: "ops-access-badge",
|
|
1908
|
+
order: 30,
|
|
1909
|
+
inject: (sessionId) => ({ sessionId })
|
|
1910
|
+
},
|
|
1726
1911
|
(props) => AccessBadge({
|
|
1727
1912
|
sessionId: props.sessionId,
|
|
1728
|
-
session: props.session,
|
|
1729
1913
|
openPanel: (sid) => opsPanels.open(sid, "access")
|
|
1730
1914
|
})
|
|
1731
1915
|
)
|
package/lib/types.d.ts
CHANGED
|
@@ -110,4 +110,25 @@ export interface PanelDenied {
|
|
|
110
110
|
export interface OverviewGrant extends PanelGrant {
|
|
111
111
|
session: string;
|
|
112
112
|
}
|
|
113
|
+
/** One pending registration request as reported by GET /ops-access/admin/requests. */
|
|
114
|
+
export interface RegistrationRequestMeta {
|
|
115
|
+
id: string;
|
|
116
|
+
kind: string;
|
|
117
|
+
name: string;
|
|
118
|
+
tier: 'ro' | 'rw';
|
|
119
|
+
envelope: {
|
|
120
|
+
name?: string;
|
|
121
|
+
description?: string;
|
|
122
|
+
environment?: string;
|
|
123
|
+
};
|
|
124
|
+
reason?: string;
|
|
125
|
+
status: 'pending' | 'approved' | 'rejected';
|
|
126
|
+
createdAt: string;
|
|
127
|
+
/** Field names → value sizes; field VALUES never ride the list. */
|
|
128
|
+
fields: Record<string, number>;
|
|
129
|
+
}
|
|
130
|
+
/** The full request incl. field values — GET /ops-access/admin/requests/detail. */
|
|
131
|
+
export interface RegistrationRequestDetail extends Omit<RegistrationRequestMeta, 'fields'> {
|
|
132
|
+
fields: Record<string, unknown>;
|
|
133
|
+
}
|
|
113
134
|
//# sourceMappingURL=types.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elinpf/dsh-ops-access-ui",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Browser half carrier for @-mentioning ops-access profiles: the host row exists only for client-bundle discovery; candidates come from GET /ops-access/list served by @elinpf/dsh-ops-access, and mention expansion (agent/pre-step) lives there too.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -24,10 +24,7 @@
|
|
|
24
24
|
"./package.json": "./package.json"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
|
-
"lib
|
|
28
|
-
"lib/client.js",
|
|
29
|
-
"lib/invariant.js",
|
|
30
|
-
"lib/types.js",
|
|
27
|
+
"lib/**/*.js",
|
|
31
28
|
"lib/**/*.d.ts",
|
|
32
29
|
"cordis.patch.yml"
|
|
33
30
|
],
|