diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index cf65b43c..2277de36 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -10,4 +10,4 @@ jobs: deploy: uses: ConductionNL/.github/.github/workflows/documentation.yml@main with: - cname: mydash.app + cname: mydash.conduction.nl diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index d0341887..9424e59d 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -4,7 +4,7 @@ const config = { title: 'MyDash', tagline: 'Your customizable dashboard for Nextcloud', - url: 'https://mydash.app', + url: 'https://mydash.conduction.nl', baseUrl: '/', // GitHub pages deployment config @@ -14,6 +14,7 @@ const config = { onBrokenLinks: 'warn', onBrokenMarkdownLinks: 'warn', + onBrokenAnchors: 'warn', i18n: { defaultLocale: 'en', @@ -105,6 +106,15 @@ const config = { }), markdown: { mermaid: true, + // Tutorial pages reference screenshots that are populated by + // `tests/e2e/docs-screenshots.spec.ts`. The Playwright capture run is + // separate from the docs build, so the build needs to succeed even + // when a fresh checkout doesn't have every PNG yet. Warn instead of + // failing — the absence is visible at preview time and the capture + // spec brings everything back on demand. + hooks: { + onBrokenMarkdownImages: 'warn', + }, }, themes: ['@docusaurus/theme-mermaid'], }; diff --git a/docs/screenshots/tutorials/admin/01-admin-settings.png b/docs/screenshots/tutorials/admin/01-admin-settings.png new file mode 100644 index 00000000..3b447854 Binary files /dev/null and b/docs/screenshots/tutorials/admin/01-admin-settings.png differ diff --git a/docs/screenshots/tutorials/admin/01-toggle.png b/docs/screenshots/tutorials/admin/01-toggle.png new file mode 100644 index 00000000..3b447854 Binary files /dev/null and b/docs/screenshots/tutorials/admin/01-toggle.png differ diff --git a/docs/screenshots/tutorials/admin/02-template-create.png b/docs/screenshots/tutorials/admin/02-template-create.png new file mode 100644 index 00000000..a4713525 Binary files /dev/null and b/docs/screenshots/tutorials/admin/02-template-create.png differ diff --git a/docs/screenshots/tutorials/admin/02-template-edit.png b/docs/screenshots/tutorials/admin/02-template-edit.png new file mode 100644 index 00000000..adf11223 Binary files /dev/null and b/docs/screenshots/tutorials/admin/02-template-edit.png differ diff --git a/docs/screenshots/tutorials/admin/02-templates-list.png b/docs/screenshots/tutorials/admin/02-templates-list.png new file mode 100644 index 00000000..bc050402 Binary files /dev/null and b/docs/screenshots/tutorials/admin/02-templates-list.png differ diff --git a/docs/screenshots/tutorials/admin/03-group-cog.png b/docs/screenshots/tutorials/admin/03-group-cog.png new file mode 100644 index 00000000..c68e2dcf Binary files /dev/null and b/docs/screenshots/tutorials/admin/03-group-cog.png differ diff --git a/docs/screenshots/tutorials/admin/04-role-create.png b/docs/screenshots/tutorials/admin/04-role-create.png new file mode 100644 index 00000000..e4b05fe8 Binary files /dev/null and b/docs/screenshots/tutorials/admin/04-role-create.png differ diff --git a/docs/screenshots/tutorials/admin/04-roles-tab.png b/docs/screenshots/tutorials/admin/04-roles-tab.png new file mode 100644 index 00000000..b1839f8c Binary files /dev/null and b/docs/screenshots/tutorials/admin/04-roles-tab.png differ diff --git a/docs/screenshots/tutorials/admin/05-bulk-panel.png b/docs/screenshots/tutorials/admin/05-bulk-panel.png new file mode 100644 index 00000000..fbcf808c Binary files /dev/null and b/docs/screenshots/tutorials/admin/05-bulk-panel.png differ diff --git a/docs/screenshots/tutorials/user/01-first-launch-overview.png b/docs/screenshots/tutorials/user/01-first-launch-overview.png new file mode 100644 index 00000000..71ba497f Binary files /dev/null and b/docs/screenshots/tutorials/user/01-first-launch-overview.png differ diff --git a/docs/screenshots/tutorials/user/02-create-add-button.png b/docs/screenshots/tutorials/user/02-create-add-button.png new file mode 100644 index 00000000..b5e445c4 Binary files /dev/null and b/docs/screenshots/tutorials/user/02-create-add-button.png differ diff --git a/docs/screenshots/tutorials/user/02-create-modal.png b/docs/screenshots/tutorials/user/02-create-modal.png new file mode 100644 index 00000000..2107ae28 Binary files /dev/null and b/docs/screenshots/tutorials/user/02-create-modal.png differ diff --git a/docs/screenshots/tutorials/user/02-sidebar-open.png b/docs/screenshots/tutorials/user/02-sidebar-open.png new file mode 100644 index 00000000..635355ec Binary files /dev/null and b/docs/screenshots/tutorials/user/02-sidebar-open.png differ diff --git a/docs/screenshots/tutorials/user/03-cog-menu.png b/docs/screenshots/tutorials/user/03-cog-menu.png new file mode 100644 index 00000000..904ad999 Binary files /dev/null and b/docs/screenshots/tutorials/user/03-cog-menu.png differ diff --git a/docs/screenshots/tutorials/user/03-edit-mode-enter.png b/docs/screenshots/tutorials/user/03-edit-mode-enter.png new file mode 100644 index 00000000..815b8fbd Binary files /dev/null and b/docs/screenshots/tutorials/user/03-edit-mode-enter.png differ diff --git a/docs/screenshots/tutorials/user/03-widget-added.png b/docs/screenshots/tutorials/user/03-widget-added.png new file mode 100644 index 00000000..525e64ab Binary files /dev/null and b/docs/screenshots/tutorials/user/03-widget-added.png differ diff --git a/docs/screenshots/tutorials/user/03-widget-form.png b/docs/screenshots/tutorials/user/03-widget-form.png new file mode 100644 index 00000000..238c2026 Binary files /dev/null and b/docs/screenshots/tutorials/user/03-widget-form.png differ diff --git a/docs/screenshots/tutorials/user/03-widget-picker.png b/docs/screenshots/tutorials/user/03-widget-picker.png new file mode 100644 index 00000000..e6a2d0d1 Binary files /dev/null and b/docs/screenshots/tutorials/user/03-widget-picker.png differ diff --git a/docs/screenshots/tutorials/user/04-dragging.png b/docs/screenshots/tutorials/user/04-dragging.png new file mode 100644 index 00000000..981d1643 Binary files /dev/null and b/docs/screenshots/tutorials/user/04-dragging.png differ diff --git a/docs/screenshots/tutorials/user/04-edit-mode.png b/docs/screenshots/tutorials/user/04-edit-mode.png new file mode 100644 index 00000000..2ac1c064 Binary files /dev/null and b/docs/screenshots/tutorials/user/04-edit-mode.png differ diff --git a/docs/screenshots/tutorials/user/04-reflowed.png b/docs/screenshots/tutorials/user/04-reflowed.png new file mode 100644 index 00000000..cd6b40c4 Binary files /dev/null and b/docs/screenshots/tutorials/user/04-reflowed.png differ diff --git a/docs/screenshots/tutorials/user/07-default-marker.png b/docs/screenshots/tutorials/user/07-default-marker.png new file mode 100644 index 00000000..6aff0490 Binary files /dev/null and b/docs/screenshots/tutorials/user/07-default-marker.png differ diff --git a/docs/screenshots/tutorials/user/08-url-bar.png b/docs/screenshots/tutorials/user/08-url-bar.png new file mode 100644 index 00000000..5f167f38 Binary files /dev/null and b/docs/screenshots/tutorials/user/08-url-bar.png differ diff --git a/docs/screenshots/tutorials/user/09-after-switch.png b/docs/screenshots/tutorials/user/09-after-switch.png new file mode 100644 index 00000000..880c1b2e Binary files /dev/null and b/docs/screenshots/tutorials/user/09-after-switch.png differ diff --git a/docs/screenshots/tutorials/user/10-config-modal.png b/docs/screenshots/tutorials/user/10-config-modal.png new file mode 100644 index 00000000..8a72253a Binary files /dev/null and b/docs/screenshots/tutorials/user/10-config-modal.png differ diff --git a/docs/screenshots/tutorials/user/10-delete-confirm.png b/docs/screenshots/tutorials/user/10-delete-confirm.png new file mode 100644 index 00000000..8dd4c625 Binary files /dev/null and b/docs/screenshots/tutorials/user/10-delete-confirm.png differ diff --git a/docs/static/CNAME b/docs/static/CNAME index 37344b67..ca7998be 100644 --- a/docs/static/CNAME +++ b/docs/static/CNAME @@ -1 +1 @@ -mydash.app +mydash.conduction.nl diff --git a/docs/tutorials/_category_.json b/docs/tutorials/_category_.json new file mode 100644 index 00000000..ab92676a --- /dev/null +++ b/docs/tutorials/_category_.json @@ -0,0 +1,11 @@ +{ + "label": "Tutorials", + "position": 2, + "collapsible": true, + "collapsed": false, + "link": { + "type": "generated-index", + "title": "MyDash tutorials", + "description": "Step-by-step walkthroughs for everyday MyDash tasks. The user track covers personal-dashboard workflows; the admin track covers org-wide configuration and policy." + } +} diff --git a/docs/tutorials/admin/01-toggle-personal-dashboards.md b/docs/tutorials/admin/01-toggle-personal-dashboards.md new file mode 100644 index 00000000..34d04afd --- /dev/null +++ b/docs/tutorials/admin/01-toggle-personal-dashboards.md @@ -0,0 +1,63 @@ +--- +sidebar_position: 1 +title: Enable or disable personal dashboards +description: Allow or prevent end users from creating their own personal dashboards across the instance. +--- + +# Enable or disable personal dashboards + +The `allow_user_dashboards` flag is the master kill-switch for end-user dashboard creation. When off, MyDash hides the **+ Add dashboard** button, blocks `POST /api/dashboard`, and shows users a localised explainer in the empty state. + +## Goal + +Toggle the flag on or off and verify the user UI reflects the change. + +## Prerequisites + +- You must be a Nextcloud admin. + +## Steps + +### 1. Open MyDash admin settings + +Settings menu (avatar) → **Administration settings** → **MyDash** in the left nav. + +![MyDash admin settings page](../../screenshots/tutorials/admin/01-admin-settings.png) + +### 2. Find the **Personal dashboards** section + +The section is near the top of the page. The toggle is labelled **Allow users to create personal dashboards**. + +![Personal dashboards toggle](../../screenshots/tutorials/admin/01-toggle.png) + +### 3. Flip the toggle + +The change persists immediately — no Save button. The flag is stored in `oc_mydash_admin_settings` (key `allow_user_dashboards`). + +### 4. Verify on the user side + +Open MyDash as a non-admin user. + +- **Toggle on** → **+ Add dashboard** is visible in the sidebar; the empty state shows the standard "Create your first dashboard" CTA. +- **Toggle off** → button hidden; empty state shows "Personal dashboards are not enabled by your administrator." + +![User UI when disabled](../../screenshots/tutorials/admin/01-user-disabled.png) + +## Behaviour notes + +- **Existing personal dashboards survive.** Disabling doesn't delete user dashboards — they remain accessible. Only creation is blocked. +- **Backend enforcement.** Even if a curl request bypasses the UI, the `POST /api/dashboard` controller calls `assertPersonalDashboardsAllowed()` and responds with `403 personal_dashboards_disabled` when the flag is off. +- **Group-shared dashboards are unaffected.** Admin templates and group defaults render regardless. + +## Common issues + +| Symptom | Fix | +|---|---| +| Toggle is missing | Confirm the user is actually a Nextcloud admin (`occ user:info `). | +| Users still see + Add after disable | Apache `apc` cache. `docker exec nextcloud apache2ctl graceful` to refresh. | +| Newly-created users land on no-dashboard | Either the toggle is off, OR no admin template applies — see [Create an admin template](02-admin-templates.md). | + +## Reference + +- [Admin settings feature reference](../../features/admin-settings.md) +- [Permissions reference](../../features/permissions.md) diff --git a/docs/tutorials/admin/02-admin-templates.md b/docs/tutorials/admin/02-admin-templates.md new file mode 100644 index 00000000..cecb46c9 --- /dev/null +++ b/docs/tutorials/admin/02-admin-templates.md @@ -0,0 +1,82 @@ +--- +sidebar_position: 2 +title: Create an admin template dashboard +description: Build a dashboard centrally and roll it out to every user (or to a specific group) on first login. +--- + +# Create an admin template dashboard + +An **admin template** is a dashboard the admin authors and assigns to one or more groups. The first time a member of an assigned group opens MyDash, they get a personal copy of the template's layout. Their copy is independent — they can edit, add, and remove widgets without affecting the template or other users. + +## When to use this + +- You want every member of a group to start with the same dashboard (KPIs, mandatory tools, brand tiles). +- You're rolling out MyDash for the first time and don't want users to face an empty grid. +- You manage a multi-tenant Nextcloud where each tenant gets a different starting layout. + +## Goal + +Author a template, assign it to a group, and verify a member of that group lands on a fresh copy on first login. + +## Prerequisites + +- You must be a Nextcloud admin. +- The target group exists in Nextcloud (`occ group:add my-group` if not). + +## Steps + +### 1. Open MyDash admin settings + +Avatar → **Administration settings** → **MyDash**. + +### 2. Scroll to **Admin templates** + +The section lists every existing template with name, assigned groups, and edit / delete actions. + +![Admin templates list](../../screenshots/tutorials/admin/02-templates-list.png) + +### 3. Click **Create template** + +The template-create modal opens. Required fields: + +- **Name** — internal label for admins. Not user-visible (the user sees a regular dashboard with whatever name you set there). +- **Group order priority** — comma-separated group ids in priority order. The resolver picks the first group in this list that the user belongs to. + +![Create template modal](../../screenshots/tutorials/admin/02-template-create.png) + +Optional fields: + +- **Compulsory widgets** — list of widget IDs that users cannot remove from their copy. Useful for mandatory KPI cards. +- **Lock layout** — when on, users get `view_only` permission on their copy (no drag/resize/add/remove). They still see all the data, just can't reshape it. + +### 4. Save and edit the template's layout + +Saving creates the template row. Click **Edit layout** on the new template — MyDash opens a regular dashboard editor scoped to the template. Add widgets, position them, configure them — exactly like editing a personal dashboard. + +![Edit template layout](../../screenshots/tutorials/admin/02-template-edit.png) + +### 5. Verify with a test user + +`occ user:add testuser` (or pick an existing member of the assigned group). Log in as that user and open MyDash. The first visit clones the template into a personal dashboard named **My Dashboard** (default) — visible in **MY DASHBOARDS** in the sidebar. + +![Test user lands on cloned template](../../screenshots/tutorials/admin/02-test-user-view.png) + +## Behaviour notes + +- **One-shot clone, not a live link.** The user's copy diverges from the template the moment either side changes. Editing the template later doesn't push changes to existing copies — only new users get the latest. +- **Compulsory widgets stay compulsory across the clone.** They render with a small lock icon in the user's view; **Remove** is greyed out. +- **Multiple groups → priority list wins.** A user in groups `engineering` and `marketing` with priorities `[engineering, marketing]` gets the engineering template even if marketing's was created later. +- **No matching group → fallback resolver.** Users who match no template land on the seven-step default-resolution chain: pin → group default → admin defaults → first available. + +## Common issues + +| Symptom | Fix | +|---|---| +| Test user sees the empty state, not the template | The user isn't actually in the assigned group, or the group isn't in the priority list. Check `occ user:info`. | +| Template's compulsory widget shows up but user can remove it | Check the compulsory flag is set on that placement (`isCompulsory=1` in `oc_mydash_widget_placements`). | +| Template edits aren't propagating | Expected. Templates are clone-on-first-login, not live. | + +## Reference + +- [Admin templates feature reference](../../features/admin-templates.md) +- [Permissions reference](../../features/permissions.md) diff --git a/docs/tutorials/admin/03-group-defaults.md b/docs/tutorials/admin/03-group-defaults.md new file mode 100644 index 00000000..09ae9b64 --- /dev/null +++ b/docs/tutorials/admin/03-group-defaults.md @@ -0,0 +1,59 @@ +--- +sidebar_position: 3 +title: Mark a group's default dashboard +description: Pin a shared dashboard so it's the landing page for everyone in a Nextcloud group. +--- + +# Mark a group's default dashboard + +While [admin templates](02-admin-templates.md) clone a layout into each user's personal space, **group defaults** keep a shared dashboard visible to everyone in a group. Group defaults appear in the user's sidebar under the **DEFAULT** section and are read-only by default. + +## Goal + +Mark an existing dashboard as the default for a Nextcloud group, and verify members of that group see it under **DEFAULT**. + +## Prerequisites + +- A dashboard already exists (created by an admin in the admin context, not a personal user dashboard). +- The dashboard is shared with the target group via the group-share flow (see admin templates above for the share-with-group path). +- You must be a Nextcloud admin. + +## Steps + +### 1. Find the dashboard you want to default + +Open MyDash as the admin and navigate to the group dashboard. Open its cog menu. + +![Group dashboard cog menu](../../screenshots/tutorials/admin/03-group-cog.png) + +### 2. Click **Set as default for <group>** + +This is admin-only. The action issues `POST /api/dashboards/group//default` with the dashboard's UUID. The service flips the previous group default off and the new one on **in a single transaction** (REQ-DASH-015), so members never see a flash with no default. + +![Set as default for group](../../screenshots/tutorials/admin/03-set-group-default.png) + +### 3. Verify on a member's view + +Log in as a member of the group. Their sidebar's **DEFAULT** section now lists the dashboard you marked. + +![Member view — DEFAULT section](../../screenshots/tutorials/admin/03-member-default.png) + +## Behaviour notes + +- **One default per group.** Marking a new default for a group automatically clears the previous one — atomic via a database transaction. There's no "two defaults" risk. +- **Cross-group safety.** If you accidentally try to mark a dashboard as default for a group it isn't shared with, the service rejects with 404 (existence is not leaked across group boundaries — REQ-DASH-015 scenario). +- **The user's personal pin still wins.** A user who manually pinned their own dashboard via [Set a default dashboard](../user/07-set-default.md) lands on their pin, not the group default. The group default is the fallback when no personal pin exists. +- **Cleared default → resolver fallback.** If you clear a group default without setting a new one, members land on the org-wide default-group default (or further down the chain). + +## Common issues + +| Symptom | Fix | +|---|---| +| The action is missing from the cog menu | The dashboard isn't shared with the group, or you're not an admin. | +| Members see no change after marking | OPcache. `docker exec nextcloud apache2ctl graceful` (the dashboard list is cached per-request). | +| "Cross-group default" error | The dashboard's `groupId` doesn't match the target group. Reshare it first. | + +## Reference + +- [Dashboards feature reference](../../features/dashboards.md) +- API: `POST /api/dashboards/group/{groupId}/default` diff --git a/docs/tutorials/admin/04-restrict-widgets.md b/docs/tutorials/admin/04-restrict-widgets.md new file mode 100644 index 00000000..bc3bec51 --- /dev/null +++ b/docs/tutorials/admin/04-restrict-widgets.md @@ -0,0 +1,74 @@ +--- +sidebar_position: 4 +title: Restrict widgets per role +description: Limit which widget types a group of users can see and add. +--- + +# Restrict widgets per role + +The role-based-content feature (RFP) lets an admin define an allow-list of widget IDs per role, where a role is a Nextcloud group plus a per-feature permission set. Members of restricted roles see a filtered widget picker and a filtered AddWidgetModal type list. + +## When to use this + +- You want to keep certain widgets out of specific groups (e.g. hide HR-Notes from engineering). +- You want to enforce a minimal widget set for compliance / security reasons. +- You're rolling out MyDash to non-technical users and want to reduce cognitive load by hiding rarely-used widgets. + +## Goal + +Define a role with a widget allow-list, assign it to a group, and verify members see only the allowed widgets. + +## Prerequisites + +- You must be a Nextcloud admin. +- The target group exists. + +## Steps + +### 1. Open MyDash admin settings → **Roles** + +![Roles tab in admin settings](../../screenshots/tutorials/admin/04-roles-tab.png) + +### 2. Click **Create role** + +Required fields: + +- **Name** — internal label. +- **Group** — Nextcloud group id this role applies to. +- **Allowed widgets** — multi-select of widget IDs (e.g. `recommendations`, `activity`, `tile`, `files`, `text`). + +![Create role modal](../../screenshots/tutorials/admin/04-role-create.png) + +Leave the allow-list empty to mean "no restriction" (the default for users not in any role). + +### 3. Save + +The role row appears in the list. The allow-list is persisted in `oc_mydash_roles` and surfaced via `RoleFeaturePermissionService::getAllowedWidgetIds(userId)`. + +### 4. Verify on a member's view + +As a member of the assigned group, open MyDash and the widget picker: + +![Filtered widget picker](../../screenshots/tutorials/admin/04-filtered-picker.png) + +Only the allowed widget IDs appear. Existing placements of disallowed widgets keep rendering — the filter only applies to **adding new** widgets. + +## Behaviour notes + +- **Permissive default.** Users with no matching role see the full widget catalogue. No role assignment ⇒ no restriction. +- **Multiple groups, multiple roles → union.** A user in groups `eng` and `pm` with roles defining different allow-lists sees the **union** of all their roles' allowed widgets. +- **Role changes take effect on next page load.** The allow-list is baked into initial state, not refetched live. +- **Backend enforcement is on display only.** The RFP allow-list is filtering the picker, not blocking the API. A determined user could still POST a placement with a disallowed widgetId — that's by design (the backend doesn't gate on widget type for placement creation, only on dashboard permission). If you need hard backend enforcement, file an issue. + +## Common issues + +| Symptom | Fix | +|---|---| +| Member still sees all widgets | Reload the page. Allow-list is read on initial state, not live. | +| Allow-list empty but member sees nothing | Empty list = "no restriction" (full catalogue). If you want to show *nothing*, that's not a supported state — use admin templates with `view_only` instead. | +| Two roles conflict | Multi-role membership is a union. There's no priority / override. | + +## Reference + +- [Role-based content feature reference](../../role-based-content.md) +- [Permissions reference](../../features/permissions.md) diff --git a/docs/tutorials/admin/05-bulk-operations.md b/docs/tutorials/admin/05-bulk-operations.md new file mode 100644 index 00000000..8c173d73 --- /dev/null +++ b/docs/tutorials/admin/05-bulk-operations.md @@ -0,0 +1,80 @@ +--- +sidebar_position: 5 +title: Bulk operations on dashboards +description: Move, delete, or reindex many dashboards in one shot from the admin UI. +--- + +# Bulk operations on dashboards + +Bulk operations are admin-only and let you act on many dashboards at once instead of one cog-menu click at a time. Available operations: + +- **Bulk move** — reassign a set of dashboards to a different group. +- **Bulk delete** — drop a set of dashboards (and their placements). +- **Bulk reindex** — refresh the search index entries (rare; useful after a schema change or a search fall-out). +- **Bulk status** — read a set's publication state without changing anything. + +All four sit behind admin endpoints; the UI is in **Admin settings → MyDash → Bulk operations**. + +## When to use this + +- Cleaning up after a bad migration that left orphan dashboards. +- Reorganising groups (e.g. merging `eng-old` into `eng`). +- Refreshing search results after touching widget content fields. + +## Goal + +Move every dashboard in `eng-old` into `eng`, then delete the now-empty `eng-old` group. + +## Prerequisites + +- You must be a Nextcloud admin. +- Take a database backup first. Bulk delete is destructive and irreversible. + +## Steps + +### 1. Open Bulk operations + +![Bulk operations panel](../../screenshots/tutorials/admin/05-bulk-panel.png) + +### 2. Filter the dashboard list + +Filter controls: by group id, by `source`, by date range, by name match. The selected rows update live. + +![Filter applied — eng-old dashboards selected](../../screenshots/tutorials/admin/05-bulk-filter.png) + +### 3. Pick **Move** and the target group + +Select the rows (or **Select all matching**), open the action dropdown, pick **Move**, type or pick the target group id (`eng`). + +![Move action — target group entered](../../screenshots/tutorials/admin/05-bulk-move-target.png) + +### 4. Confirm + +A confirmation dialog summarises what's about to happen — count, source group, target group, the same fields as a `dry-run` query. + +The action issues `POST /api/admin/dashboards/bulk-move` with the UUIDs and target group id. Service-side this is a single transaction: all rows or nothing. + +![Bulk move confirmation](../../screenshots/tutorials/admin/05-bulk-confirm.png) + +### 5. Repeat for **Delete** on the empty group + +After the move, refilter on `eng-old` (now zero hits if move succeeded), or just delete the group itself outside MyDash via `occ group:delete eng-old`. + +## Behaviour notes + +- **All-or-nothing transactions.** Move and delete wrap in a DB transaction. A partial failure rolls back, so you never end up with half-moved sets. +- **Audit trail.** Every bulk operation logs to the Nextcloud activity stream with the actor user id, action, and affected count. Useful for incident response. +- **Reindex is async.** Bulk reindex enqueues background jobs — the response returns immediately with the job count, and reindexing happens in `cron.php` runs. + +## Common issues + +| Symptom | Fix | +|---|---| +| "Some rows could not be moved" | The transaction rolled back. Look at the error detail — likely a slug-collision in the target group. Rename a colliding dashboard first. | +| Bulk delete didn't reduce row count | Pre-check the filter; you may have deleted nothing. | +| Reindex queued but search still stale | `cron.php` hasn't run since enqueue. Trigger manually: `docker exec nextcloud php cron.php`. | + +## Reference + +- [Dashboards feature reference](../../features/dashboards.md) +- API: `POST /api/admin/dashboards/bulk-{move,delete,reindex,status}` diff --git a/docs/tutorials/admin/_category_.json b/docs/tutorials/admin/_category_.json new file mode 100644 index 00000000..4c74ef3c --- /dev/null +++ b/docs/tutorials/admin/_category_.json @@ -0,0 +1,11 @@ +{ + "label": "Admin guide", + "position": 2, + "collapsible": true, + "collapsed": true, + "link": { + "type": "generated-index", + "title": "Admin guide", + "description": "Org-wide MyDash administration — toggling personal dashboards, authoring admin templates, marking group defaults, and restricting widgets per role." + } +} diff --git a/docs/tutorials/user/01-first-launch.md b/docs/tutorials/user/01-first-launch.md new file mode 100644 index 00000000..e90dfbb9 --- /dev/null +++ b/docs/tutorials/user/01-first-launch.md @@ -0,0 +1,59 @@ +--- +sidebar_position: 1 +title: Open MyDash for the first time +description: What you see the first time you open MyDash, and what each part of the screen does. +--- + +# Open MyDash for the first time + +The very first time you open MyDash, the app creates a personal dashboard for you and seeds it with a default widget bundle so you have something to look at right away. This page is a tour of what's on screen. + +## Goal + +Recognise the parts of the MyDash workspace and know where to find the controls you'll use in every other tutorial. + +## Steps + +### 1. Click the MyDash icon in the Nextcloud app launcher + +Or visit `/apps/mydash/` directly. After a moment of loading you land on **My Dashboard** with the default widget bundle already in place: + +![First-launch overview](../../screenshots/tutorials/user/01-first-launch-overview.png) + +The bundle ships with three preconfigured tile widgets on the top row — Conduction, Sendent, and Nextcloud — plus a Files widget below. + +### 2. Open the dashboard sidebar + +Click the **hamburger** button in the top-right to slide out the sidebar: + +![Sidebar open](../../screenshots/tutorials/user/02-sidebar-open.png) + +The sidebar is your jumping-off point for everything — switching between dashboards, creating new ones, configuring an existing one, and pinning a default. + +### 3. Scan the sidebar layout + +Three sections, top-down: + +- **DEFAULT** — group-shared dashboards your administrator marked as defaults for your group. Read-only by default. +- **MY DASHBOARDS** — your personal dashboards. The currently-active one is highlighted in primary colour. +- **Powered by** footer — brand attribution, doesn't affect functionality. + +A ★ next to a row marks that dashboard as your **default** — the one MyDash opens automatically when you visit `/apps/mydash/` cold. See [Set a default dashboard](07-set-default.md). + +### 4. Open a dashboard's cog menu + +Each row carries a cog (⚙️) on the right. Click it for the per-dashboard action menu: + +![Cog action menu](../../screenshots/tutorials/user/03-cog-menu.png) + +From here you can edit the layout, configure metadata, add custom widgets, pin as default, and delete. Most of the rest of the user guide is built around these actions. + +## What's next + +- [Create a new dashboard](02-create-dashboard.md) — start with a fresh canvas. +- [Add a widget](03-add-widget.md) — drop content blocks onto a dashboard. + +## Reference + +- [Dashboards feature reference](../../features/dashboards.md) +- [Widgets feature reference](../../features/widgets.md) diff --git a/docs/tutorials/user/02-create-dashboard.md b/docs/tutorials/user/02-create-dashboard.md new file mode 100644 index 00000000..186a9e3b --- /dev/null +++ b/docs/tutorials/user/02-create-dashboard.md @@ -0,0 +1,58 @@ +--- +sidebar_position: 2 +title: Create a new dashboard +description: Add a new personal dashboard alongside the one you already have. +--- + +# Create a new dashboard + +Each personal dashboard is an independent canvas — its own layout, its own widget set, its own URL. You can have as many as you need (e.g. one per project, one per workflow, one per role). + +## Goal + +Create a new personal dashboard, give it a name, optionally add a description and an icon, and land on it ready to add widgets. + +## Prerequisites + +- Personal dashboards must be enabled by your admin. If the **+ Add dashboard** button is hidden in the sidebar, it has been switched off — see your administrator (or the [admin guide](../admin/01-toggle-personal-dashboards.md)). + +## Steps + +### 1. Open the sidebar and click **+ Add dashboard** + +![Add dashboard button](../../screenshots/tutorials/user/02-create-add-button.png) + +### 2. Fill in the create modal + +A configuration modal opens with these fields: + +- **Name** — required. Used for the sidebar label and the URL slug. +- **Description** — optional. Shown in admin tooling and inside the configuration modal. +- **Icon** — optional. Pick from the registered icon set, or paste a URL for a custom icon (see [Dashboard icons capability](../../features/dashboards.md)). + +![Create dashboard modal](../../screenshots/tutorials/user/02-create-modal.png) + +### 3. Click **Save** + +The new dashboard is auto-activated, appears at the top of **MY DASHBOARDS** in the sidebar, and is bootstrapped with the default widget bundle (three tiles + a Files widget). You can now [add more widgets](03-add-widget.md), [reposition them](04-reposition-resize.md), or [pin this as your default](07-set-default.md). + +![New dashboard with default bundle](../../screenshots/tutorials/user/02-create-success.png) + +## Verification + +- The sidebar shows your new dashboard's name, highlighted as active. +- The URL bar reads `/apps/mydash/` — the slug is auto-derived from the name. +- The grid contains the four default placements (Conduction tile, Sendent tile, Nextcloud tile, Files widget). + +## Common issues + +| Symptom | Fix | +|---|---| +| **+ Add dashboard** button is missing | Personal dashboards are disabled by your admin. | +| Save button is disabled | The Name field is empty — required. | +| "Slug must be unique among siblings" error | A dashboard with the same auto-derived slug already exists. Pick a different name or set an explicit slug via [Dashboard configuration](10-rename-or-delete.md). | + +## Reference + +- [Dashboards feature reference](../../features/dashboards.md) +- [Grid layout reference](../../features/grid-layout.md) diff --git a/docs/tutorials/user/03-add-widget.md b/docs/tutorials/user/03-add-widget.md new file mode 100644 index 00000000..f5352c48 --- /dev/null +++ b/docs/tutorials/user/03-add-widget.md @@ -0,0 +1,75 @@ +--- +sidebar_position: 3 +title: Add a widget +description: Drop a built-in Nextcloud widget or a MyDash custom widget onto a dashboard. +--- + +# Add a widget + +Widgets are the content blocks on a dashboard. MyDash supports two families: + +- **Nextcloud widgets** — Mail, Activity, Calendar, Talk, Files, etc. Discovered automatically from every installed Nextcloud app via the `IManager` widget API. +- **MyDash custom widgets** — Label, Text, Image, Link button, Divider, Files (rich), People, Quicklinks, News, Video, Calendar, Links, Menu, Container, Tile. + +Both families use the same picker UI and live on the same grid. + +## Goal + +Add a new widget to your active dashboard. + +## Prerequisites + +- A dashboard you can edit (your own personal dashboard, or one your admin granted you `add_only` or `full` permission on). + +## Steps + +### 1. Enter edit mode + +Open the active dashboard's cog menu and click **Edit dashboard**. The grid shows resize handles on each placement. + +![Edit dashboard menu entry](../../screenshots/tutorials/user/03-edit-mode-enter.png) + +### 2. Open the **Add widget** modal + +From the cog menu pick **Add custom widget…** for the MyDash custom families, or click the empty-cell **+** marker that appears in edit mode for built-in Nextcloud widgets. + +![Widget picker modal](../../screenshots/tutorials/user/03-widget-picker.png) + +### 3. Pick a widget type + +The picker groups widgets by family. Hover for a one-line description. Custom widgets that haven't shipped a configuration form yet are filtered out — only types you can actually configure show. + +### 4. Configure the per-type form + +Each custom widget type opens its own form. Some examples: + +- **Tile** — title, icon (registry key, URL, emoji, or SVG), background colour, link type (`app` or `url`), link value. +- **Text** — markdown body, font size, alignment, optional table mode. +- **Files** — folder path, view mode (list / grid), optional filters and upload toggle. +- **Link button** — single-link button or list mode with multiple links. + +![Per-type configuration form](../../screenshots/tutorials/user/03-widget-form.png) + +### 5. Click **Save** + +The new placement appears in the grid at the next available cell. You can immediately drag it where you want — see [Reposition & resize widgets](04-reposition-resize.md). + +![Newly-added widget on dashboard](../../screenshots/tutorials/user/03-widget-added.png) + +## Verification + +- The widget renders with the content you configured. +- Reloading the page brings it back at the same grid position with the same content (changes are persisted to `oc_mydash_widget_placements`). + +## Common issues + +| Symptom | Fix | +|---|---| +| Widget type isn't in the picker | Either the type has no configuration form yet, or your admin's role-based widget allow-list excludes it (see [admin: restrict widgets per role](../admin/04-restrict-widgets.md)). | +| Widget renders empty | The type-specific config (e.g. Tile `linkValue`) wasn't filled. Right-click → **Edit** to fix. | +| "You don't have permission" toast | The dashboard's permission level is `view_only` for you. Owners or admins can change it. | + +## Reference + +- [Widgets feature reference](../../features/widgets.md) +- [Widgets vs tiles explainer](../../widgets-vs-tiles.md) diff --git a/docs/tutorials/user/04-reposition-resize.md b/docs/tutorials/user/04-reposition-resize.md new file mode 100644 index 00000000..1c6a0785 --- /dev/null +++ b/docs/tutorials/user/04-reposition-resize.md @@ -0,0 +1,68 @@ +--- +sidebar_position: 4 +title: Reposition & resize widgets +description: Drag and resize widgets on the grid; the layout snaps and saves automatically. +--- + +# Reposition & resize widgets + +The MyDash grid is built on **GridStack** — a 12-column responsive grid where every widget snaps to whole-cell boundaries. Layouts persist as soon as you let go of the mouse. + +## Goal + +Move a widget to a new position and resize it. + +## Prerequisites + +- A dashboard you can edit (`add_only` or `full` permission). +- Two or more widgets on the dashboard so the swap-on-collision behaviour is visible. + +## Steps + +### 1. Enter edit mode + +Cog menu → **Edit dashboard**. Drag/resize handles appear on every placement; the cog button on the active row flips to **Save dashboard** with a save icon. + +![Edit mode active](../../screenshots/tutorials/user/04-edit-mode.png) + +### 2. Reposition by dragging the title bar + +Click and hold anywhere on a widget's header (or its drag handle if the widget overrides the title row). Drop targets light up as you move; release to snap into place. Other widgets reflow around the drop point. + +![Mid-drag — drop targets](../../screenshots/tutorials/user/04-dragging.png) + +![After drop — reflowed layout](../../screenshots/tutorials/user/04-reflowed.png) + +### 3. Resize from the corner / edge handles + +Each widget has a bottom-right corner handle for diagonal resize and edge handles for single-axis resize. Minimum size is 2×2 cells; the registry-defined widget size hints take precedence on first placement but you can resize freely afterwards. + +![Resizing a widget](../../screenshots/tutorials/user/04-resizing.png) + +### 4. Save the layout + +Click **Save dashboard** in the top-right (the cog button switches into save mode while editing). MyDash issues a single `PUT /api/dashboard/{id}` with the new layout. + +![Save layout](../../screenshots/tutorials/user/04-save-layout.png) + +:::tip +The grid auto-saves on drop, so the explicit Save button is mostly a confirmation that no other layout changes are pending. If you reload mid-edit you'll see the auto-saved state, not a draft. +::: + +## Verification + +- Reload the page. Widgets are at the new positions and sizes. +- Open the database (or `GET /api/dashboard`) — the placement rows reflect the new `gridX` / `gridY` / `gridWidth` / `gridHeight`. + +## Common issues + +| Symptom | Fix | +|---|---| +| Widget snaps back after release | The drop target collided with a `compulsory` widget your admin pinned. Move it to a free cell instead. | +| Can't resize below a certain width | The widget's per-type minimum (set in `widgetRegistry.js`) is enforced. Widen the column count via [Dashboard configuration](10-rename-or-delete.md) if you need more horizontal space. | +| Drag handle is captured by the widget body | Some widgets handle their own click events. Drag from the top edge of the title bar instead. | + +## Reference + +- [Grid layout reference](../../features/grid-layout.md) +- [Permissions reference](../../features/permissions.md) diff --git a/docs/tutorials/user/05-edit-content.md b/docs/tutorials/user/05-edit-content.md new file mode 100644 index 00000000..61fa9ed4 --- /dev/null +++ b/docs/tutorials/user/05-edit-content.md @@ -0,0 +1,77 @@ +--- +sidebar_position: 5 +title: Edit widget content & style +description: Change a widget's content, colours, custom title, or border without removing it. +--- + +# Edit widget content & style + +Each placement carries two layers of configuration: + +- **Content** — type-specific fields (text body, link URL, folder path, …). Changes the widget's payload. +- **Style** — borders, background colour, custom title override, custom icon override. Cosmetic. + +Both are editable post-add without removing the widget. + +## Goal + +Edit a widget you already added — both its content and its visual style. + +## Prerequisites + +- A dashboard you can edit, with at least one widget on it. + +## Steps + +### 1. Right-click the widget + +In edit mode, right-clicking a widget opens a context menu anchored at the cursor: + +![Right-click context menu](../../screenshots/tutorials/user/05-context-menu.png) + +Options: +- **Edit** — opens the per-type configuration form (same as during add). +- **Style** — opens the cosmetic style editor. +- **Remove** — see [Remove a widget](06-remove-widget.md). +- **Cancel** — close the menu. + +### 2. Edit content + +Pick **Edit**. The same `AddWidgetModal` you used to add the widget reopens, this time pre-filled with the current placement's content. Change fields and **Save**. + +![Edit content modal](../../screenshots/tutorials/user/05-edit-content.png) + +### 3. Edit style + +Pick **Style** instead. The dedicated `WidgetStyleEditor` opens with these controls: + +- **Custom title** — overrides the widget's default title (leave blank for default). +- **Custom icon** — registry key, URL, or empty for default. +- **Show title** — toggle the title bar on/off. +- **Border** — colour and thickness. +- **Background colour** — solid, transparent, or theme-bound. + +![Style editor](../../screenshots/tutorials/user/05-style-editor.png) + +The style is persisted as a JSON blob in `placement.styleConfig`; it doesn't touch the widget's content. + +### 4. Save + +Both modals close on **Save** and the change is reflected immediately. + +## Verification + +- Reload the page. Content / style changes are still applied. +- The widget header reflects any custom title; widget background reflects any custom colour. + +## Common issues + +| Symptom | Fix | +|---|---| +| **Edit** is disabled | The widget type has no configuration form (renderer-only widgets). Use **Style** for cosmetics, or remove and re-add. | +| Custom icon doesn't render | The icon string isn't a registered registry key and not a valid URL. See [Dashboard icons capability](../../features/dashboards.md). | +| Title row gone after toggling **Show title** | Re-open the style editor and toggle it back on, OR set a custom title. | + +## Reference + +- [Widgets feature reference](../../features/widgets.md) diff --git a/docs/tutorials/user/06-remove-widget.md b/docs/tutorials/user/06-remove-widget.md new file mode 100644 index 00000000..629561b6 --- /dev/null +++ b/docs/tutorials/user/06-remove-widget.md @@ -0,0 +1,53 @@ +--- +sidebar_position: 6 +title: Remove a widget +description: Take a widget off the dashboard without deleting the dashboard itself. +--- + +# Remove a widget + +Removing a widget deletes its placement row (`oc_mydash_widget_placements`) but doesn't touch the dashboard or the widget's underlying data. + +## Goal + +Remove one widget from a dashboard. + +## Prerequisites + +- A dashboard you can edit. +- The widget must not be marked **compulsory** by an admin template (compulsory widgets can only be removed by an admin). + +## Steps + +### 1. Right-click the widget + +In edit mode, right-click anywhere on the widget. The context menu opens at the cursor. + +![Right-click context menu](../../screenshots/tutorials/user/05-context-menu.png) + +### 2. Click **Remove** + +The menu auto-closes and the placement disappears from the grid. The DELETE call fires immediately; there is no undo. + +![After remove](../../screenshots/tutorials/user/06-after-remove.png) + +:::caution +The remove is destructive. If you're unsure, [edit the style](05-edit-content.md) and toggle **Show title** off — it hides the placement without deleting it. +::: + +## Verification + +- The widget is gone from the grid. +- Reload — it stays gone (the row was deleted server-side). + +## Common issues + +| Symptom | Fix | +|---|---| +| **Remove** is greyed out | The widget is `isCompulsory=1` on this dashboard (admin-pinned). Ask your admin to lift it. | +| Removing throws "permission denied" | Your permission level on the dashboard is `view_only`. | + +## Reference + +- [Widgets feature reference](../../features/widgets.md) +- [Permissions reference](../../features/permissions.md) diff --git a/docs/tutorials/user/07-set-default.md b/docs/tutorials/user/07-set-default.md new file mode 100644 index 00000000..73ea3eb1 --- /dev/null +++ b/docs/tutorials/user/07-set-default.md @@ -0,0 +1,63 @@ +--- +sidebar_position: 7 +title: Set a default dashboard +description: Pin one dashboard so MyDash opens to it automatically. +--- + +# Set a default dashboard + +When you visit `/apps/mydash/` cold (no specific dashboard URL) MyDash needs to pick one to land you on. The default-resolution chain is: + +1. **Your pin** — the dashboard you marked **Default dashboard** in the cog menu. +2. Your primary group's default dashboard (if your admin set one). +3. The org-wide default group's default dashboard. +4. The first available group dashboard. +5. The first available default-group dashboard. +6. The first of your personal dashboards. + +This page is about step 1 — the per-user pin. It always wins over the resolver fallback chain. + +## Goal + +Pin a dashboard as your personal default; verify the ★ marker appears in the sidebar. + +## Steps + +### 1. Open the sidebar and click the cog on the row you want as default + +![Cog action menu](../../screenshots/tutorials/user/03-cog-menu.png) + +### 2. Click **Set as default** + +(If the row is already your default the entry reads **Default dashboard** with a filled-star icon — clicking it now would clear the pin.) + +### 3. Watch the ★ appear next to that row + +The sidebar redraws with a small star to the left of the dashboard name on the pinned row: + +![Sidebar with star marker](../../screenshots/tutorials/user/07-default-marker.png) + +The marker carries a tooltip on hover: *"Default dashboard — opens automatically when you visit MyDash"*. Screen readers announce the same string via `aria-label`. + +### 4. Verify on next cold-load + +Close the tab. Visit `/apps/mydash/` from scratch. You land on the pinned dashboard. + +## Behaviour notes + +- **The marker is reactive.** It moves immediately when you pin a different dashboard or clear the pin. +- **The marker can land on any section.** Personal pin in **MY DASHBOARDS**, group dashboard in the primary-group section, default-group dashboard in **DEFAULT** — all carry the same star. +- **No pin set?** The marker still appears on whichever group dashboard the resolver would land on (the same fallback chain — steps 2-5 above). + +![No pin — fallback to group default](../../screenshots/tutorials/user/07-fallback-marker.png) + +## Common issues + +| Symptom | Fix | +|---|---| +| Star doesn't appear after clicking | Reload the page once. If it's still missing, check that your environment is running development-branch mydash (the marker shipped recently). | +| The star is on the wrong row after a rename | The pin is by UUID, which is stable across renames — so this shouldn't happen. If it does, clear and re-pin. | + +## Reference + +- [Permissions reference](../../features/permissions.md) diff --git a/docs/tutorials/user/08-deep-link.md b/docs/tutorials/user/08-deep-link.md new file mode 100644 index 00000000..f611bce5 --- /dev/null +++ b/docs/tutorials/user/08-deep-link.md @@ -0,0 +1,79 @@ +--- +sidebar_position: 8 +title: Bookmark or share a dashboard URL +description: Each dashboard has its own URL — paste, bookmark, or share it. +--- + +# Bookmark or share a dashboard URL + +Every dashboard has a stable, friendly URL based on its slug-chain. You can paste it into the address bar, bookmark it, or share it with a colleague who has access to that dashboard. + +## URL format + +``` +http://your-nextcloud.example/apps/mydash/ +``` + +For nested dashboards (parent → child), the chain reflects the hierarchy: + +``` +/apps/mydash/finance +/apps/mydash/finance/q1-roadmap +/apps/mydash/finance/q1-roadmap/details +``` + +The slug for a dashboard is auto-derived from its name when you create it; you can set an explicit slug via [Dashboard configuration](10-rename-or-delete.md). + +## Goal + +Open a dashboard via its URL, and verify the URL stays in sync as you switch dashboards inside the app. + +## Steps + +### 1. Find the slug for a dashboard + +The address bar already shows it after the slash: + +![URL bar](../../screenshots/tutorials/user/08-url-bar.png) + +Or check **Dashboard configuration…** in the cog menu — the slug is editable there. + +### 2. Paste the URL into a new tab + +Open a new tab and paste `http://localhost:8080/apps/mydash/`. MyDash loads with that dashboard already active — no extra clicks needed. + +![Deep-link landing](../../screenshots/tutorials/user/08-deep-link-landed.png) + +### 3. Switch dashboards in the sidebar + +Click another row. The URL updates immediately via `history.pushState` — the page doesn't reload but the address bar is now the slug-chain of the new dashboard. + +![URL updates after switch](../../screenshots/tutorials/user/08-url-after-switch.png) + +### 4. Use the browser back / forward buttons + +Each switch pushed a history entry. **Back** returns you to the previous dashboard; **Forward** goes the other way. The dashboard re-resolves via the existing `getDashboardByPath` API, so your active dashboard tracks the URL exactly as expected. + +## Behaviour notes + +- **Stale or unknown slug.** If you visit a URL whose slug-chain doesn't resolve (renamed, deleted, you don't have access), MyDash silently falls back to your default dashboard rather than 404. Old bookmarks always land on something. +- **Renamed parents.** When a parent dashboard is renamed, the canonical slug-chain for its descendants changes. The server normalises the URL on every load — visiting `/apps/mydash/old-parent/child` lands on the same dashboard, and the address bar updates to `/apps/mydash/new-parent/child` automatically (via `history.replaceState`). +- **Dashboard with no slug.** Some dashboards have no slug (NULL slug field). They have no addressable URL — the URL bar shows just `/apps/mydash/` and switching to one doesn't push history. + +## Verification + +- Cold-load the URL → the dashboard renders without a flash of the empty state. +- Sidebar click → URL changes. Browser back → URL reverts. Active dashboard matches the URL. + +## Common issues + +| Symptom | Fix | +|---|---| +| URL doesn't change when switching | The destination dashboard has no slug. Set one in **Dashboard configuration…**. | +| Cold-load lands on the wrong dashboard | The URL slug doesn't resolve to a visible-to-you dashboard — fallback fired. Check the slug spelling. | +| API requests 404 after a deep-link | Shouldn't happen — the catch-all route excludes `/api/...`. If you see this, file an issue with the request URL. | + +## Reference + +- [Dashboards feature reference](../../features/dashboards.md) +- API endpoint: `GET /api/dashboards/by-path/{path}` and `GET /api/dashboards/{uuid}/path` diff --git a/docs/tutorials/user/09-switch-dashboards.md b/docs/tutorials/user/09-switch-dashboards.md new file mode 100644 index 00000000..b661a177 --- /dev/null +++ b/docs/tutorials/user/09-switch-dashboards.md @@ -0,0 +1,48 @@ +--- +sidebar_position: 9 +title: Switch between dashboards +description: Move between your dashboards via the sidebar. +--- + +# Switch between dashboards + +The sidebar is the only switching surface — there's no top toolbar dropdown after `runtime-shell-trim`. + +## Goal + +Switch from the active dashboard to a different one. + +## Steps + +### 1. Open the sidebar + +Click the hamburger button (top-right). The sidebar slides in from the left: + +![Sidebar open](../../screenshots/tutorials/user/02-sidebar-open.png) + +### 2. Click the row of the dashboard you want + +The sidebar closes immediately (per `REQ-SWITCH-002`); the workspace re-renders with the selected dashboard's layout. The URL updates via `history.pushState` so back/forward navigates between dashboards (see [Bookmark or share a dashboard URL](08-deep-link.md)). + +![After switching](../../screenshots/tutorials/user/09-after-switch.png) + +### 3. The active row is highlighted + +When you reopen the sidebar later, the now-active dashboard's row carries the `.active` class (primary-colour highlight) so you can tell at a glance where you are. + +## Behaviour notes + +- **Edit mode resets on switch.** If you were in edit mode on the previous dashboard, your unsaved changes were already auto-saved (per drop), but the new dashboard opens in view mode. Re-enter edit mode via the cog menu on the new active row. +- **Sidebar auto-closes.** This is intentional — keep the sidebar a transient overlay rather than a persistent navigation pane. +- **Keyboard.** With the sidebar open, **Tab** through rows; **Enter** or **Space** activates a row exactly like a click. + +## Common issues + +| Symptom | Fix | +|---|---| +| Sidebar doesn't close after click | A dashboard switch failed (e.g. you no longer have access). Check the toast for the error. | +| Active highlight is on the wrong row | Reload the page once. The store reconciles `activeDashboardId` with the server's truth. | + +## Reference + +- [Dashboards feature reference](../../features/dashboards.md) diff --git a/docs/tutorials/user/10-rename-or-delete.md b/docs/tutorials/user/10-rename-or-delete.md new file mode 100644 index 00000000..5fb2780a --- /dev/null +++ b/docs/tutorials/user/10-rename-or-delete.md @@ -0,0 +1,78 @@ +--- +sidebar_position: 10 +title: Rename or delete a dashboard +description: Edit a dashboard's name, description, icon, slug, or remove it entirely. +--- + +# Rename or delete a dashboard + +Both flows live behind the same modal: **Dashboard configuration…** opens an editor; the same modal carries the **Delete** button. + +## Goal + +Change a dashboard's metadata (name, description, icon, slug) — or delete it. + +## Prerequisites + +- You must own the dashboard (or be an admin) to delete it. +- At least one personal dashboard must remain — MyDash blocks the delete on your last one. + +## Steps + +### 1. Open the cog menu on the row + +![Cog action menu](../../screenshots/tutorials/user/03-cog-menu.png) + +### 2. Click **Dashboard configuration…** + +The configuration modal opens with the current values pre-filled: + +![Dashboard configuration modal](../../screenshots/tutorials/user/10-config-modal.png) + +Editable fields: + +- **Name** — required. +- **Description** — optional. +- **Icon** — registry key, URL, or empty for default. +- **Slug** — affects the dashboard's URL. Auto-derived from the name on first save; you can override. +- **Sort order** — position in its sibling list. +- **Set as default** — toggle (same effect as the cog menu's **Set as default**). + +### 3a. Rename — change fields and click **Save** + +The dashboard updates in place. Slug changes propagate to the URL: if you were viewing `/apps/mydash/old-slug` your address bar updates to the new slug via `history.replaceState`. + +### 3b. Delete — click the **Delete dashboard** button + +A confirmation prompt appears. Confirming issues `DELETE /api/dashboard/{id}`; the row is removed from the sidebar and the active dashboard falls back to your default (or the next available). + +![Delete confirmation](../../screenshots/tutorials/user/10-delete-confirm.png) + +:::danger Permanent +Deleting a dashboard removes it AND every widget placement on it. There is no undo. Tile data, widget configs, and the dashboard row itself are all dropped. +::: + +## Verification (rename) + +- Sidebar row shows the new name. +- URL bar reflects the new slug if you were on that dashboard. +- The change persists across reloads. + +## Verification (delete) + +- Sidebar row is gone. +- Active dashboard is now your default (or the next available). +- `GET /api/dashboards` returns the same row count minus one. + +## Common issues + +| Symptom | Fix | +|---|---| +| **Delete dashboard** is greyed out | This is your last personal dashboard. Create another one first. | +| "Slug must be unique among siblings" on save | Pick a different slug or rename a sibling. | +| Slug field rejected | Slugs allow lowercase ASCII letters, digits, and dashes. The configuration modal validates client-side. | + +## Reference + +- [Dashboards feature reference](../../features/dashboards.md) +- API: `PUT /api/dashboard/{id}`, `DELETE /api/dashboard/{id}` diff --git a/docs/tutorials/user/_category_.json b/docs/tutorials/user/_category_.json new file mode 100644 index 00000000..a1d03550 --- /dev/null +++ b/docs/tutorials/user/_category_.json @@ -0,0 +1,11 @@ +{ + "label": "User guide", + "position": 1, + "collapsible": true, + "collapsed": false, + "link": { + "type": "generated-index", + "title": "User guide", + "description": "Workflows for individual MyDash users — opening the app, customizing dashboards, switching between them, and pinning a default." + } +} diff --git a/playwright.config.ts b/playwright.config.ts index 16f366e4..2bdaf895 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -30,7 +30,13 @@ const baseURL = process.env.NC_BASE_URL ?? 'http://localhost:8080' export default defineConfig({ testDir: './tests/e2e', - testIgnore: ['**/global-setup.ts', '**/fixtures/**'], + // Root-level ignore covers fixtures only. The `docs-screenshots` + // spec is filtered per-project below — keeping it out of the root + // ignore so the `docs-capture` project can still match it. + testIgnore: [ + '**/global-setup.ts', + '**/fixtures/**', + ], timeout: 30_000, expect: { timeout: 5_000, @@ -50,9 +56,24 @@ export default defineConfig({ navigationTimeout: 60_000, }, projects: [ + // Default regression project. Excludes the docs capture spec so + // PR pipelines don't reshoot screenshots on every push. { name: 'chromium', + testIgnore: ['**/docs-screenshots.spec.ts'], use: { ...devices['Desktop Chrome'] }, }, + // Dedicated project for the documentation capture spec. Opt in: + // npx playwright test --project docs-capture + // Output lands in `docs/screenshots/tutorials/{user,admin}/`. + { + name: 'docs-capture', + testMatch: /docs-screenshots\.spec\.ts$/, + use: { + ...devices['Desktop Chrome'], + viewport: { width: 1280, height: 800 }, + }, + timeout: 90_000, + }, ], }) diff --git a/src/components/DashboardConfigModal.vue b/src/components/DashboardConfigModal.vue index fa5fc7cc..71299729 100644 --- a/src/components/DashboardConfigModal.vue +++ b/src/components/DashboardConfigModal.vue @@ -19,6 +19,7 @@ :value="form.name" :label="t('mydash', 'Title')" :placeholder="t('mydash', 'My dashboard')" + data-testid="dashboard-name-input" @update:value="form.name = $event" /> @@ -31,6 +32,7 @@ v-model="form.description" class="dashboard-config__textarea" rows="3" + data-testid="dashboard-description-input" :placeholder="t('mydash', 'What is this dashboard for?')" /> @@ -144,6 +146,7 @@ v-if="canDelete && !isCreate" type="error" :disabled="saving" + data-testid="dashboard-delete-button" @click="onDelete">