diff --git a/tenant-platform/src/api/system.ts b/tenant-platform/src/api/system.ts index 8adb803..f7e0abd 100644 --- a/tenant-platform/src/api/system.ts +++ b/tenant-platform/src/api/system.ts @@ -86,10 +86,29 @@ export function streamSystemUpdate(onLine: (line: string) => void, signal?: Abor return streamOperation('/system/update', onLine, signal) } +export function streamSelectiveUpdate(services: string[], onLine: (line: string) => void, signal?: AbortSignal) { + const qs = services.map(s => `services=${encodeURIComponent(s)}`).join('&') + return streamOperation(`/system/update-selective${qs ? '?' + qs : ''}`, onLine, signal) +} + export function streamSystemReset(onLine: (line: string) => void, signal?: AbortSignal) { return streamOperation('/system/reset', onLine, signal) } +export interface UpdateCheckResult { + currentVersion: string + latestVersion: string + hasUpdate: boolean + releasedAt: string + changelog: string + services: Record +} + +export async function checkForUpdates(): Promise { + const json = await authFetch('/system/check-update') + return json as UpdateCheckResult +} + // ── Database Management (PRIVATE mode only) ────────────────────────────── export interface TableInfo { diff --git a/tenant-platform/src/views/apps/AppDetailView.vue b/tenant-platform/src/views/apps/AppDetailView.vue index 5fad9e0..8a3946a 100644 --- a/tenant-platform/src/views/apps/AppDetailView.vue +++ b/tenant-platform/src/views/apps/AppDetailView.vue @@ -172,6 +172,72 @@ + + + +

+ API Key 用于外部工具(Postman / CI/CD)调用平台 API 进行认证,支持 update / push / im / license 等所有服务。每个应用最多 8 个,创建后仅显示一次。 +

+ + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
{{ createdApiKey }}
+ 复制 API Key + +
+
@@ -251,6 +317,7 @@ import { ElMessage, ElMessageBox } from 'element-plus' import { View } from '@element-plus/icons-vue' import { appApi, type App, type FeatureService } from '@/api/app' import client from '@/api/client' +import { formatTime } from '@/utils/date' import { connectServiceActivationRealtime, disconnectServiceActivationRealtime, @@ -278,6 +345,87 @@ const submittingActivation = ref(false) const activationForm = ref({ platform: '', serviceType: '', reason: '' }) const regenerating = ref(false) +// ── API Key Management ── +const apiKeys = ref<{ id: string; appKey: string; apiKey: string; name: string; enabled: boolean; createdAt: string }[]>([]) +const loadingApiKeys = ref(false) +const showCreateApiKey = ref(false) +const creatingApiKey = ref(false) +const newApiKeyName = ref('') +const showApiKeyResult = ref(false) +const createdApiKey = ref('') + +async function loadApiKeys() { + if (!app.value) return + loadingApiKeys.value = true + try { + const res = await client.get(`/apps/${app.value.appKey}/api-keys`) + apiKeys.value = res.data.data + } catch { + apiKeys.value = [] + } finally { + loadingApiKeys.value = false + } +} + +function openCreateApiKey() { + newApiKeyName.value = '' + showCreateApiKey.value = true +} + +async function submitCreateApiKey() { + if (!app.value) return + creatingApiKey.value = true + try { + const res = await client.post(`/apps/${app.value.appKey}/api-keys`, { + name: newApiKeyName.value.trim() || undefined, + }) + createdApiKey.value = res.data.data.apiKey + showCreateApiKey.value = false + showApiKeyResult.value = true + await loadApiKeys() + } catch (e: any) { + ElMessage.error(e?.response?.data?.message || '创建失败') + } finally { + creatingApiKey.value = false + } +} + +async function toggleApiKey(key: { id: string; enabled: boolean }) { + if (!app.value) return + try { + await client.patch(`/apps/${app.value.appKey}/api-keys/${key.id}`, { + enabled: !key.enabled, + }) + ElMessage.success(key.enabled ? '已禁用' : '已启用') + await loadApiKeys() + } catch (e: any) { + ElMessage.error(e?.response?.data?.message || '操作失败') + } +} + +async function deleteApiKey(key: { id: string; name: string }) { + if (!app.value) return + try { + await ElMessageBox.confirm(`确定删除 API Key${key.name ? ` "${key.name}"` : ''}?删除后不可恢复。`, '删除 API Key', { + type: 'warning', + confirmButtonText: '删除', + cancelButtonText: '取消', + }) + } catch { return } + try { + await client.delete(`/apps/${app.value.appKey}/api-keys/${key.id}`) + ElMessage.success('已删除') + await loadApiKeys() + } catch (e: any) { + ElMessage.error(e?.response?.data?.message || '删除失败') + } +} + +function copyApiKey() { + navigator.clipboard.writeText(createdApiKey.value) + ElMessage.success('已复制') +} + const showEditDialog = ref(false) const savingEdit = ref(false) const editForm = ref({ @@ -360,6 +508,7 @@ async function loadData() { app.value = appRes.data.data services.value = svcRes.data.data revealedSecret.value = null + loadApiKeys() } async function onToggleService(svcType: string, enable: boolean) { @@ -524,6 +673,17 @@ onBeforeUnmount(() => {