feat(monorepo): restructure web components and update configurations
CI / changes (push) Successful in 10s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 27s
CI / web (push) Successful in 38s
CI / go (push) Successful in 2m36s
CI / bird2 (push) Successful in 15s
CI / release (push) Failing after 3m7s
CI / changes (push) Successful in 10s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 27s
CI / web (push) Successful in 38s
CI / go (push) Successful in 2m36s
CI / bird2 (push) Successful in 15s
CI / release (push) Failing after 3m7s
Refactored the project structure to support a monorepo setup, moving the web application to `apps/web/` and updating related configurations. Adjusted pre-commit hooks to use `pnpm` for linting and formatting. Updated CI workflows to reflect the new directory structure and dependencies. Removed legacy files and configurations from the previous `web/` directory, streamlining the project for better maintainability and clarity.
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "@evobgp/shared",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./types/*": "./src/types/*",
|
||||
"./contracts/*": "./src/contracts/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^4.4.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const pageSchema = <T extends z.ZodTypeAny>(itemSchema: T) =>
|
||||
z.object({
|
||||
items: z.array(itemSchema),
|
||||
next_cursor: z.string().nullable(),
|
||||
has_more: z.boolean()
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const moduleTypeSchema = z.enum(['AS_PREFIXES', 'CDN_CIDRS', 'DOMAINS', 'IP_RANGES']);
|
||||
|
||||
export const dohResolverPolicySchema = z.enum(['primary_only', 'failover', 'union']);
|
||||
|
||||
export const moduleRowSchema = z.object({
|
||||
id: z.string(),
|
||||
type: moduleTypeSchema,
|
||||
name: z.string().min(1),
|
||||
enabled: z.boolean(),
|
||||
priority: z.number().int(),
|
||||
refresh_interval_sec: z.number().int().nullable(),
|
||||
cron_expr: z.string().nullable(),
|
||||
default_community_id: z.string().nullable(),
|
||||
doh_profile_id: z.string().nullable(),
|
||||
doh_profile_ids: z.array(z.string()),
|
||||
doh_resolver_policy: dohResolverPolicySchema,
|
||||
last_refreshed_at: z.string().nullable()
|
||||
});
|
||||
|
||||
export const moduleCreateSchema = z.object({
|
||||
type: moduleTypeSchema,
|
||||
name: z.string().min(1, 'Укажите название'),
|
||||
enabled: z.boolean().optional(),
|
||||
priority: z.number().int().optional(),
|
||||
doh_profile_id: z.string().nullable().optional(),
|
||||
doh_profile_ids: z.array(z.string()).optional(),
|
||||
doh_resolver_policy: dohResolverPolicySchema.optional(),
|
||||
refresh_interval_sec: z.number().int().nullable().optional(),
|
||||
cron_expr: z.string().nullable().optional(),
|
||||
default_community_id: z.string().nullable().optional()
|
||||
});
|
||||
|
||||
export const modulePatchSchema = moduleCreateSchema.omit({ type: true }).partial();
|
||||
|
||||
export type ModuleCreateInput = z.infer<typeof moduleCreateSchema>;
|
||||
export type ModulePatchInput = z.infer<typeof modulePatchSchema>;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './types/api.js';
|
||||
export * from './contracts/modules.js';
|
||||
@@ -0,0 +1,340 @@
|
||||
// ---- Pagination ----
|
||||
export type Page<T> = {
|
||||
items: T[];
|
||||
next_cursor: string | null;
|
||||
has_more: boolean;
|
||||
};
|
||||
|
||||
// ---- Modules ----
|
||||
export type ModuleType = 'AS_PREFIXES' | 'CDN_CIDRS' | 'DOMAINS' | 'IP_RANGES';
|
||||
|
||||
export type DohResolverPolicy = 'primary_only' | 'failover' | 'union';
|
||||
|
||||
export type ModuleRow = {
|
||||
id: string;
|
||||
type: ModuleType;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
priority: number;
|
||||
refresh_interval_sec: number | null;
|
||||
cron_expr: string | null;
|
||||
default_community_id: string | null;
|
||||
/** @deprecated use doh_profile_ids */
|
||||
doh_profile_id: string | null;
|
||||
doh_profile_ids: string[];
|
||||
doh_resolver_policy: DohResolverPolicy;
|
||||
last_refreshed_at: string | null;
|
||||
};
|
||||
export type ModulesResponse = Page<ModuleRow>;
|
||||
|
||||
export type RouterListsCatalogResponse = {
|
||||
modules: { items: ModuleRow[] };
|
||||
domains: { items: { module_id: string; entry: DomainEntry }[] };
|
||||
asns: { items: { module_id: string; entry: AsEntry }[] };
|
||||
ip_ranges: { items: { module_id: string; entry: IpRangeEntry }[] };
|
||||
communities: { items: BgpCommunity[] };
|
||||
};
|
||||
|
||||
export type ModuleCreate = {
|
||||
type: ModuleType;
|
||||
name: string;
|
||||
enabled?: boolean;
|
||||
priority?: number;
|
||||
doh_profile_id?: string | null;
|
||||
doh_profile_ids?: string[];
|
||||
doh_resolver_policy?: DohResolverPolicy;
|
||||
refresh_interval_sec?: number | null;
|
||||
cron_expr?: string | null;
|
||||
default_community_id?: string | null;
|
||||
};
|
||||
export type ModulePatch = Partial<Omit<ModuleCreate, 'type'>>;
|
||||
|
||||
// ---- AS Entries ----
|
||||
export type AsEntry = {
|
||||
id: string;
|
||||
asn: number;
|
||||
community_id: string | null;
|
||||
/** Имя/держатель AS (RIPEstat), после успешного обновления модуля */
|
||||
asn_name?: string | null;
|
||||
/** Число объявленных префиксов на момент последнего резолва */
|
||||
prefix_count?: number | null;
|
||||
/** ISO-время последнего успешного резолва ASN */
|
||||
asn_resolved_at?: string | null;
|
||||
};
|
||||
export type AsEntryCreate = {
|
||||
asn: number;
|
||||
community_id?: string | null;
|
||||
};
|
||||
export type AsEntryPatch = {
|
||||
asn?: number;
|
||||
community_id?: string | null;
|
||||
};
|
||||
export type AsEntriesResponse = Page<AsEntry>;
|
||||
|
||||
// ---- CDN Sources ----
|
||||
export type CdnSource = {
|
||||
id: string;
|
||||
url: string;
|
||||
source_kind: string;
|
||||
prefix_path: string;
|
||||
community_id: string | null;
|
||||
refresh_interval_sec: number | null;
|
||||
last_refreshed_at: string | null;
|
||||
};
|
||||
export type CdnSourceCreate = {
|
||||
url: string;
|
||||
source_kind: string;
|
||||
prefix_path?: string;
|
||||
community_id?: string | null;
|
||||
};
|
||||
export type CdnSourcePatch = Partial<CdnSourceCreate> & { refresh_interval_sec?: number | null };
|
||||
export type CdnSourcesResponse = Page<CdnSource>;
|
||||
|
||||
export type CdnPreviewResponse = {
|
||||
items: string[];
|
||||
total: number;
|
||||
truncated: boolean;
|
||||
source_url: string;
|
||||
};
|
||||
|
||||
// ---- Domain Entries ----
|
||||
export type DomainEntry = {
|
||||
id: string;
|
||||
fqdn: string;
|
||||
community_id: string | null;
|
||||
};
|
||||
export type DomainEntryCreate = {
|
||||
fqdn: string;
|
||||
community_id?: string | null;
|
||||
};
|
||||
export type DomainEntriesResponse = Page<DomainEntry>;
|
||||
|
||||
// ---- IP Range Entries ----
|
||||
export type IpRangeEntry = {
|
||||
id: string;
|
||||
prefix: string;
|
||||
community_id: string;
|
||||
};
|
||||
export type IpRangeEntryCreate = {
|
||||
prefix: string;
|
||||
community_id: string;
|
||||
};
|
||||
export type IpRangeEntriesResponse = Page<IpRangeEntry>;
|
||||
|
||||
// ---- DoH Profiles ----
|
||||
export type DohProfile = {
|
||||
id: string;
|
||||
name?: string;
|
||||
url: string;
|
||||
timeout_ms: number | null;
|
||||
vault_secret_ref: string | null;
|
||||
};
|
||||
export type DohProfileCreate = {
|
||||
name?: string;
|
||||
url: string;
|
||||
timeout_ms?: number | null;
|
||||
vault_secret_ref?: string | null;
|
||||
};
|
||||
export type DohProfilePatch = Partial<DohProfileCreate>;
|
||||
export type DohProfilesResponse = Page<DohProfile>;
|
||||
|
||||
// ---- Communities ----
|
||||
export type BgpCommunity = {
|
||||
id: string;
|
||||
community: string;
|
||||
title: string;
|
||||
};
|
||||
export type BgpCommunityCreate = {
|
||||
community: string;
|
||||
title?: string;
|
||||
};
|
||||
export type BgpCommunityPatch = Partial<BgpCommunityCreate>;
|
||||
export type CommunitiesResponse = Page<BgpCommunity>;
|
||||
|
||||
// ---- Peers ----
|
||||
export type PeerSessionOnSpeaker = {
|
||||
speaker_id: string;
|
||||
label: string;
|
||||
state: string;
|
||||
poll_error?: string;
|
||||
};
|
||||
|
||||
export type PeerRow = {
|
||||
id: string;
|
||||
name?: string;
|
||||
neighbor: string;
|
||||
remote_asn?: number;
|
||||
enabled?: boolean;
|
||||
session_state: string;
|
||||
bgp_speaker_id: string | null;
|
||||
connected_speaker_id?: string | null;
|
||||
connected_speaker_label?: string;
|
||||
session_on_speakers?: PeerSessionOnSpeaker[];
|
||||
established_on_speakers?: PeerSessionOnSpeaker[];
|
||||
session_mismatch?: boolean;
|
||||
};
|
||||
export type LiveSpeakerPoll = {
|
||||
speaker_id: string;
|
||||
label: string;
|
||||
ok: boolean;
|
||||
session_count: number;
|
||||
poll_error?: string;
|
||||
};
|
||||
export type PeersResponse = Page<PeerRow> & { live_speaker_poll?: LiveSpeakerPoll[] };
|
||||
export type BgpPeerCreate = {
|
||||
name?: string;
|
||||
neighbor: string;
|
||||
remote_asn: number;
|
||||
bgp_speaker_id?: string | null;
|
||||
enabled?: boolean;
|
||||
};
|
||||
export type BgpPeerPatch = Partial<BgpPeerCreate>;
|
||||
|
||||
// ---- Speakers ----
|
||||
export type BgpSessionLive = {
|
||||
name: string;
|
||||
neighbor?: string;
|
||||
state: string;
|
||||
};
|
||||
|
||||
export type SpeakerLiveStatus = {
|
||||
label?: string;
|
||||
agent_ok?: boolean;
|
||||
agent_error?: string;
|
||||
agent_last_sync_at?: string;
|
||||
agent_last_applied_revision_id?: string;
|
||||
bgp_poll_ok?: boolean;
|
||||
bgp_poll_error?: string;
|
||||
bgp_sessions_total?: number;
|
||||
bgp_established?: number;
|
||||
sessions?: BgpSessionLive[];
|
||||
};
|
||||
|
||||
export type SpeakerRow = {
|
||||
id: string;
|
||||
role: string;
|
||||
endpoint: string;
|
||||
last_applied_revision_id: string | null;
|
||||
published_revision_id?: string | null;
|
||||
published_at?: string | null;
|
||||
agent_domain?: string;
|
||||
node_ipv4?: string;
|
||||
bird_bgp_source_ipv4?: string;
|
||||
dispatch_status?: string;
|
||||
sync_status?: string;
|
||||
last_dispatch_at?: string | null;
|
||||
last_dispatch_error?: string | null;
|
||||
meta_json?: Record<string, unknown>;
|
||||
agent_secret?: string;
|
||||
live?: SpeakerLiveStatus;
|
||||
};
|
||||
export type SpeakersResponse = Page<SpeakerRow>;
|
||||
export type BgpSpeakerCreate = {
|
||||
endpoint: string;
|
||||
role?: string;
|
||||
meta_json?: string;
|
||||
};
|
||||
export type BgpSpeakerPatch = Partial<BgpSpeakerCreate>;
|
||||
|
||||
export type BundleSigningPublicKey = {
|
||||
public_key_base64: string;
|
||||
};
|
||||
|
||||
// ---- Revisions ----
|
||||
export type RevisionRow = {
|
||||
id: string;
|
||||
content_hash: string;
|
||||
created_at: string;
|
||||
parent_revision_id: string | null;
|
||||
materialized_prefix_count: number;
|
||||
module_id: string | null;
|
||||
};
|
||||
export type RevisionsResponse = Page<RevisionRow>;
|
||||
|
||||
export type RevisionPrefix = {
|
||||
/** Обычно CIDR; для AS-модуля в снимке ревизии — строка вида `as:<номер_asn>`. */
|
||||
prefix: string;
|
||||
/** Источник материализации (например, domain:<fqdn>, as:<asn>, cdn:<source_id>, ip_range). */
|
||||
source?: string;
|
||||
community_id?: string | null;
|
||||
};
|
||||
export type RevisionPrefixesResponse = Page<RevisionPrefix>;
|
||||
|
||||
export type RevisionPreview = {
|
||||
id: string;
|
||||
prefixes?: RevisionPrefix[];
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
/** Ответ GET /v1/revisions/{a}/diff/{b}: префиксы в `prefixes` (источник правды в бэкенде). */
|
||||
export type RevisionDiff = {
|
||||
revision_a?: string;
|
||||
revision_b?: string;
|
||||
prefixes?: {
|
||||
added: string[];
|
||||
removed: string[];
|
||||
unchanged_count?: number;
|
||||
};
|
||||
/** Устаревший/нормализованный вид — см. нормализацию в UI */
|
||||
added?: (string | RevisionPrefix)[];
|
||||
removed?: (string | RevisionPrefix)[];
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
// ---- BIRD (локальный birdc на хосте с API, если задан EVOBGP_BIRDC_SOCKET) ----
|
||||
export type BirdStatus = {
|
||||
birdc_configured: boolean;
|
||||
message?: string;
|
||||
error?: string;
|
||||
protocols_excerpt?: string;
|
||||
bgp_sessions_total: number;
|
||||
bgp_established: number;
|
||||
healthy: boolean | null;
|
||||
};
|
||||
|
||||
// ---- Jobs ----
|
||||
export type JobRow = {
|
||||
job_id: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
idempotency_key?: string | null;
|
||||
created_at?: string;
|
||||
started_at?: string | null;
|
||||
finished_at?: string | null;
|
||||
error?: string | null;
|
||||
meta?: Record<string, unknown>;
|
||||
};
|
||||
export type JobsResponse = Page<JobRow>;
|
||||
|
||||
// ---- Settings ----
|
||||
export type AppSettings = Record<string, unknown>;
|
||||
|
||||
// ---- Auth / API keys ----
|
||||
export type AuthSession = {
|
||||
tenant_id: string;
|
||||
role: 'viewer' | 'editor' | 'operator' | 'node';
|
||||
};
|
||||
|
||||
export type ApiKeyRole = AuthSession['role'];
|
||||
|
||||
export type ApiKey = {
|
||||
id: string;
|
||||
name: string;
|
||||
role: ApiKeyRole;
|
||||
prefix: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
expires_at: string | null;
|
||||
revoked_at: string | null;
|
||||
last_used_at: string | null;
|
||||
};
|
||||
|
||||
export type ApiKeysResponse = Page<ApiKey>;
|
||||
|
||||
export type ApiKeyCreate = {
|
||||
name: string;
|
||||
role: ApiKeyRole;
|
||||
expires_at?: string | null;
|
||||
};
|
||||
|
||||
export type ApiKeyCreated = ApiKey & { token: string };
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user