Add monitoring service and database schema
- Implement monitoring service with heartbeat, lease tasks, resume tasks, and task result handling. - Create monitoring time utilities for business date calculations. - Add unit tests for date window resolution and business day handling. - Define database schema for monitoring-related tables including quotas, daily reports, and task management. - Establish migration scripts for creating and dropping monitoring tables.
This commit is contained in:
@@ -1,13 +1,50 @@
|
||||
import type { MonitoringLeaseTask } from "@geo/shared-types";
|
||||
import { browser } from "wxt/browser";
|
||||
|
||||
import {
|
||||
createDefaultMonitoringPlatformStates,
|
||||
supportedMonitoringPlatforms,
|
||||
type StoredMonitoringPlatformState,
|
||||
} from "./monitoring-platforms";
|
||||
import { createDefaultPlatformStates, supportedPlatforms, type StoredPlatformState } from "./platforms";
|
||||
|
||||
export interface DoubaoMonitoringRuntimeState {
|
||||
fp: string | null;
|
||||
ms_token: string | null;
|
||||
device_id: string | null;
|
||||
web_id: string | null;
|
||||
tea_uuid: string | null;
|
||||
synced_at: string | null;
|
||||
source_url: string | null;
|
||||
}
|
||||
|
||||
export interface MonitoringPlatformRuntimeState {
|
||||
doubao: DoubaoMonitoringRuntimeState | null;
|
||||
}
|
||||
|
||||
export interface MonitoringPlatformRuntimeStatePatch {
|
||||
doubao?: Partial<DoubaoMonitoringRuntimeState> | null;
|
||||
}
|
||||
|
||||
export interface StoredMonitoringScheduledTask extends MonitoringLeaseTask {
|
||||
pacing_mode: "immediate" | "delayed";
|
||||
scheduled_at: string;
|
||||
next_execute_at: string;
|
||||
}
|
||||
|
||||
export interface ExtensionStorageState {
|
||||
installation_key: string;
|
||||
plugin_installation_id: number | null;
|
||||
installation_token: string | null;
|
||||
api_base_url: string | null;
|
||||
platforms: StoredPlatformState[];
|
||||
monitoring_platforms: StoredMonitoringPlatformState[];
|
||||
monitoring_last_heartbeat_at: string | null;
|
||||
monitoring_last_heartbeat_status: string | null;
|
||||
monitoring_last_cycle_at: string | null;
|
||||
monitoring_last_cycle_status: string | null;
|
||||
monitoring_task_queue: StoredMonitoringScheduledTask[];
|
||||
monitoring_runtime: MonitoringPlatformRuntimeState;
|
||||
last_updated_at: string | null;
|
||||
}
|
||||
|
||||
@@ -33,6 +70,85 @@ function mergePlatformStates(platforms?: StoredPlatformState[]): StoredPlatformS
|
||||
});
|
||||
}
|
||||
|
||||
function mergeMonitoringPlatformStates(platforms?: StoredMonitoringPlatformState[]): StoredMonitoringPlatformState[] {
|
||||
const defaults = createDefaultMonitoringPlatformStates();
|
||||
if (!platforms?.length) {
|
||||
return defaults;
|
||||
}
|
||||
|
||||
const incoming = new Map(platforms.map((platform) => [platform.ai_platform_id, platform]));
|
||||
return defaults.map((platform) => {
|
||||
const stored = incoming.get(platform.ai_platform_id);
|
||||
return stored ? { ...platform, ...stored } : platform;
|
||||
});
|
||||
}
|
||||
|
||||
function mergeMonitoringScheduledTasks(tasks?: StoredMonitoringScheduledTask[]): StoredMonitoringScheduledTask[] {
|
||||
if (!tasks?.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return tasks
|
||||
.filter((task) => typeof task?.task_id === "number" && task.task_id > 0)
|
||||
.map((task) => ({
|
||||
...task,
|
||||
pacing_mode: task.pacing_mode === "immediate" ? "immediate" : "delayed",
|
||||
scheduled_at: task.scheduled_at ?? new Date().toISOString(),
|
||||
next_execute_at: task.next_execute_at ?? new Date().toISOString(),
|
||||
}));
|
||||
}
|
||||
|
||||
function normalizeDoubaoMonitoringRuntimeState(
|
||||
state?: Partial<DoubaoMonitoringRuntimeState> | null,
|
||||
): DoubaoMonitoringRuntimeState | null {
|
||||
if (!state) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
fp: state.fp ?? null,
|
||||
ms_token: state.ms_token ?? null,
|
||||
device_id: state.device_id ?? null,
|
||||
web_id: state.web_id ?? null,
|
||||
tea_uuid: state.tea_uuid ?? null,
|
||||
synced_at: state.synced_at ?? null,
|
||||
source_url: state.source_url ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function mergeMonitoringRuntimeState(
|
||||
runtime?: MonitoringPlatformRuntimeStatePatch | null,
|
||||
): MonitoringPlatformRuntimeState {
|
||||
return {
|
||||
doubao: normalizeDoubaoMonitoringRuntimeState(runtime?.doubao),
|
||||
};
|
||||
}
|
||||
|
||||
function resolveNextMonitoringRuntimeState(
|
||||
current: MonitoringPlatformRuntimeState,
|
||||
patch?: MonitoringPlatformRuntimeStatePatch | null,
|
||||
): MonitoringPlatformRuntimeState {
|
||||
if (patch == null) {
|
||||
return current;
|
||||
}
|
||||
|
||||
const nextDoubao =
|
||||
typeof patch.doubao === "undefined"
|
||||
? current.doubao
|
||||
: patch.doubao === null
|
||||
? null
|
||||
: {
|
||||
...(current.doubao ?? {}),
|
||||
...patch.doubao,
|
||||
};
|
||||
|
||||
return mergeMonitoringRuntimeState({
|
||||
...current,
|
||||
...patch,
|
||||
doubao: nextDoubao,
|
||||
});
|
||||
}
|
||||
|
||||
async function writeState(nextState: ExtensionStorageState): Promise<void> {
|
||||
await browser.storage.local.set({
|
||||
[STORAGE_KEY]: nextState,
|
||||
@@ -49,13 +165,24 @@ export async function getExtensionState(): Promise<ExtensionStorageState> {
|
||||
installation_token: stored?.installation_token ?? null,
|
||||
api_base_url: stored?.api_base_url ?? null,
|
||||
platforms: mergePlatformStates(stored?.platforms),
|
||||
monitoring_platforms: mergeMonitoringPlatformStates(stored?.monitoring_platforms),
|
||||
monitoring_last_heartbeat_at: stored?.monitoring_last_heartbeat_at ?? null,
|
||||
monitoring_last_heartbeat_status: stored?.monitoring_last_heartbeat_status ?? null,
|
||||
monitoring_last_cycle_at: stored?.monitoring_last_cycle_at ?? null,
|
||||
monitoring_last_cycle_status: stored?.monitoring_last_cycle_status ?? null,
|
||||
monitoring_task_queue: mergeMonitoringScheduledTasks(stored?.monitoring_task_queue),
|
||||
monitoring_runtime: mergeMonitoringRuntimeState(stored?.monitoring_runtime),
|
||||
last_updated_at: stored?.last_updated_at ?? null,
|
||||
};
|
||||
|
||||
const shouldPersist =
|
||||
!stored?.installation_key ||
|
||||
!stored?.platforms ||
|
||||
stored.platforms.length !== supportedPlatforms.length;
|
||||
stored.platforms.length !== supportedPlatforms.length ||
|
||||
!stored?.monitoring_platforms ||
|
||||
stored.monitoring_platforms.length !== supportedMonitoringPlatforms.length ||
|
||||
typeof stored?.monitoring_task_queue === "undefined" ||
|
||||
!stored?.monitoring_runtime;
|
||||
|
||||
if (shouldPersist) {
|
||||
await writeState(nextState);
|
||||
@@ -65,13 +192,29 @@ export async function getExtensionState(): Promise<ExtensionStorageState> {
|
||||
}
|
||||
|
||||
export async function patchExtensionState(
|
||||
patch: Partial<Omit<ExtensionStorageState, "platforms">> & { platforms?: StoredPlatformState[] },
|
||||
patch: Partial<Omit<ExtensionStorageState, "platforms" | "monitoring_platforms" | "monitoring_runtime">> & {
|
||||
platforms?: StoredPlatformState[];
|
||||
monitoring_platforms?: StoredMonitoringPlatformState[];
|
||||
monitoring_task_queue?: StoredMonitoringScheduledTask[];
|
||||
monitoring_runtime?: MonitoringPlatformRuntimeStatePatch | null;
|
||||
},
|
||||
): Promise<ExtensionStorageState> {
|
||||
const current = await getExtensionState();
|
||||
const nextState: ExtensionStorageState = {
|
||||
...current,
|
||||
...patch,
|
||||
platforms: patch.platforms ? mergePlatformStates(patch.platforms) : current.platforms,
|
||||
monitoring_platforms: patch.monitoring_platforms
|
||||
? mergeMonitoringPlatformStates(patch.monitoring_platforms)
|
||||
: current.monitoring_platforms,
|
||||
monitoring_task_queue:
|
||||
typeof patch.monitoring_task_queue !== "undefined"
|
||||
? mergeMonitoringScheduledTasks(patch.monitoring_task_queue)
|
||||
: current.monitoring_task_queue,
|
||||
monitoring_runtime:
|
||||
typeof patch.monitoring_runtime !== "undefined"
|
||||
? resolveNextMonitoringRuntimeState(current.monitoring_runtime, patch.monitoring_runtime)
|
||||
: current.monitoring_runtime,
|
||||
last_updated_at: new Date().toISOString(),
|
||||
};
|
||||
await writeState(nextState);
|
||||
@@ -97,3 +240,23 @@ export async function updatePlatformState(
|
||||
export async function resetPlatformStates(): Promise<ExtensionStorageState> {
|
||||
return patchExtensionState({ platforms: createDefaultPlatformStates() });
|
||||
}
|
||||
|
||||
export async function updateMonitoringPlatformState(
|
||||
platformId: string,
|
||||
updater: Partial<StoredMonitoringPlatformState>,
|
||||
): Promise<ExtensionStorageState> {
|
||||
const current = await getExtensionState();
|
||||
const monitoringPlatforms = current.monitoring_platforms.map((platform) =>
|
||||
platform.ai_platform_id === platformId
|
||||
? {
|
||||
...platform,
|
||||
...updater,
|
||||
}
|
||||
: platform,
|
||||
);
|
||||
return patchExtensionState({ monitoring_platforms: monitoringPlatforms });
|
||||
}
|
||||
|
||||
export async function resetMonitoringPlatformStates(): Promise<ExtensionStorageState> {
|
||||
return patchExtensionState({ monitoring_platforms: createDefaultMonitoringPlatformStates() });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user