Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ All notable changes to this project are documented in this file. The format is b
- Scaffold new modules with `core/` and `web/` areas and mirrored nested Python tests; existing modules migrate individually. See `docs/module-layout.md`.
- `mail` follows the module layout: `mail.core.{constants,settings,state,data_compatibility}` and `mail.delivery.{sender,runtime}`. `mail.settings` and `mail.constants` still import, with a `DeprecationWarning`, until 0.0.46.
- `site_lock` follows the module layout: `site_lock.core.{constants,settings,state}` and `site_lock.web.{middleware,page,rate_limit}` (the unlock template moves with the page). `site_lock.settings` still imports, with a `DeprecationWarning`, until 0.0.46.
- `dashboard` follows the module layout: `dashboard.core.constants`, `dashboard.web.{endpoints,stats,doctor}`, and the page components move from `pages/components/` to `ui/components/`. Nothing outside the module imported the old paths, so no aliases remain.

### Fixed

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ The frontend uses an emerald + teal design system mirrored as Tailwind 4 tokens.
- Reach for `StatCard` / `SectionTitle` / `FilterPills` before rolling new layouts.
- Use lucide-react icons (already a dependency) and the existing `Badge` / `Button` variants — emerald primary for the main CTA, outline / ghost for secondary actions.

The 300-line file cap (enforced by CI) usually pushes you to factor row-level components into `pages/components/` — see `modules/users/users/admin/components/UserRow.tsx` and `modules/dashboard/dashboard/pages/components/doctor/report.ts` for the pattern.
The 300-line file cap (enforced by CI) usually pushes you to factor row-level components into `pages/components/` — see `modules/users/users/admin/components/UserRow.tsx` and `modules/dashboard/dashboard/ui/components/doctor/report.ts` for the pattern.

## User management

Expand Down
2 changes: 1 addition & 1 deletion docs/module-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ when a later module PR moves files.
| auth | 8 | 20 | flat |
| background_tasks | 49 | 83 | flat |
| branding | 13 | 20 | flat |
| dashboard | 5 | 8 | flat |
| dashboard | 2 | 0 | follows this layout |
| feature_flags | 8 | 9 | flat |
| file_storage | 39 | 65 | flat |
| keycloak | 13 | 19 | flat |
Expand Down
2 changes: 1 addition & 1 deletion docs/modules/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ the role editor to change that.

The three user figures come from the app's [user directory](/framework/user-directory) (`stats()`), so they count whichever auth provider holds the accounts. A figure the provider cannot give is `null` and its tile is hidden: under `keycloak` `users_created_this_month` is `null` (the cache only learns of a user at first sign-in), and with no auth provider all three are.

The result is cached process-wide for 30 seconds. If you mutate something that should change the numbers (e.g. seed users from a script), call `dashboard.stats.invalidate_stats_cache()` to clear it on the next call.
The result is cached process-wide for 30 seconds. If you mutate something that should change the numbers (e.g. seed users from a script), call `dashboard.web.stats.invalidate_stats_cache()` to clear it on the next call.

## Menu

Expand Down
2 changes: 1 addition & 1 deletion framework/cli/tests/test_module_css_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def test_module_class_is_emitted_by_vite_build(self):
PROBE_PAGE = PROBE_MODULE_PAGES / "CrossModuleImportProbe.tsx"
PROBE_SOURCE = """\
// Generated by test_module_css_build.py — safe to delete.
import { ModuleTile } from '@simple-module-py/dashboard/pages/components/ModuleTile';
import { ModuleTile } from '@simple-module-py/dashboard/ui/components/ModuleTile';

export default function CrossModuleImportProbe() {
return (
Expand Down
5 changes: 0 additions & 5 deletions host/tests/test_module_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,6 @@
services.py settings.py shared_props.py tenant_branding.py tenant_definitions.py
""".split()
),
"dashboard": frozenset(
"""
constants.py doctor.py endpoints stats.py
""".split()
),
"feature_flags": frozenset(
"""
constants.py deps.py endpoints invalidation.py service.py tenant_cleanup.py
Expand Down
1 change: 1 addition & 0 deletions modules/dashboard/dashboard/core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""What the dashboard builds on: its constants."""
8 changes: 4 additions & 4 deletions modules/dashboard/dashboard/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from simple_module_core.module import ModuleBase, ModuleMeta
from simple_module_core.permissions import PermissionRegistry

from dashboard.constants import PERM_GROUP, PERM_VIEW
from dashboard.core.constants import PERM_GROUP, PERM_VIEW

_URL_DASHBOARD = "/dashboard/"
_URL_DOCTOR = "/admin/doctor/"
Expand All @@ -29,14 +29,14 @@ class DashboardModule(ModuleBase):
)

def register_routes(self, api_router: APIRouter, view_router: APIRouter) -> None:
from dashboard.endpoints.api import router as api
from dashboard.endpoints.views import router as views
from dashboard.web.endpoints.api import router as api
from dashboard.web.endpoints.views import router as views

api_router.include_router(api)
view_router.include_router(views)

def register_admin_routes(self, admin_router: APIRouter) -> None:
from dashboard.endpoints.views import admin_router as doctor_views
from dashboard.web.endpoints.views import admin_router as doctor_views

admin_router.include_router(doctor_views)

Expand Down
18 changes: 9 additions & 9 deletions modules/dashboard/dashboard/pages/Doctor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { Button } from '@simple-module-py/ui/components/ui/button';
import { AdminLayout } from '@simple-module-py/ui/layouts/AdminLayout';
import { RefreshCw } from 'lucide-react';
import { toast } from 'sonner';
import { ChecksCard } from './components/doctor/ChecksCard';
import { useCheckLabels } from './components/doctor/check-copy';
import { copyToClipboard } from './components/doctor/copy';
import { DevServerCard } from './components/doctor/DevServerCard';
import { MigrationsCard } from './components/doctor/MigrationsCard';
import { buildDoctorReport } from './components/doctor/report';
import { StatsRow } from './components/doctor/StatsRow';
import { TerminalPanel } from './components/doctor/TerminalPanel';
import type { DoctorProps } from './components/doctor/types';
import { ChecksCard } from '../ui/components/doctor/ChecksCard';
import { useCheckLabels } from '../ui/components/doctor/check-copy';
import { copyToClipboard } from '../ui/components/doctor/copy';
import { DevServerCard } from '../ui/components/doctor/DevServerCard';
import { MigrationsCard } from '../ui/components/doctor/MigrationsCard';
import { buildDoctorReport } from '../ui/components/doctor/report';
import { StatsRow } from '../ui/components/doctor/StatsRow';
import { TerminalPanel } from '../ui/components/doctor/TerminalPanel';
import type { DoctorProps } from '../ui/components/doctor/types';

const RERUN_URL = '/admin/doctor/rerun';

Expand Down
4 changes: 2 additions & 2 deletions modules/dashboard/dashboard/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { Card, CardContent } from '@simple-module-py/ui/components/ui/card';
import { usePageProps } from '@simple-module-py/ui/hooks/usePageProps';
import { AuthenticatedLayout } from '@simple-module-py/ui/layouts/AuthenticatedLayout';
import { Activity, Box, Stethoscope, Users } from 'lucide-react';
import { type ModuleHealth, ModuleTile } from './components/ModuleTile';
import { moduleTargetResolver } from './components/module-target';
import { type ModuleHealth, ModuleTile } from '../ui/components/ModuleTile';
import { moduleTargetResolver } from '../ui/components/module-target';

interface SystemModule {
name: string;
Expand Down
1 change: 1 addition & 0 deletions modules/dashboard/dashboard/web/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""The HTTP side: the home and doctor endpoints and the stats and doctor reads they share."""
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from simple_module_hosting.permissions import RequiresPermission
from sqlalchemy.ext.asyncio import AsyncSession

from dashboard.constants import PERM_VIEW
from dashboard.stats import fetch_dashboard_stats
from dashboard.core.constants import PERM_VIEW
from dashboard.web.stats import fetch_dashboard_stats

router = APIRouter()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
from sqlalchemy.ext.asyncio import AsyncSession
from starlette.responses import RedirectResponse

from dashboard.constants import PERM_VIEW
from dashboard.stats import fetch_dashboard_stats
from dashboard.core.constants import PERM_VIEW
from dashboard.web.stats import fetch_dashboard_stats

router = APIRouter()

Expand Down Expand Up @@ -66,7 +66,7 @@ async def dashboard(
@admin_router.get("/", response_model=None)
async def doctor(request: Request, inertia: InertiaDep) -> InertiaResponse:
"""`make doctor` mirror — checks, migrations, dev server, all live."""
from dashboard.doctor import doctor_props
from dashboard.web.doctor import doctor_props

# doctor_props walks the alembic script directory and every module's pages
# dir, so it is blocking filesystem work: keep it off the event loop rather
Expand Down
2 changes: 1 addition & 1 deletion modules/dashboard/tests-js/dashboard_Doctor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ vi.mock('@inertiajs/react', () => ({
usePage: () => ({ props: {} }),
}));

import type { DoctorProps } from '../dashboard/pages/components/doctor/types';
import Doctor from '../dashboard/pages/Doctor';
import type { DoctorProps } from '../dashboard/ui/components/doctor/types';

const props: DoctorProps = {
checks: [{ id: 'pages', status: 'pass', command: null, findings: [] }],
Expand Down
6 changes: 3 additions & 3 deletions modules/dashboard/tests-js/doctor-stats.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ configureI18n({
},
});

import { buildDoctorReport } from '../dashboard/pages/components/doctor/report';
import { StatsRow } from '../dashboard/pages/components/doctor/StatsRow';
import type { DoctorProps } from '../dashboard/pages/components/doctor/types';
import { buildDoctorReport } from '../dashboard/ui/components/doctor/report';
import { StatsRow } from '../dashboard/ui/components/doctor/StatsRow';
import type { DoctorProps } from '../dashboard/ui/components/doctor/types';

const STATS = {
checks_passing: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import httpx
import pytest
from dashboard.module import DashboardModule
from dashboard.stats import invalidate_stats_cache
from dashboard.web.stats import invalidate_stats_cache


@pytest.fixture(autouse=True)
Expand Down Expand Up @@ -35,7 +35,7 @@ async def test_module_meta(self):
class TestFetchDashboardStats:
@pytest.fixture
async def stats(self, app):
from dashboard.stats import fetch_dashboard_stats
from dashboard.web.stats import fetch_dashboard_stats

async with app.state.sm.db.session_factory() as db:
return await fetch_dashboard_stats(db, app)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from __future__ import annotations

from dashboard.doctor import HOST_MIGRATION_CHIP, _module_chip
from dashboard.web.doctor import HOST_MIGRATION_CHIP, _module_chip


class TestMigrationChips:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

import httpx
import pytest
from dashboard.doctor import CHECKS, MIGRATION_CHECK_ID
from dashboard.stats import invalidate_stats_cache
from dashboard.web.doctor import CHECKS, MIGRATION_CHECK_ID
from dashboard.web.stats import invalidate_stats_cache
from simple_module_core.diagnostics import Diagnostic, DiagnosticLevel
from simple_module_test.fixtures import forge_session_cookie

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
from __future__ import annotations

import pytest
from dashboard.constants import PERM_VIEW
from dashboard.stats import invalidate_stats_cache
from dashboard.core.constants import PERM_VIEW
from dashboard.web.stats import invalidate_stats_cache
from simple_module_core.tenancy import TenantRole, tenant_role

_STATS = "/api/dashboard/stats"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import httpx
import pytest
from dashboard.stats import fetch_dashboard_stats, invalidate_stats_cache
from dashboard.web.stats import fetch_dashboard_stats, invalidate_stats_cache
from users.models import User


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from __future__ import annotations

import pytest
from dashboard.constants import PERM_VIEW
from dashboard.stats import invalidate_stats_cache
from dashboard.core.constants import PERM_VIEW
from dashboard.web.stats import invalidate_stats_cache
from simple_module_hosting.settings import Settings
from simple_module_test.tenant_client import session_client

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

@pytest.fixture(autouse=True)
def _fresh_cache():
from dashboard.stats import invalidate_stats_cache
from dashboard.web.stats import invalidate_stats_cache

invalidate_stats_cache()
yield
Expand All @@ -34,7 +34,7 @@ async def stats(self, db, *, active_window):


async def _stats(app) -> dict:
from dashboard.stats import fetch_dashboard_stats
from dashboard.web.stats import fetch_dashboard_stats

async with app.state.sm.db.session_factory() as db:
return await fetch_dashboard_stats(db, app)
Expand Down
2 changes: 1 addition & 1 deletion modules/settings/settings/endpoints/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ async def test_connection(package: str, request: Request) -> dict:
# Run independently of one another — a module can register several checks
# (e.g. SMTP + primary storage + backup storage), and sequentially awaiting
# each one would block the request for the sum of their latencies instead
# of the slowest one, the same reasoning dashboard.stats._run_health_checks
# of the slowest one, the same reasoning dashboard.web.stats._run_health_checks
# already applies to probe checks.
async def _run_one(check) -> dict:
try:
Expand Down
Loading