510 lines
29 KiB
TypeScript
510 lines
29 KiB
TypeScript
import type {
|
|
ApiEnvelope,
|
|
AdminUser,
|
|
AccessQuery,
|
|
AccessSummary,
|
|
AccessThresholdConfig,
|
|
AccessThresholdUpdate,
|
|
AccessIdentityClaimInput,
|
|
AccessIdentityClaimResult,
|
|
AccessUnresolvedIdentity,
|
|
AccessUnresolvedIdentityQuery,
|
|
AccessVehicleRow,
|
|
AlertAction,
|
|
AlertEvent,
|
|
AlertNotification,
|
|
AlertNotificationConfig,
|
|
AlertNotificationDeliveryHealth,
|
|
AlertNotificationRetryAudit,
|
|
AlertNotificationRetryResult,
|
|
AlertQuery,
|
|
AlertRule,
|
|
AlertRulePage,
|
|
AlertRuleInput,
|
|
AlertRuleRevision,
|
|
AlertSummary,
|
|
DailyMileageRow,
|
|
HydrogenDailyEvidence,
|
|
DashboardSummary,
|
|
HistoryLocationRow,
|
|
HistoryDataResponse,
|
|
HistorySeriesResponse,
|
|
HistoryExportJob,
|
|
HistoryExportBatchAction, HistoryExportBatchResult,
|
|
HistoryExportCleanupPreview,
|
|
HistoryExportCleanupRecord,
|
|
HistoryExportCleanupResult,
|
|
HistoryExportCleanupAutomationState,
|
|
HistoryExportCleanupAutomationPolicyInput,
|
|
HistoryExportCleanupAutomationActionInput,
|
|
HistoryExportPage,
|
|
HistoryExportRequest,
|
|
HistoryPreferences,
|
|
HistoryMetricCatalog,
|
|
MetricCatalog,
|
|
LatestTelemetryResponse,
|
|
MileageSummary,
|
|
MileageStatistics,
|
|
MapReverseGeocode,
|
|
MonitorMapResponse,
|
|
MonitorWorkspaceResponse,
|
|
MonitorSummary,
|
|
OnlineStatisticsSummary,
|
|
OnlineVehicleStatusRow,
|
|
OpsHealth,
|
|
QualityNotificationPlan,
|
|
Page,
|
|
QualitySummary,
|
|
QualityIssueRow,
|
|
ReconciliationIssue,
|
|
ReconciliationAssignee,
|
|
ReconciliationAssignmentRequest,
|
|
ReconciliationBatchAssignmentRequest,
|
|
ReconciliationBatchActionRequest,
|
|
ReconciliationBatchActionResult,
|
|
ReconciliationQuery,
|
|
ReconciliationLifecycleRequest,
|
|
ReconciliationBatchLifecycleRequest,
|
|
ReconciliationSummary,
|
|
RawFrameRow,
|
|
RealtimeLocationRow,
|
|
SourceReadinessPlan,
|
|
SessionInfo,
|
|
LoginResponse,
|
|
OneOSExchangeResponse,
|
|
CustomerUserInput,
|
|
CustomerUserBatchItem,
|
|
CustomerUserBatchResult,
|
|
TrackPlaybackResponse,
|
|
VehicleRealtimeRow,
|
|
VehicleCoverageRow,
|
|
VehicleCoverageSummary,
|
|
VehicleBusinessFilters,
|
|
VehicleDetail,
|
|
VehicleProfile,
|
|
VehicleProfileInput,
|
|
VehicleProfileSyncRequest,
|
|
VehicleProfileSyncResult,
|
|
VehicleIdentityResolution,
|
|
VehicleServiceOverview,
|
|
VehicleServiceSummary,
|
|
VehicleSourceDiagnostic,
|
|
VehicleSourceEvidence,
|
|
VehicleSourcePolicyUpdate,
|
|
VehicleRow
|
|
} from './types';
|
|
import { getAccessToken, notifyUnauthorizedSession } from '../v2/auth/session';
|
|
|
|
export type RawFrameQuery = {
|
|
keyword?: string;
|
|
vin?: string;
|
|
protocol?: string;
|
|
dateFrom?: string;
|
|
dateTo?: string;
|
|
fields?: string[];
|
|
includeFields?: boolean;
|
|
limit?: number;
|
|
offset?: number;
|
|
};
|
|
|
|
export type VehicleOverviewBatchQuery = {
|
|
keywords: string[];
|
|
protocol?: string;
|
|
limit?: number;
|
|
offset?: number;
|
|
};
|
|
|
|
export type MileageQuery = {
|
|
dateFrom: string;
|
|
dateTo: string;
|
|
keyword?: string;
|
|
vins?: string[];
|
|
vehicleScope?: 'bound';
|
|
protocol?: string;
|
|
protocols?: string[];
|
|
deduplicate?: boolean;
|
|
limit?: number;
|
|
offset?: number;
|
|
};
|
|
|
|
type ApiErrorEnvelope = {
|
|
error?: {
|
|
code?: string;
|
|
message?: string;
|
|
detail?: string;
|
|
};
|
|
traceId?: string;
|
|
};
|
|
|
|
export const API_QUERY_TIMEOUT_MS = 15_000;
|
|
|
|
function queryTimeout(init: RequestInit | undefined) {
|
|
const upstream = init?.signal;
|
|
const controller = new AbortController();
|
|
let timedOut = false;
|
|
const abortFromUpstream = () => controller.abort(upstream?.reason);
|
|
if (upstream?.aborted) abortFromUpstream();
|
|
else upstream?.addEventListener('abort', abortFromUpstream, { once: true });
|
|
const timer = window.setTimeout(() => {
|
|
timedOut = true;
|
|
controller.abort();
|
|
}, API_QUERY_TIMEOUT_MS);
|
|
return {
|
|
init: { ...init, signal: controller.signal },
|
|
timedOut: () => timedOut,
|
|
cleanup: () => {
|
|
window.clearTimeout(timer);
|
|
upstream?.removeEventListener('abort', abortFromUpstream);
|
|
}
|
|
};
|
|
}
|
|
|
|
function requestTimeoutError() {
|
|
const error = new Error(`请求超过 ${API_QUERY_TIMEOUT_MS / 1_000} 秒仍未完成,请检查网络后重试`);
|
|
error.name = 'ApiRequestTimeoutError';
|
|
return error;
|
|
}
|
|
|
|
async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
|
const token = getAccessToken();
|
|
const requestInit = token ? { ...init, headers: withAuthorization(init?.headers, token) } : init;
|
|
const timeout = queryTimeout(requestInit);
|
|
try {
|
|
const response = await fetch(path, timeout?.init ?? requestInit);
|
|
if (!response.ok) {
|
|
if (response.status === 401 && token && path !== '/api/v2/session') notifyUnauthorizedSession(token);
|
|
throw new Error(await responseErrorMessage(response));
|
|
}
|
|
const envelope = (await response.json()) as ApiEnvelope<T>;
|
|
if (timeout?.timedOut()) throw requestTimeoutError();
|
|
return envelope.data;
|
|
} catch (error) {
|
|
if (timeout?.timedOut() && (!(error instanceof Error) || error.name !== 'ApiRequestTimeoutError')) {
|
|
throw requestTimeoutError();
|
|
}
|
|
throw error;
|
|
} finally {
|
|
timeout?.cleanup();
|
|
}
|
|
}
|
|
|
|
async function requestBlob(path: string, signal?: AbortSignal, requestInit?: RequestInit, fallbackFilename = 'history-export.csv') {
|
|
const token = getAccessToken();
|
|
const init: RequestInit = token
|
|
? { ...requestInit, signal, headers: withAuthorization(requestInit?.headers, token) }
|
|
: { ...requestInit, signal };
|
|
const response = await fetch(path, init);
|
|
if (!response.ok) {
|
|
if (response.status === 401 && token) notifyUnauthorizedSession(token);
|
|
throw new Error(await responseErrorMessage(response));
|
|
}
|
|
const encodedFilename = response.headers.get('X-Export-Name')?.trim();
|
|
let filename = fallbackFilename;
|
|
if (encodedFilename) {
|
|
try {
|
|
filename = decodeURIComponent(encodedFilename);
|
|
} catch {
|
|
filename = encodedFilename;
|
|
}
|
|
}
|
|
return {
|
|
blob: await response.blob(),
|
|
filename
|
|
};
|
|
}
|
|
|
|
function withAuthorization(headers: HeadersInit | undefined, token: string) {
|
|
const authorized = new Headers(headers);
|
|
authorized.set('Authorization', `Bearer ${token}`);
|
|
return authorized;
|
|
}
|
|
|
|
function withSignal(init: RequestInit | undefined, signal?: AbortSignal) {
|
|
return signal ? { ...init, signal } : init;
|
|
}
|
|
|
|
function normalizeOpsHealth(value: OpsHealth): OpsHealth {
|
|
return {
|
|
...value,
|
|
linkHealth: Array.isArray(value.linkHealth) ? value.linkHealth : [],
|
|
capacityFindings: Array.isArray(value.capacityFindings) ? value.capacityFindings : []
|
|
};
|
|
}
|
|
|
|
function normalizeSourceReadiness(value: SourceReadinessPlan): SourceReadinessPlan {
|
|
return {
|
|
...value,
|
|
sources: Array.isArray(value.sources) ? value.sources : []
|
|
};
|
|
}
|
|
|
|
async function responseErrorMessage(response: Response) {
|
|
try {
|
|
const envelope = (await response.json()) as ApiErrorEnvelope;
|
|
const message = envelope.error?.message?.trim();
|
|
const detail = envelope.error?.detail?.trim();
|
|
const traceID = envelope.traceId?.trim();
|
|
if (message && detail) {
|
|
return withTraceID(`${message}: ${detail}`, traceID);
|
|
}
|
|
if (message) {
|
|
return withTraceID(message, traceID);
|
|
}
|
|
} catch {
|
|
// Fall back to status when the server does not return the platform envelope.
|
|
}
|
|
return `request failed ${response.status}`;
|
|
}
|
|
|
|
function withTraceID(message: string, traceID?: string) {
|
|
return traceID ? `${message} (traceId: ${traceID})` : message;
|
|
}
|
|
|
|
export const api = {
|
|
session: (signal?: AbortSignal) => request<SessionInfo>('/api/v2/session', withSignal(undefined, signal)),
|
|
login: (credentials: { username: string; password: string }) => request<LoginResponse>('/api/v2/auth/login', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials)
|
|
}),
|
|
exchangeOneOSTicket: (ticket: string) => request<OneOSExchangeResponse>('/api/v2/auth/oneos/exchange', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ticket })
|
|
}),
|
|
logout: () => request<{ loggedOut: boolean }>('/api/v2/auth/logout', { method: 'POST' }),
|
|
changePassword: (input: { currentPassword: string; newPassword: string }) => request<{ changed: boolean }>('/api/v2/auth/password', {
|
|
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
adminUsers: (signal?: AbortSignal) => request<AdminUser[]>('/api/v2/admin/users', withSignal(undefined, signal)),
|
|
createCustomerUser: (input: CustomerUserInput) => request<{ id: number }>('/api/v2/admin/users', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
batchCustomerUsers: (mode: 'preview' | 'create', items: CustomerUserBatchItem[]) => request<CustomerUserBatchResult>('/api/v2/admin/users/batch', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode, items })
|
|
}),
|
|
updateCustomerUser: (id: number, input: CustomerUserInput) => request<{ id: number }>(`/api/v2/admin/users/${id}`, {
|
|
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
monitorSummary: (params = new URLSearchParams(), signal?: AbortSignal) => request<MonitorSummary>(`/api/v2/monitor/summary?${params.toString()}`, signal ? { signal } : undefined),
|
|
monitorMap: (params = new URLSearchParams(), signal?: AbortSignal) => request<MonitorMapResponse>(`/api/v2/monitor/map?${params.toString()}`, signal ? { signal } : undefined),
|
|
monitorWorkspace: (params = new URLSearchParams(), signal?: AbortSignal) => request<MonitorWorkspaceResponse>(`/api/v2/monitor/workspace?${params.toString()}`, signal ? { signal } : undefined),
|
|
trackPlayback: (params = new URLSearchParams(), signal?: AbortSignal) => request<TrackPlaybackResponse>(`/api/v2/tracks?${params.toString()}`, signal ? { signal } : undefined),
|
|
metricCatalog: (signal?: AbortSignal) => request<MetricCatalog>('/api/v2/metrics', withSignal(undefined, signal)),
|
|
historyMetricCatalog: (signal?: AbortSignal) => request<HistoryMetricCatalog>('/api/v2/history/metrics', withSignal(undefined, signal)),
|
|
historyData: (params = new URLSearchParams(), signal?: AbortSignal) => request<HistoryDataResponse>(`/api/v2/history/query?${params.toString()}`, signal ? { signal } : undefined),
|
|
historySeries: (params = new URLSearchParams(), signal?: AbortSignal) => request<HistorySeriesResponse>(`/api/v2/history/series?${params.toString()}`, signal ? { signal } : undefined),
|
|
historyExports: (signal?: AbortSignal) => request<HistoryExportJob[]>('/api/v2/exports', withSignal(undefined, signal)),
|
|
historyExportPage: (params = new URLSearchParams(), signal?: AbortSignal) => request<HistoryExportPage>(`/api/v2/exports/page?${params.toString()}`, withSignal(undefined, signal)),
|
|
historyExportCleanupPreview: (params = new URLSearchParams(), signal?: AbortSignal) => request<HistoryExportCleanupPreview>(`/api/v2/exports/cleanup/preview?${params.toString()}`, withSignal(undefined, signal)),
|
|
historyExportCleanupAudit: (signal?: AbortSignal) => request<HistoryExportCleanupRecord[]>('/api/v2/exports/cleanup/audit?limit=20', withSignal(undefined, signal)),
|
|
cleanupHistoryExports: (input: { olderThanDays: number; ownerScope: 'all' | 'mine'; previewToken: string }) => request<HistoryExportCleanupResult>('/api/v2/exports/cleanup', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
historyExportCleanupAutomation: (signal?: AbortSignal) => request<HistoryExportCleanupAutomationState>('/api/v2/exports/cleanup/automation', withSignal(undefined, signal)),
|
|
updateHistoryExportCleanupAutomation: (input: HistoryExportCleanupAutomationPolicyInput) => request<HistoryExportCleanupAutomationState>('/api/v2/exports/cleanup/automation', {
|
|
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
approveHistoryExportCleanupAutomation: (id: string, input: HistoryExportCleanupAutomationActionInput) => request<HistoryExportCleanupAutomationState>(`/api/v2/exports/cleanup/automation/${encodeURIComponent(id)}/approve`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
rejectHistoryExportCleanupAutomation: (id: string, input: HistoryExportCleanupAutomationActionInput) => request<HistoryExportCleanupAutomationState>(`/api/v2/exports/cleanup/automation/${encodeURIComponent(id)}/reject`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
retryHistoryExportCleanupAutomation: (id: string, input: HistoryExportCleanupAutomationActionInput) => request<HistoryExportCleanupAutomationState>(`/api/v2/exports/cleanup/automation/${encodeURIComponent(id)}/retry`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
updateHistoryExportCleanupProtection: (id: string, input: { protected: boolean; reason: string }) => request<HistoryExportJob>(`/api/v2/exports/${encodeURIComponent(id)}/cleanup-protection`, {
|
|
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
historyPreferences: (signal?: AbortSignal) => request<HistoryPreferences>('/api/v2/history/preferences', withSignal(undefined, signal)),
|
|
updateHistoryPreferences: (input: Pick<HistoryPreferences, 'retentionDays' | 'fieldViews'>) => request<HistoryPreferences>('/api/v2/history/preferences', {
|
|
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
createHistoryExport: (query: HistoryExportRequest) => request<HistoryExportJob>('/api/v2/exports', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
|
|
}),
|
|
cancelHistoryExport: (id: string) => request<HistoryExportJob>(`/api/v2/exports/${encodeURIComponent(id)}/cancel`, { method: 'POST' }),
|
|
rebuildHistoryExport: (id: string) => request<HistoryExportJob>(`/api/v2/exports/${encodeURIComponent(id)}/rebuild`, { method: 'POST' }),
|
|
archiveHistoryExport: (id: string) => request<HistoryExportJob>(`/api/v2/exports/${encodeURIComponent(id)}/archive`, { method: 'POST' }),
|
|
restoreHistoryExport: (id: string) => request<HistoryExportJob>(`/api/v2/exports/${encodeURIComponent(id)}/restore`, { method: 'POST' }),
|
|
batchHistoryExports: (input: { ids: string[]; action: HistoryExportBatchAction }) => request<HistoryExportBatchResult>('/api/v2/exports/batch', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
downloadHistoryExport: (id: string, signal?: AbortSignal) => requestBlob(`/api/v2/exports/${encodeURIComponent(id)}/download`, signal),
|
|
accessSummary: (query: AccessQuery, signal?: AbortSignal) => request<AccessSummary>('/api/v2/access/summary', withSignal({
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
|
|
}, signal)),
|
|
accessVehicles: (query: AccessQuery, signal?: AbortSignal) => request<Page<AccessVehicleRow>>('/api/v2/access/vehicles', withSignal({
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
|
|
}, signal)),
|
|
accessUnresolvedIdentities: (query: AccessUnresolvedIdentityQuery, signal?: AbortSignal) => request<Page<AccessUnresolvedIdentity>>('/api/v2/access/unresolved-identities', withSignal({
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
|
|
}, signal)),
|
|
claimAccessIdentity: (id: string, input: AccessIdentityClaimInput) => request<AccessIdentityClaimResult>(`/api/v2/access/unresolved-identities/${encodeURIComponent(id)}/claim`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
accessThresholds: (signal?: AbortSignal) => request<AccessThresholdConfig>('/api/v2/access/thresholds', withSignal(undefined, signal)),
|
|
updateAccessThresholds: (update: AccessThresholdUpdate) => request<AccessThresholdConfig>('/api/v2/access/thresholds', {
|
|
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(update)
|
|
}),
|
|
alertSummaryV2: (query: AlertQuery, signal?: AbortSignal) => request<AlertSummary>('/api/v2/alerts/summary', withSignal({
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
|
|
}, signal)),
|
|
alertEventsV2: (query: AlertQuery, signal?: AbortSignal) => request<Page<AlertEvent>>('/api/v2/alerts/events', withSignal({
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
|
|
}, signal)),
|
|
alertEventV2: (id: string, signal?: AbortSignal) => request<AlertEvent>(`/api/v2/alerts/events/${encodeURIComponent(id)}`, withSignal(undefined, signal)),
|
|
actOnAlertV2: (id: string, action: Pick<AlertAction, 'action' | 'note'> & { version: number; actor?: string }) => request<AlertEvent>(`/api/v2/alerts/events/${encodeURIComponent(id)}/actions`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(action)
|
|
}),
|
|
alertRulesV2: (signal?: AbortSignal) => request<AlertRule[]>('/api/v2/alerts/rules', withSignal(undefined, signal)),
|
|
alertRuleLibraryV2: (params = new URLSearchParams(), signal?: AbortSignal) => request<AlertRulePage>(`/api/v2/alerts/rules/library?${params.toString()}`, withSignal(undefined, signal)),
|
|
alertRuleRevisionsV2: (id: string, signal?: AbortSignal) => request<AlertRuleRevision[]>(`/api/v2/alerts/rules/${encodeURIComponent(id)}/revisions`, withSignal(undefined, signal)),
|
|
saveAlertRuleV2: (input: AlertRuleInput) => request<AlertRule>(input.version > 0 ? `/api/v2/alerts/rules/${encodeURIComponent(input.id)}` : '/api/v2/alerts/rules', {
|
|
method: input.version > 0 ? 'PUT' : 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
setAlertRuleEnabledV2: (id: string, update: { version: number; enabled: boolean; actor?: string }) => request<AlertRule>(`/api/v2/alerts/rules/${encodeURIComponent(id)}/enabled`, {
|
|
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(update)
|
|
}),
|
|
rollbackAlertRuleV2: (id: string, input: { targetVersion: number; currentVersion: number; actor?: string }) => request<AlertRule>(`/api/v2/alerts/rules/${encodeURIComponent(id)}/rollback`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
archiveAlertRuleV2: (id: string, input: { version: number; reason: string; actor?: string }) => request<AlertRule>(`/api/v2/alerts/rules/${encodeURIComponent(id)}/archive`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
restoreAlertRuleV2: (id: string, input: { version: number; reason: string; actor?: string }) => request<AlertRule>(`/api/v2/alerts/rules/${encodeURIComponent(id)}/restore`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
alertNotificationConfigV2: (signal?: AbortSignal) => request<AlertNotificationConfig>('/api/v2/alerts/notification-config', withSignal(undefined, signal)),
|
|
alertNotificationDeliveryHealthV2: (signal?: AbortSignal) => request<AlertNotificationDeliveryHealth>('/api/v2/alerts/notifications/health', withSignal(undefined, signal)),
|
|
alertNotificationsV2: (params = new URLSearchParams(), signal?: AbortSignal) => request<Page<AlertNotification>>(`/api/v2/alerts/notifications?${params.toString()}`, withSignal(undefined, signal)),
|
|
readAlertNotificationsV2: (ids: number[]) => request<{ updated: number }>('/api/v2/alerts/notifications/read', {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ids })
|
|
}),
|
|
retryAlertNotificationV2: (id: number, input: { expectedAttemptCount: number; reason: string; idempotencyKey: string }) => request<AlertNotificationRetryResult>(`/api/v2/alerts/notifications/${encodeURIComponent(id)}/retry`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
alertNotificationRetryAuditsV2: (id: number, signal?: AbortSignal) => request<AlertNotificationRetryAudit[]>(`/api/v2/alerts/notifications/${encodeURIComponent(id)}/retry-audit`, withSignal(undefined, signal)),
|
|
dashboardSummary: () => request<DashboardSummary>('/api/dashboard/summary'),
|
|
vehicles: (params = new URLSearchParams(), signal?: AbortSignal) => request<Page<VehicleRow>>(`/api/vehicles?${params.toString()}`, signal ? { signal } : undefined),
|
|
vehicleResolve: (params = new URLSearchParams()) => request<VehicleIdentityResolution>(`/api/vehicles/resolve?${params.toString()}`),
|
|
vehicleCoverage: (params = new URLSearchParams(), signal?: AbortSignal) => request<Page<VehicleCoverageRow>>(`/api/vehicles/coverage?${params.toString()}`, signal ? { signal } : undefined),
|
|
vehicleCoverageSummary: (params = new URLSearchParams()) => request<VehicleCoverageSummary>(`/api/vehicles/coverage/summary?${params.toString()}`),
|
|
vehicleBusinessFilters: (signal?: AbortSignal) => request<VehicleBusinessFilters>('/api/vehicles/business-filters', withSignal(undefined, signal)),
|
|
vehicleDetail: (params = new URLSearchParams(), signal?: AbortSignal) => request<VehicleDetail>(`/api/vehicle-service?${params.toString()}`, withSignal(undefined, signal)),
|
|
vehicleProfile: (vin: string) => request<VehicleProfile>(`/api/v2/vehicles/${encodeURIComponent(vin)}/profile`),
|
|
latestTelemetry: (vin: string, signal?: AbortSignal) => request<LatestTelemetryResponse>(`/api/v2/vehicles/${encodeURIComponent(vin)}/telemetry/latest`, withSignal(undefined, signal)),
|
|
vehicleSourceEvidence: (vin: string, date?: string, signal?: AbortSignal) => {
|
|
const params = new URLSearchParams();
|
|
if (date) params.set('date', date);
|
|
const suffix = params.toString() ? `?${params.toString()}` : '';
|
|
return request<VehicleSourceEvidence>(`/api/v2/vehicles/${encodeURIComponent(vin)}/source-evidence${suffix}`, withSignal(undefined, signal));
|
|
},
|
|
vehicleSourceDiagnostic: (vin: string, signal?: AbortSignal) => request<VehicleSourceDiagnostic>(
|
|
`/api/v2/operations/vehicles/${encodeURIComponent(vin)}/sources`,
|
|
withSignal(undefined, signal)
|
|
),
|
|
updateVehicleSourcePolicy: (vin: string, update: VehicleSourcePolicyUpdate) => request<VehicleSourceDiagnostic>(
|
|
`/api/v2/operations/vehicles/${encodeURIComponent(vin)}/sources/${encodeURIComponent(update.sourceRef)}`,
|
|
{
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
version: update.version,
|
|
providerName: update.providerName,
|
|
providerEvidence: update.providerEvidence,
|
|
enabled: update.enabled,
|
|
priority: update.priority,
|
|
remark: update.remark
|
|
})
|
|
}
|
|
),
|
|
reconciliationSummary: (days = 30, signal?: AbortSignal) => request<ReconciliationSummary>(
|
|
`/api/v2/reconciliation/summary?days=${days}`,
|
|
withSignal(undefined, signal)
|
|
),
|
|
reconciliationIssues: (query: ReconciliationQuery, signal?: AbortSignal) => request<Page<ReconciliationIssue>>(
|
|
'/api/v2/reconciliation/issues',
|
|
withSignal({ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query) }, signal)
|
|
),
|
|
reconciliationAssignees: (search = '', signal?: AbortSignal) => request<ReconciliationAssignee[]>(
|
|
`/api/v2/reconciliation/assignees?${new URLSearchParams(search ? { search } : {}).toString()}`,
|
|
withSignal(undefined, signal)
|
|
),
|
|
downloadReconciliationIssues: (query: ReconciliationQuery, signal?: AbortSignal) => requestBlob(
|
|
'/api/v2/reconciliation/issues/export',
|
|
signal,
|
|
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query) },
|
|
'quality-issues.csv'
|
|
),
|
|
reconciliationIssue: (id: string, signal?: AbortSignal) => request<ReconciliationIssue>(
|
|
`/api/v2/reconciliation/issues/${encodeURIComponent(id)}`,
|
|
withSignal(undefined, signal)
|
|
),
|
|
updateReconciliationIssue: (id: string, input: { version: number; status: string; note: string }) => request<ReconciliationIssue>(
|
|
`/api/v2/reconciliation/issues/${encodeURIComponent(id)}/actions`,
|
|
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input) }
|
|
),
|
|
batchUpdateReconciliationIssues: (input: ReconciliationBatchActionRequest) => request<ReconciliationBatchActionResult>(
|
|
'/api/v2/reconciliation/issues/batch-actions',
|
|
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input) }
|
|
),
|
|
assignReconciliationIssue: (id: string, input: ReconciliationAssignmentRequest) => request<ReconciliationIssue>(
|
|
`/api/v2/reconciliation/issues/${encodeURIComponent(id)}/assignment`,
|
|
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input) }
|
|
),
|
|
batchAssignReconciliationIssues: (input: ReconciliationBatchAssignmentRequest) => request<ReconciliationBatchActionResult>(
|
|
'/api/v2/reconciliation/issues/batch-assignments',
|
|
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input) }
|
|
),
|
|
setReconciliationIssueArchived: (id: string, archived: boolean, input: ReconciliationLifecycleRequest) => request<ReconciliationIssue>(
|
|
`/api/v2/reconciliation/issues/${encodeURIComponent(id)}/${archived ? 'archive' : 'restore'}`,
|
|
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input) }
|
|
),
|
|
batchSetReconciliationIssuesArchived: (archived: boolean, input: ReconciliationBatchLifecycleRequest) => request<ReconciliationBatchActionResult>(
|
|
`/api/v2/reconciliation/issues/${archived ? 'batch-archive' : 'batch-restore'}`,
|
|
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input) }
|
|
),
|
|
updateVehicleProfile: (vin: string, input: VehicleProfileInput) => request<VehicleProfile>(`/api/v2/vehicles/${encodeURIComponent(vin)}/profile`, {
|
|
method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}),
|
|
syncVehicleProfiles: (input: VehicleProfileSyncRequest, signal?: AbortSignal) => request<VehicleProfileSyncResult>('/api/v2/vehicle-profiles/sync', withSignal({
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(input)
|
|
}, signal)),
|
|
vehicleServiceSummary: () => request<VehicleServiceSummary>('/api/vehicle-service/summary'),
|
|
vehicleServiceOverview: (params = new URLSearchParams()) => request<VehicleServiceOverview>(`/api/vehicle-service/overview?${params.toString()}`),
|
|
vehicleServiceOverviews: (query: VehicleOverviewBatchQuery) => request<Page<VehicleServiceOverview>>('/api/vehicle-service/overviews', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(query)
|
|
}),
|
|
vehicleRealtime: (params = new URLSearchParams(), signal?: AbortSignal) => request<Page<VehicleRealtimeRow>>(`/api/realtime/vehicles?${params.toString()}`, signal ? { signal } : undefined),
|
|
realtimeLocations: (params = new URLSearchParams()) => request<Page<RealtimeLocationRow>>(`/api/realtime/locations?${params.toString()}`),
|
|
historyLocations: (params = new URLSearchParams()) => request<Page<HistoryLocationRow>>(`/api/history/locations?${params.toString()}`),
|
|
rawFrames: (params = new URLSearchParams()) => request<Page<RawFrameRow>>(`/api/history/raw-frames?${params.toString()}`),
|
|
rawFramesQuery: (query: RawFrameQuery) => request<Page<RawFrameRow>>('/api/history/raw-frames/query', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(query)
|
|
}),
|
|
mileageSummary: (params = new URLSearchParams()) => request<MileageSummary>(`/api/mileage/summary?${params.toString()}`),
|
|
dailyMileage: (query: MileageQuery, signal?: AbortSignal) => request<Page<DailyMileageRow>>('/api/mileage/daily', withSignal({
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
|
|
}, signal)),
|
|
hydrogenDailyEvidence: (vin: string, date: string, signal?: AbortSignal) => request<HydrogenDailyEvidence>(
|
|
`/api/v2/vehicles/${encodeURIComponent(vin)}/hydrogen-evidence?date=${encodeURIComponent(date)}`,
|
|
withSignal(undefined, signal)
|
|
),
|
|
mileageStatistics: (query: MileageQuery, signal?: AbortSignal) => request<MileageStatistics>('/api/v2/statistics/mileage', withSignal({
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query)
|
|
}, signal)),
|
|
onlineStatisticsSummary: (params = new URLSearchParams()) => request<OnlineStatisticsSummary>(`/api/statistics/online-summary?${params.toString()}`),
|
|
onlineVehicleStatuses: (params = new URLSearchParams()) => request<Page<OnlineVehicleStatusRow>>(`/api/statistics/online-vehicles?${params.toString()}`),
|
|
qualitySummary: (params = new URLSearchParams()) => request<QualitySummary>(`/api/quality/summary?${params.toString()}`),
|
|
qualityIssues: (params = new URLSearchParams()) => request<Page<QualityIssueRow>>(`/api/quality/issues?${params.toString()}`),
|
|
qualityNotificationPlan: (params = new URLSearchParams()) => request<QualityNotificationPlan>(`/api/quality/notification-plan?${params.toString()}`),
|
|
alertEventSummary: (params = new URLSearchParams()) => request<QualitySummary>(`/api/alert-events/summary?${params.toString()}`),
|
|
alertEvents: (params = new URLSearchParams()) => request<Page<QualityIssueRow>>(`/api/alert-events?${params.toString()}`),
|
|
alertEventNotificationPlan: (params = new URLSearchParams()) => request<QualityNotificationPlan>(`/api/alert-events/notification-plan?${params.toString()}`),
|
|
reverseGeocode: (params = new URLSearchParams(), signal?: AbortSignal) => request<MapReverseGeocode>(`/api/map/reverse-geocode?${params.toString()}`, withSignal(undefined, signal)),
|
|
opsHealth: (signal?: AbortSignal) => request<OpsHealth>('/api/ops/health', withSignal(undefined, signal)).then(normalizeOpsHealth),
|
|
sourceReadiness: (signal?: AbortSignal) => request<SourceReadinessPlan>('/api/ops/source-readiness', withSignal(undefined, signal)).then(normalizeSourceReadiness)
|
|
};
|