Add CA admin UI and extend admin API for the unified platform.
Deliver parsers, events, analytics, and PI management in Vue; fix Telegram session mount and map navigation to events by eventId. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import { ADMIN_BASE, request } from "./client";
|
||||
import type {
|
||||
AnalyticsSummary,
|
||||
Consumer,
|
||||
ConsumerCreate,
|
||||
ConsumerUpdate,
|
||||
EventFilters,
|
||||
EventListResponse,
|
||||
EventRecord,
|
||||
ParseJob,
|
||||
ParseJobCreate,
|
||||
TimelinePoint,
|
||||
TopItem,
|
||||
} from "../types/admin";
|
||||
|
||||
function buildQuery(params: Record<string, string | number | undefined>): string {
|
||||
const search = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined && value !== "") {
|
||||
search.set(key, String(value));
|
||||
}
|
||||
}
|
||||
const qs = search.toString();
|
||||
return qs ? `?${qs}` : "";
|
||||
}
|
||||
|
||||
export function fetchJobs(): Promise<ParseJob[]> {
|
||||
return request<ParseJob[]>("/jobs", undefined, ADMIN_BASE);
|
||||
}
|
||||
|
||||
export function createJob(payload: ParseJobCreate): Promise<ParseJob> {
|
||||
return request<ParseJob>(
|
||||
"/jobs",
|
||||
{ method: "POST", body: JSON.stringify(payload) },
|
||||
ADMIN_BASE,
|
||||
);
|
||||
}
|
||||
|
||||
export function retryJob(jobId: number): Promise<ParseJob> {
|
||||
return request<ParseJob>(
|
||||
`/jobs/${jobId}/retry`,
|
||||
{ method: "POST" },
|
||||
ADMIN_BASE,
|
||||
);
|
||||
}
|
||||
|
||||
export function fetchEvents(filters: EventFilters = {}): Promise<EventListResponse> {
|
||||
return request<EventListResponse>(
|
||||
`/events${buildQuery(filters as Record<string, string | number | undefined>)}`,
|
||||
undefined,
|
||||
ADMIN_BASE,
|
||||
);
|
||||
}
|
||||
|
||||
export function fetchConsumers(): Promise<Consumer[]> {
|
||||
return request<Consumer[]>("/consumers", undefined, ADMIN_BASE);
|
||||
}
|
||||
|
||||
export function createConsumer(payload: ConsumerCreate): Promise<Consumer> {
|
||||
return request<Consumer>(
|
||||
"/consumers",
|
||||
{ method: "POST", body: JSON.stringify(payload) },
|
||||
ADMIN_BASE,
|
||||
);
|
||||
}
|
||||
|
||||
export function updateConsumer(id: number, payload: ConsumerUpdate): Promise<Consumer> {
|
||||
return request<Consumer>(
|
||||
`/consumers/${id}`,
|
||||
{ method: "PATCH", body: JSON.stringify(payload) },
|
||||
ADMIN_BASE,
|
||||
);
|
||||
}
|
||||
|
||||
export function rotateConsumerKey(id: number): Promise<Consumer> {
|
||||
return request<Consumer>(
|
||||
`/consumers/${id}/rotate-key`,
|
||||
{ method: "POST" },
|
||||
ADMIN_BASE,
|
||||
);
|
||||
}
|
||||
|
||||
export function fetchAnalyticsSummary(): Promise<AnalyticsSummary> {
|
||||
return request<AnalyticsSummary>("/analytics/summary", undefined, ADMIN_BASE);
|
||||
}
|
||||
|
||||
export function fetchAnalyticsTimeline(days = 30): Promise<TimelinePoint[]> {
|
||||
return request<TimelinePoint[]>(
|
||||
`/analytics/timeline${buildQuery({ days })}`,
|
||||
undefined,
|
||||
ADMIN_BASE,
|
||||
);
|
||||
}
|
||||
|
||||
export function fetchTopLocalities(limit = 10): Promise<TopItem[]> {
|
||||
return request<TopItem[]>(
|
||||
`/analytics/top-localities${buildQuery({ limit })}`,
|
||||
undefined,
|
||||
ADMIN_BASE,
|
||||
);
|
||||
}
|
||||
|
||||
export function fetchTopRegions(limit = 10): Promise<TopItem[]> {
|
||||
return request<TopItem[]>(
|
||||
`/analytics/top-regions${buildQuery({ limit })}`,
|
||||
undefined,
|
||||
ADMIN_BASE,
|
||||
);
|
||||
}
|
||||
|
||||
export async function testDistribution(apiKey: string, limit = 5): Promise<EventRecord[]> {
|
||||
const response = await fetch(`/api/v1/events?limit=${limit}`, {
|
||||
headers: { Authorization: `Bearer ${apiKey}` },
|
||||
});
|
||||
if (!response.ok) {
|
||||
const message = await response.text();
|
||||
throw new Error(message || `Ошибка запроса: ${response.status}`);
|
||||
}
|
||||
return response.json() as Promise<EventRecord[]>;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
const API_BASE = "/api";
|
||||
const ADMIN_BASE = "/admin";
|
||||
|
||||
export async function request<T>(
|
||||
url: string,
|
||||
options?: RequestInit,
|
||||
base: string = API_BASE,
|
||||
): Promise<T> {
|
||||
const headers = new Headers(options?.headers);
|
||||
const isFormData = options?.body instanceof FormData;
|
||||
|
||||
if (!isFormData && !headers.has("Content-Type")) {
|
||||
headers.set("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
const response = await fetch(`${base}${url}`, {
|
||||
...options,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = await response.text();
|
||||
throw new Error(message || `Ошибка запроса: ${response.status}`);
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export { API_BASE, ADMIN_BASE };
|
||||
@@ -1,32 +1,6 @@
|
||||
import { request } from "./client";
|
||||
import type { MapObject, MapObjectCreate, MapObjectUpdate, ObjectMedia } from "../types/object";
|
||||
|
||||
const API_BASE = "/api";
|
||||
|
||||
async function request<T>(url: string, options?: RequestInit): Promise<T> {
|
||||
const headers = new Headers(options?.headers);
|
||||
const isFormData = options?.body instanceof FormData;
|
||||
|
||||
if (!isFormData && !headers.has("Content-Type")) {
|
||||
headers.set("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE}${url}`, {
|
||||
...options,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = await response.text();
|
||||
throw new Error(message || `Ошибка запроса: ${response.status}`);
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export function fetchObjects(): Promise<MapObject[]> {
|
||||
return request<MapObject[]>("/objects");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user