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

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:
Denozordec
2026-06-30 23:54:28 +07:00
parent db75126bea
commit a37c931ee7
377 changed files with 8537 additions and 1093 deletions
+14
View File
@@ -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"
}
}
+8
View File
@@ -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()
});
+38
View File
@@ -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>;
+2
View File
@@ -0,0 +1,2 @@
export * from './types/api.js';
export * from './contracts/modules.js';
+340
View File
@@ -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 };
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*"]
}