Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ updates:
interval: "weekly"
cooldown:
default-days: 7
groups:
github-actions:
patterns: ["*"]
update-types: ["minor", "patch"]

- package-ecosystem: "uv"
directory: "/"
Expand All @@ -14,6 +18,10 @@ updates:
cooldown:
default-days: 7
semver-major-days: 14
groups:
python:
patterns: ["*"]
update-types: ["minor", "patch"]

- package-ecosystem: "npm"
directory: "/frontend/app"
Expand All @@ -22,10 +30,18 @@ updates:
cooldown:
default-days: 7
semver-major-days: 14
groups:
npm:
patterns: ["*"]
update-types: ["minor", "patch"]

- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
cooldown:
default-days: 7
groups:
docker:
patterns: ["*"]
update-types: ["minor", "patch"]
2 changes: 1 addition & 1 deletion .github/workflows/release-docker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
fetch-depth: 0 # Fetch tags for setuptools_scm

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0

- name: Log in to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
Expand Down
19 changes: 1 addition & 18 deletions .github/workflows/release-pypi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,7 @@ jobs:
git merge-base --is-ancestor "$GITHUB_SHA" origin/main \
|| { echo "Tag $GITHUB_REF_NAME is not on main"; exit 1; }

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: frontend/app/package-lock.json

- name: Build SvelteKit frontend
working-directory: frontend/app
run: |
npm ci
npm run build

- uses: hynek/build-and-inspect-python-package@v2

- name: Verify frontend is bundled in the wheel
run: |
unzip -l dist/pypsa_app-*.whl | grep -q "static/app/index.html" \
|| { echo "wheel missing built frontend (static/app/index.html)"; exit 1; }
- uses: ./.github/actions/build-package

release:
name: Create GitHub release
Expand Down
8 changes: 0 additions & 8 deletions frontend/app/src/lib/components/AppSidebar.svelte
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { version } from '$lib/api/client.js';
import { authStore } from '$lib/stores/auth.svelte.js';
import NavMain from './sidebar/NavMain.svelte';
import NavAdmin from './sidebar/NavAdmin.svelte';
import NavUser from './sidebar/NavUser.svelte';
import NavNetworksList from './sidebar/NavNetworksList.svelte';
import * as Sidebar from '$lib/components/ui/sidebar';
import * as Tooltip from '$lib/components/ui/tooltip';
import Badge from '$lib/components/ui/badge/badge.svelte';

// Match network detail page + any nested sub-route (e.g. /data, /report/foo).
const isNetworkPage = $derived(/^\/networks\/[^/]+(\/.*)?$/.test($page.url.pathname));

// Version info
interface VersionData {
app: string;
Expand Down Expand Up @@ -93,9 +88,6 @@
<Sidebar.Content class="flex flex-col overflow-hidden">
<NavMain />
<NavAdmin />
{#if isNetworkPage}
<NavNetworksList />
{/if}
</Sidebar.Content>

{#if !authStore.loading && authStore.isAuthenticated}
Expand Down
53 changes: 0 additions & 53 deletions frontend/app/src/lib/components/sidebar/NavNetworksList.svelte

This file was deleted.

1 change: 1 addition & 0 deletions frontend/app/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export interface RunNetwork {
id: string;
name: string | null;
filename: string;
source_path?: string | null;
}

export interface UserPublic {
Expand Down
13 changes: 0 additions & 13 deletions frontend/app/src/routes/runs/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -479,19 +479,6 @@ const workflowDisplay = $derived.by(() => {
</div>
{/if}
{/snippet}
{#snippet networks()}
{#if run && run.networks.length > 0}
<div class="h-4 w-px bg-border"></div>
<div class="flex items-center gap-1.5">
{#each run.networks as network, i}
{#if i > 0}<span>,</span>{/if}
<a href="/networks/{network.id}" class="underline hover:text-foreground">
{network.filename}
</a>
{/each}
</div>
{/if}
{/snippet}
</RunHeader>

<!-- Workflow -->
Expand Down
34 changes: 21 additions & 13 deletions frontend/app/src/routes/runs/components/RunHeader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import Clock from '@lucide/svelte/icons/clock';
import Calendar from '@lucide/svelte/icons/calendar';
import Server from '@lucide/svelte/icons/server';
import Network from '@lucide/svelte/icons/network';
import ChevronRight from '@lucide/svelte/icons/chevron-right';

interface RunLike {
id: string;
Expand All @@ -28,7 +30,6 @@
progress?: { total: number; done: number; pct: number } | null;
actions?: Snippet;
extraChips?: Snippet;
networks?: Snippet;
}

let {
Expand All @@ -40,7 +41,6 @@
progress = null,
actions,
extraChips,
networks,
}: Props = $props();
</script>

Expand Down Expand Up @@ -97,17 +97,6 @@
{#if extraChips}
{@render extraChips()}
{/if}
{#if networks}
{@render networks()}
{:else if run.networks.length > 0}
<div class="h-4 w-px bg-border"></div>
<div class="flex items-center gap-1.5">
{#each run.networks as network, i}
{#if i > 0}<span>,</span>{/if}
<span>{network.name || network.id.slice(0, 8)}</span>
{/each}
</div>
{/if}
{#if run.owner}
<div class="flex items-center gap-1.5 ml-auto">
{#if run.owner.avatar_url}
Expand All @@ -118,6 +107,25 @@
{/if}
</div>

<!-- Row 3: Output networks list -->
{#if run.networks.length > 0}
<div class="mt-4">
<p class="text-[10px] uppercase tracking-wider font-medium text-muted-foreground mb-2">Output networks</p>
<div class="flex flex-col">
{#each run.networks as network}
<a
href="/networks/{network.id}"
class="flex items-center gap-1.5 -mx-1.5 px-1.5 py-0.5 rounded hover:bg-accent transition-colors"
>
<Network class="h-3 w-3 text-muted-foreground shrink-0" />
<span class="font-mono text-xs flex-1 truncate">{network.source_path ?? network.filename}</span>
<ChevronRight class="h-3 w-3 text-muted-foreground shrink-0" />
</a>
{/each}
</div>
</div>
{/if}

<!-- Progress bar -->
{#if !isTerminal && progress}
<div class="mt-4">
Expand Down
5 changes: 5 additions & 0 deletions src/pypsa_app/backend/alembic/versions/0001_initial_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,8 @@ def downgrade() -> None:
op.drop_table("user_oauth_providers")
op.drop_table("users")
op.drop_table("snakedispatch_backends")

# Postgres: drop ENUM types so re-upgrade does not hit DuplicateObject
if op.get_bind().dialect.name == "postgresql":
for enum_name in ("network_visibility", "run_status", "user_role"):
op.execute(f"DROP TYPE IF EXISTS {enum_name}")
29 changes: 29 additions & 0 deletions src/pypsa_app/backend/alembic/versions/0006_network_source_path.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Add source_path column to networks table.

Revision ID: 0006
Revises: 0005
Create Date: 2026-05-13

"""

from collections.abc import Sequence

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision: str = "0006"
down_revision: str | None = "0005"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
op.add_column(
"networks",
sa.Column("source_path", sa.Text(), nullable=True),
)


def downgrade() -> None:
op.drop_column("networks", "source_path")
1 change: 1 addition & 0 deletions src/pypsa_app/backend/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ class Network(Base):
# File information
filename: Mapped[str] = mapped_column(String(255))
file_path: Mapped[str] = mapped_column(Text, unique=True, index=True)
source_path: Mapped[str | None] = mapped_column(Text)
file_size: Mapped[int | None] = mapped_column(BigInteger)
file_hash: Mapped[str | None] = mapped_column(String(64))
# External: file lives outside data_dir (LOCAL_MODE only); do not unlink on delete
Expand Down
1 change: 1 addition & 0 deletions src/pypsa_app/backend/schemas/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class RunNetworkSummary(BaseModel):
id: uuid.UUID
name: str | None = None
filename: str
source_path: str | None = None


class RunCreate(BaseModel):
Expand Down
2 changes: 2 additions & 0 deletions src/pypsa_app/backend/services/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ def import_network_file( # noqa: PLR0913
visibility: Visibility = Visibility.PRIVATE,
*,
is_external: bool = False,
source_path: str | None = None,
) -> Network:
"""Import a network file and create a DB record.

Expand Down Expand Up @@ -383,6 +384,7 @@ def import_network_file( # noqa: PLR0913
filename=original_filename,
file_path=str(dest),
is_external=is_external,
source_path=source_path,
)
_apply_network_metadata(network, dest, file_hash)
db.add(network)
Expand Down
3 changes: 3 additions & 0 deletions src/pypsa_app/backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,9 @@ def validate_local_mode(self) -> Self:
"Local mode is a single-user dashboard deployment."
)
raise ValueError(msg)
if self.local_mode and self.snakedispatch_backends:
msg = "SNAKEDISPATCH_BACKENDS is not yet implemented in LOCAL_MODE."
raise ValueError(msg)
return self

@model_validator(mode="after")
Expand Down
1 change: 1 addition & 0 deletions src/pypsa_app/backend/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ def import_run_outputs_task(self: Any, job_id: str) -> None: # noqa: PLR0915
db,
source_run_id=run.job_id,
visibility=run.visibility,
source_path=output_path,
)
logger.info(
"Imported network from run output",
Expand Down
Loading