feat(client): show digital sync credential gaps
This commit is contained in:
@@ -57,6 +57,7 @@ interface QimingclawSyncStatus {
|
|||||||
syncing: boolean;
|
syncing: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
endpoint: string | null;
|
endpoint: string | null;
|
||||||
|
missingCredentials?: string[];
|
||||||
pending: number;
|
pending: number;
|
||||||
failed: number;
|
failed: number;
|
||||||
lastSyncAt: string | null;
|
lastSyncAt: string | null;
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ interface ManagementSyncStatus {
|
|||||||
syncing: boolean;
|
syncing: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
endpoint: string | null;
|
endpoint: string | null;
|
||||||
|
missingCredentials?: string[];
|
||||||
pending: number;
|
pending: number;
|
||||||
failed: number;
|
failed: number;
|
||||||
lastSyncAt: string | null;
|
lastSyncAt: string | null;
|
||||||
@@ -448,6 +449,7 @@ function summarizeSyncError(value?: string | null): string {
|
|||||||
function syncStatusTone(status: ManagementSyncStatus | null): 'info' | 'success' | 'warning' | 'danger' {
|
function syncStatusTone(status: ManagementSyncStatus | null): 'info' | 'success' | 'warning' | 'danger' {
|
||||||
if (!status) return 'info';
|
if (!status) return 'info';
|
||||||
if (!status.enabled || !status.endpoint) return 'warning';
|
if (!status.enabled || !status.endpoint) return 'warning';
|
||||||
|
if ((status.missingCredentials?.length ?? 0) > 0) return 'warning';
|
||||||
if (status.failed > 0 || status.lastSyncError) return 'danger';
|
if (status.failed > 0 || status.lastSyncError) return 'danger';
|
||||||
if (status.syncing || status.pending > 0) return 'warning';
|
if (status.syncing || status.pending > 0) return 'warning';
|
||||||
return status.lastSyncAt ? 'success' : 'info';
|
return status.lastSyncAt ? 'success' : 'info';
|
||||||
@@ -457,6 +459,7 @@ function syncStatusLabel(status: ManagementSyncStatus | null): string {
|
|||||||
if (!status) return '未连接';
|
if (!status) return '未连接';
|
||||||
if (!status.enabled) return '已停用';
|
if (!status.enabled) return '已停用';
|
||||||
if (!status.endpoint) return '未配置';
|
if (!status.endpoint) return '未配置';
|
||||||
|
if ((status.missingCredentials?.length ?? 0) > 0) return '缺少凭据';
|
||||||
if (status.syncing) return '同步中';
|
if (status.syncing) return '同步中';
|
||||||
if (status.failed > 0 || status.lastSyncError) return '同步异常';
|
if (status.failed > 0 || status.lastSyncError) return '同步异常';
|
||||||
if (status.pending > 0) return '待同步';
|
if (status.pending > 0) return '待同步';
|
||||||
@@ -467,6 +470,9 @@ function syncStatusSummary(status: ManagementSyncStatus | null): string {
|
|||||||
if (!status) return '当前环境未连接 qimingclaw 同步桥。';
|
if (!status) return '当前环境未连接 qimingclaw 同步桥。';
|
||||||
if (!status.enabled) return '管理端同步已停用。';
|
if (!status.enabled) return '管理端同步已停用。';
|
||||||
if (!status.endpoint) return '管理端同步地址未配置。';
|
if (!status.endpoint) return '管理端同步地址未配置。';
|
||||||
|
if ((status.missingCredentials?.length ?? 0) > 0) {
|
||||||
|
return `管理端同步凭据未完整:${status.missingCredentials?.join('、')}`;
|
||||||
|
}
|
||||||
const error = summarizeSyncError(status.lastSyncError);
|
const error = summarizeSyncError(status.lastSyncError);
|
||||||
if (status.failed > 0 || error) {
|
if (status.failed > 0 || error) {
|
||||||
const retrySummary = status.failureSummary
|
const retrySummary = status.failureSummary
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -13,7 +13,7 @@
|
|||||||
console.warn("[qimingclaw] digital employee auth bootstrap skipped", error);
|
console.warn("[qimingclaw] digital employee auth bootstrap skipped", error);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script type="module" crossorigin src="./assets/index-BKHFgVhX.js"></script>
|
<script type="module" crossorigin src="./assets/index-gtzF6MAO.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-ReBtI0Fy.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-ReBtI0Fy.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ export interface DigitalEmployeeSyncStatus {
|
|||||||
syncing: boolean;
|
syncing: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
endpoint: string | null;
|
endpoint: string | null;
|
||||||
|
missingCredentials: string[];
|
||||||
pending: number;
|
pending: number;
|
||||||
failed: number;
|
failed: number;
|
||||||
lastSyncAt: string | null;
|
lastSyncAt: string | null;
|
||||||
@@ -132,6 +133,7 @@ export function getDigitalEmployeeSyncStatus(): DigitalEmployeeSyncStatus {
|
|||||||
syncing,
|
syncing,
|
||||||
enabled: config.enabled,
|
enabled: config.enabled,
|
||||||
endpoint: config.endpoint,
|
endpoint: config.endpoint,
|
||||||
|
missingCredentials: missingSyncCredentialLabels(config),
|
||||||
pending: countOutbox("pending"),
|
pending: countOutbox("pending"),
|
||||||
failed: countOutbox("failed"),
|
failed: countOutbox("failed"),
|
||||||
lastSyncAt,
|
lastSyncAt,
|
||||||
@@ -147,7 +149,11 @@ export async function flushDigitalEmployeeSyncOutbox(
|
|||||||
if (syncing) return getDigitalEmployeeSyncStatus();
|
if (syncing) return getDigitalEmployeeSyncStatus();
|
||||||
|
|
||||||
const config = resolveSyncConfig();
|
const config = resolveSyncConfig();
|
||||||
if (!config.enabled || !config.endpoint || !config.clientKey || !config.authToken) {
|
if (
|
||||||
|
!config.enabled ||
|
||||||
|
!config.endpoint ||
|
||||||
|
missingSyncCredentialLabels(config).length > 0
|
||||||
|
) {
|
||||||
lastSyncError = null;
|
lastSyncError = null;
|
||||||
return getDigitalEmployeeSyncStatus();
|
return getDigitalEmployeeSyncStatus();
|
||||||
}
|
}
|
||||||
@@ -255,6 +261,15 @@ function buildEndpoint(serverHost: string | null): string | null {
|
|||||||
return `${normalized.replace(/\/+$/, "")}${DEFAULT_SYNC_PATH}`;
|
return `${normalized.replace(/\/+$/, "")}${DEFAULT_SYNC_PATH}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function missingSyncCredentialLabels(
|
||||||
|
config: DigitalEmployeeSyncConfig,
|
||||||
|
): string[] {
|
||||||
|
return [
|
||||||
|
config.clientKey ? "" : "客户端 Key",
|
||||||
|
config.authToken ? "" : "登录 token",
|
||||||
|
].filter((value): value is string => Boolean(value));
|
||||||
|
}
|
||||||
|
|
||||||
function readClientKey(serverHost: string | null): string | null {
|
function readClientKey(serverHost: string | null): string | null {
|
||||||
const username = readSetting("auth.username");
|
const username = readSetting("auth.username");
|
||||||
const globalKey = readSetting("auth.saved_key");
|
const globalKey = readSetting("auth.saved_key");
|
||||||
|
|||||||
@@ -464,7 +464,7 @@ window.QimingClawBridge.digital.flushSync()
|
|||||||
- worker 只消费 outbox,不直接侵入 Plan / Task / Run / Event 写入链路。
|
- worker 只消费 outbox,不直接侵入 Plan / Task / Run / Event 写入链路。
|
||||||
- 管理端返回格式包含 `acked[]`,每项带 `outbox_id`、`entity_type`、`entity_id`、`remote_id`。
|
- 管理端返回格式包含 `acked[]`,每项带 `outbox_id`、`entity_type`、`entity_id`、`remote_id`。
|
||||||
- 若管理端只 ack 批次中的部分 item,已 ack 的 item 会标记 synced,未 ack 的 item 会保留 failed 状态并进入下一轮补偿。
|
- 若管理端只 ack 批次中的部分 item,已 ack 的 item 会标记 synced,未 ack 的 item 会保留 failed 状态并进入下一轮补偿。
|
||||||
- 当前接口沿用管理端登录态鉴权,客户端必须能读取到登录 ticket/token 才会推送;没有 token 时保持本地 outbox 等待。
|
- 当前接口沿用管理端登录态鉴权,客户端必须能读取到客户端 Key 和登录 ticket/token 才会推送;缺少凭据时保持本地 outbox 等待,并在数字员工同步状态条显示缺少项。
|
||||||
|
|
||||||
后端最小落点:
|
后端最小落点:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user