Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
6c27453
feat: add custom analysis pipelines and performance optimizations
MaykThewessen Apr 6, 2026
ec79c6f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 6, 2026
2d756c5
fix: safe bus country lookups and cross-border AC line support
MaykThewessen Apr 6, 2026
a3400f6
feat: add nodal_balance and line_flow_snapshot analysis types (closes…
MaykThewessen Apr 6, 2026
2fc73e4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 6, 2026
dcf44de
feat: add network component data browser with paginated API
MaykThewessen Apr 6, 2026
3f6fd6c
feat: add inline editing for network component data
MaykThewessen Apr 6, 2026
8875398
feat: add saved dashboard views with CRUD API and frontend UI
MaykThewessen Apr 6, 2026
2b87e68
feat: add interactive Leaflet map showing network buses and branches
MaykThewessen Apr 6, 2026
23db4ff
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 6, 2026
9b06be1
feat: add network sharing with specific users
MaykThewessen Apr 6, 2026
c801fd8
feat: add New Run creation dialog for self-service workflow submission
MaykThewessen Apr 6, 2026
56c73be
fix: correct export method and add non-admin user search for sharing
MaykThewessen Apr 6, 2026
f4d014e
feat: polish map with carrier/country coloring, legend, and rich popups
MaykThewessen Apr 6, 2026
f08f841
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Apr 6, 2026
46bb6ab
style: fix ruff lint errors (type annotations, line length, imports)
MaykThewessen Apr 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions frontend/app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions frontend/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@sveltejs/vite-plugin-svelte": "^6.2.4",
"@tailwindcss/vite": "^4.2.1",
"@tanstack/table-core": "^8.21.3",
"@types/leaflet": "^1.9.21",
"bits-ui": "^2.16.2",
"clsx": "^2.1.1",
"svelte": "^5.53.6",
Expand All @@ -35,6 +36,7 @@
"@tanstack/svelte-table": "^9.0.0-alpha.10",
"class-variance-authority": "^0.7.1",
"elkjs": "^0.11.1",
"leaflet": "^1.9.4",
"lucide-react": "^0.575.0",
"lucide-svelte": "^0.553.0",
"mode-watcher": "^1.1.0",
Expand Down
116 changes: 116 additions & 0 deletions frontend/app/src/lib/api/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {
User,
Network,
NetworkShareResponse,
Run,
RunSummary,
Backend,
Expand All @@ -18,6 +19,13 @@ import type {
Visibility,
PaginatedResponse,
Workflow,
ComponentListResponse,
ComponentDataResponse,
ComponentTimeseriesResponse,
SavedView,
SavedViewListResponse,
ViewConfig,
MapDataResponse,
} from "$lib/types.js";

const API_BASE = '/api/v1';
Expand Down Expand Up @@ -127,11 +135,87 @@ export const networks = {
async delete(id: string): Promise<void> {
return request<void>(`/networks/${id}`, { method: 'DELETE' });
},
async searchUsers(q: string): Promise<{ id: string; username: string; avatar_url?: string }[]> {
return request<{ id: string; username: string; avatar_url?: string }[]>(
`/networks/users/search?q=${encodeURIComponent(q)}`
);
},
async getShares(id: string): Promise<NetworkShareResponse> {
return request<NetworkShareResponse>(`/networks/${id}/shares`);
},
async shareWith(id: string, userId: string): Promise<NetworkShareResponse> {
return request<NetworkShareResponse>(`/networks/${id}/shares`, {
method: 'POST',
body: JSON.stringify({ user_id: userId })
});
},
async unshare(id: string, userId: string): Promise<NetworkShareResponse> {
return request<NetworkShareResponse>(`/networks/${id}/shares/${userId}`, { method: 'DELETE' });
},
async updateVisibility(id: string, visibility: Visibility): Promise<Network> {
return request<Network>(`/networks/${id}`, {
method: 'PATCH',
body: JSON.stringify({ visibility })
});
},
async getComponents(id: string): Promise<ComponentListResponse> {
return request<ComponentListResponse>(`/networks/${id}/components`, {}, `components-${id}`);
},
async getComponentData(
id: string,
componentName: string,
params: { skip?: number; limit?: number; sort_by?: string; sort_desc?: boolean; search?: string } = {}
): Promise<ComponentDataResponse> {
const searchParams = new URLSearchParams();
if (params.skip !== undefined) searchParams.set('skip', String(params.skip));
if (params.limit !== undefined) searchParams.set('limit', String(params.limit));
if (params.sort_by) searchParams.set('sort_by', params.sort_by);
if (params.sort_desc) searchParams.set('sort_desc', 'true');
if (params.search) searchParams.set('search', params.search);
const qs = searchParams.toString();
return request<ComponentDataResponse>(
`/networks/${id}/components/${componentName}${qs ? '?' + qs : ''}`,
{},
`component-data-${id}-${componentName}`
);
},
async getComponentTimeseries(
id: string,
componentName: string,
attr: string,
params: { skip?: number; limit?: number } = {}
): Promise<ComponentTimeseriesResponse> {
const searchParams = new URLSearchParams();
if (params.skip !== undefined) searchParams.set('skip', String(params.skip));
if (params.limit !== undefined) searchParams.set('limit', String(params.limit));
const qs = searchParams.toString();
return request<ComponentTimeseriesResponse>(
`/networks/${id}/components/${componentName}/timeseries/${attr}${qs ? '?' + qs : ''}`,
{},
`component-ts-${id}-${componentName}-${attr}`
);
},
async getMapData(id: string, carriers?: string[]): Promise<MapDataResponse> {
const params = new URLSearchParams();
if (carriers && carriers.length > 0) {
carriers.forEach(c => params.append('carriers', c));
}
const qs = params.toString();
return request<MapDataResponse>(
`/networks/${id}/map${qs ? '?' + qs : ''}`,
{},
`map-${id}`
);
},
async updateComponentData(
id: string,
componentName: string,
updates: Record<string, Record<string, unknown>>
): Promise<{ message: string }> {
return request<{ message: string }>(`/networks/${id}/components/${componentName}`, {
method: 'PATCH',
body: JSON.stringify({ updates })
});
}
};

Expand Down Expand Up @@ -299,6 +383,38 @@ export const runs = {
}
};

// Views API
export const savedViews = {
async list(networkId?: string, skip = 0, limit = 50): Promise<SavedViewListResponse> {
const params = new URLSearchParams({ skip: String(skip), limit: String(limit) });
if (networkId) params.set('network_id', networkId);
return request<SavedViewListResponse>(`/views/?${params}`);
},
async get(id: string): Promise<SavedView> {
return request<SavedView>(`/views/${id}`);
},
async create(body: {
name: string;
description?: string;
network_id?: string;
visibility?: string;
config: ViewConfig;
}): Promise<SavedView> {
return request<SavedView>('/views/', { method: 'POST', body: JSON.stringify(body) });
},
async update(id: string, body: {
name?: string;
description?: string;
visibility?: string;
config?: ViewConfig;
}): Promise<SavedView> {
return request<SavedView>(`/views/${id}`, { method: 'PATCH', body: JSON.stringify(body) });
},
async delete(id: string): Promise<void> {
return request<void>(`/views/${id}`, { method: 'DELETE' });
}
};

// Cache API
export const cache = {
async clearNetwork(networkId: string): Promise<void> {
Expand Down
105 changes: 105 additions & 0 deletions frontend/app/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface Network {
file_size?: number;
visibility: Visibility;
owner: User;
shared_with?: User[];
source_run_id?: string;
dimensions?: Record<string, number>;
dimensions_count?: number;
Expand All @@ -52,6 +53,11 @@ export interface Network {
updated_at?: string;
}

export interface NetworkShareResponse {
network_id: string;
shared_with: User[];
}

export type Visibility = "public" | "private";

export interface BackendPublic {
Expand Down Expand Up @@ -267,6 +273,105 @@ export interface Workflow {
errors: WorkflowError[];
}

// Component types

export interface ComponentSummary {
name: string;
list_name: string;
count: number;
category: string | null;
attrs: string[];
has_dynamic: boolean;
dynamic_attrs: string[];
}

export interface ComponentListResponse {
components: ComponentSummary[];
total_components: number;
}

export interface ComponentDataResponse {
component: string;
columns: string[];
index: string[];
data: (string | number | boolean | null)[][];
dtypes: Record<string, string>;
total: number;
skip: number;
limit: number;
}

export interface ComponentTimeseriesResponse {
component: string;
attr: string;
columns: string[];
index: string[];
data: (number | null)[][];
total_snapshots: number;
skip: number;
limit: number;
}

// Map types

export interface GeoJSONFeature {
type: 'Feature';
geometry: {
type: 'Point' | 'LineString';
coordinates: number[] | number[][];
};
properties: Record<string, unknown>;
}

export interface GeoJSONFeatureCollection {
type: 'FeatureCollection';
features: GeoJSONFeature[];
}

export interface MapDataResponse {
buses: GeoJSONFeatureCollection;
branches: GeoJSONFeatureCollection;
bounds: { southwest: [number, number]; northeast: [number, number] } | null;
total_buses: number;
total_branches: number;
carrier_colors: Record<string, string>;
country_colors: Record<string, string>;
}

// Saved view types

export interface ViewConfig {
active_tab?: string;
statistic?: string;
plot_type?: string;
selected_carriers: string[];
selected_countries: string[];
individual_plots: boolean;
analysis_type?: string;
analysis_parameters: Record<string, unknown>;
selected_component?: string;
component_columns?: string[];
compare_network_ids: string[];
extra: Record<string, unknown>;
}

export interface SavedView {
id: string;
name: string;
description?: string;
network_id?: string;
visibility: Visibility;
config: ViewConfig;
owner: User;
created_at?: string;
updated_at?: string;
}

export interface SavedViewListResponse {
data: SavedView[];
total: number;
}

// API error type

export interface ApiError extends Error {
Expand Down
Loading