feat(desktop): push publish tasks via AMQP topic dispatch WebSocket

Introduce a desktop.task.dispatch topic exchange with per-client routing
keys. Tenant-api publishes a task_available frame keyed by target client
ID when a publish job is created; every instance binds its own transient
queue and forwards to the matching WebSocket (/api/desktop/dispatch) it
owns. Desktop-client now prefers this push channel and only falls back
to HTTP /lease polling when the socket is down. Also drop admin-web
monitoring-plugin remnants and scope the publish modal account list to
publish platforms.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-20 19:46:59 +08:00
parent 7abac1e9c4
commit 9fa091c150
20 changed files with 1083 additions and 177 deletions
@@ -11,7 +11,12 @@ import { articlesApi, mediaApi, publishJobsApi, resolveApiURL, tenantAccountsApi
import { coverUploadRequired, deriveCoverFileName } from "@/lib/cover-requirements";
import { formatDateTime } from "@/lib/display";
import { formatError } from "@/lib/errors";
import { getPublishPlatformMeta, normalizePublishPlatformId, normalizePublishPlatformIds } from "@/lib/publish-platforms";
import {
getPublishPlatformMeta,
isPublishPlatformId,
normalizePublishPlatformId,
normalizePublishPlatformIds,
} from "@/lib/publish-platforms";
type PublishState = "immediate" | "queued" | "unavailable";
@@ -109,8 +114,19 @@ const platformMap = computed(() => {
);
});
const publishAccounts = computed(() => {
return (accountsQuery.data.value ?? []).filter((account) => {
if (account.deleted_at) {
return false;
}
const platformId = normalizePublishPlatformId(account.platform);
return platformMap.value.has(platformId) || isPublishPlatformId(platformId);
});
});
const rawAccountMap = computed(() => {
return new Map((accountsQuery.data.value ?? []).map((account) => [account.id, account]));
return new Map(publishAccounts.value.map((account) => [account.id, account]));
});
const articlePlatformIds = computed(() => normalizePublishPlatformIds(detailQuery.data.value?.platforms ?? []));
@@ -118,8 +134,7 @@ const articlePlatformIds = computed(() => normalizePublishPlatformIds(detailQuer
const accountCards = computed<PublishAccountCard[]>(() => {
const preferredPlatforms = new Set(articlePlatformIds.value);
return [...(accountsQuery.data.value ?? [])]
.filter((account) => !account.deleted_at)
return [...publishAccounts.value]
.map((account) => {
const platformId = normalizePublishPlatformId(account.platform);
const platform = platformMap.value.get(platformId);