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
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,49 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.6.5] — 2026-05-17

UI overhaul, part 2 (release 3 of 3): edit page polish, a universal delete
pattern, click-to-view widget data, and an auto-refresh that knows not to wipe
out your work while you're looking at it.

### Added

- **Trash icon on Tiled tiles and Dashboard rows.** One click deletes a dynamic
entry, with a small inline confirm popover anchored to the icon. Static
(Locked) entries show a lock icon in the same slot instead — they can only be
deleted from the edit page, which is the deliberate path for protected entries.
- **Widget data modal.** Clicking the chart icon on a tile now opens a modal
showing just that service's widget data. Faster than expanding the full drawer,
especially on mobile. The chevron drawer still works as before.
- **Auto-refresh pauses while you're interacting.** Open a drawer, the widget
modal, or the changelog popup, and the page won't refresh out from under you.
The refresh timer shows "Refresh paused" while interaction is active and
resumes (counter reset to 0) when you close everything.

### Changed

- **Edit page visual polish.** Tighter input padding and border-radius, section
headings (Identity / URLs & Health / Grouping & Display / Widget), tighter
spacing within sections and looser between, muted helper text. URL fields now
group their health-check checkbox inline below the input instead of floating
it to the side. The "Select Existing / Add New" group selector is now a
tab-style toggle instead of bullet radios.
- **Universal delete UI.** All delete affordances — drawer Delete button, edit
page Delete button, and the new trash icons — use the same small inline
popover with a Confirm button. The typed-name confirmation form is gone;
static entries are protected by being deletable only from the edit page.
- **Lock icon replaces 🔒 emoji on Dashboard rows.** Consistent visual language
with the new Tiled lock indicator. Same meaning: "this entry is locked from
notifier updates; delete from the edit page."
- **Drawer Delete button hidden for static entries.** The tile-level lock icon
signals the constraint; the drawer no longer duplicates it.

### Removed

- **Danger Zone section on the edit page.** Replaced by a single Delete button
at the bottom of the form, using the new popover.

## [0.6.4] — 2026-05-17

UI overhaul, part 2 (release 2 of 3): mobile usability and tile restructure.
Expand Down
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@

## Build Status

Current shipped release: **v0.6.3** (latest tag on `main`)
Current shipped release: **v0.6.4** (latest tag on `main`)

Status:

Expand All @@ -56,8 +56,8 @@ Status:
- v0.6.1 — UI overhaul part 1 (tiled redesign, drawer, shared CSS/JS): DONE
- v0.6.2 — hotfix: restore `toggleRestoreSource`, upload filename feedback: DONE
- v0.6.3 — UI overhaul part 2 foundations (orphan sweep, inline-style cleanup, what's new popup): DONE
- v0.6.4 — UI overhaul part 2: mobile usability + tile icon restructure: IN PROGRESS (on dev)
- v0.6.5 — UI overhaul part 2 (edit page polish + universal delete popover): TBD
- v0.6.4 — UI overhaul part 2: mobile usability + tile icon restructure: DONE
- v0.6.5 — UI overhaul part 2 (edit page polish, universal delete, widget modal, refresh pause): IN PROGRESS (on dev)
- v0.7.0+ — TBD (no scoped features at present)

## Git Workflow
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ through the web UI or directly via API.

## What It Does

- Three dashboard views (table, tiled, compact). Tiled view shows each service as a tile with a per-tile expand drawer for full host, URL, Docker, network, port, exposure, and widget detail. Icons use [Tabler Icons](https://tabler.io/icons) v3.34.0 (loaded via CDN).
- Three dashboard views (table, tiled, compact). Tiled view shows each service as a tile with a per-tile expand drawer for full host, URL, Docker, network, port, exposure, and widget detail. Icons use [Tabler Icons](https://tabler.io/icons) v3.34.0 (loaded via CDN). Clicking the chart icon on a tile opens a widget-data modal without expanding the full drawer.
- **Dashboard view controls (v0.6.0+).** A `Group by` axis selector
(`group` / `stack` / `host`) and a `Show URL-less` filter render
above the service grid on all three views. State is URL-driven, so
Expand All @@ -52,7 +52,7 @@ through the web UI or directly via API.
- Register endpoint for pushing container metadata from external tools.
- SQLite backing store, file-based, no external DB server.
- Daily YAML backups with retention.
- Manual add / edit / delete through the web UI.
- Manual add / edit / delete through the web UI. Tiled tiles and Dashboard rows have a one-click trash icon with an inline confirm popover. Static (Locked) entries show a lock icon and can only be deleted from the edit page.
- Optional Dozzle log link integration.
- Local user accounts with session-based auth.
- Per-entry sort priority within groups; grouping and group sort.
Expand Down
24 changes: 24 additions & 0 deletions routes_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,30 @@ def edit_entry(id):
selected_widget=selected_widget)


@dashboard_bp.route('/api/v1/entries/<int:id>/delete', methods=['POST'])
@login_required
@is_admin_required
def delete_entry_json(id):
"""JSON delete endpoint for JS-triggered deletes (tile trash icon, drawer delete button).

Returns {ok: true} on success. The caller is responsible for DOM cleanup or redirect.
"""
entry = ServiceEntry.query.get_or_404(id)

if entry.widget_id:
other_services = ServiceEntry.query.filter(
ServiceEntry.widget_id == entry.widget_id,
ServiceEntry.id != entry.id
).count()
if other_services == 0:
WidgetValue.query.filter_by(widget_id=entry.widget_id).delete()
Widget.query.filter_by(id=entry.widget_id).delete()

db.session.delete(entry)
db.session.commit()
return jsonify({'ok': True})


@dashboard_bp.route('/api/v1/changelog')
@login_required
def changelog_api():
Expand Down
218 changes: 216 additions & 2 deletions static/css/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@
background-color: var(--color-bg-surface);
color: var(--color-text-secondary);
border: 1px solid #4a5568;
padding: 0.625rem 1rem;
border-radius: var(--radius-input);
padding: 0.5rem 0.75rem;
border-radius: 0.375rem;
font-size: 0.875rem;
width: 100%;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
Expand Down Expand Up @@ -707,3 +707,217 @@
padding: 8px 14px;
}
}

/* ── Delete popover ───────────────────────────────────────── */
.delete-popover {
position: fixed;
z-index: 300;
max-width: 280px;
background-color: var(--color-bg-raised);
border: 1px solid var(--color-border-input);
border-radius: var(--radius-tile);
padding: 10px 14px 12px;
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.65);
}
.delete-popover.hidden { display: none; }
.delete-popover-message {
font-size: 0.85rem;
color: var(--color-text-secondary);
margin-bottom: 10px;
}
.delete-popover-message strong { color: var(--color-text-primary); }
.delete-popover-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
}
.delete-popover-cancel {
font-size: 0.78rem;
padding: 4px 10px;
border-radius: 4px;
border: 1px solid var(--color-border-input);
background: none;
color: var(--color-text-muted);
cursor: pointer;
transition: color 0.15s ease, border-color 0.15s ease;
}
.delete-popover-cancel:hover {
color: var(--color-text-secondary);
border-color: var(--color-text-muted);
}
.delete-popover-confirm {
font-size: 0.78rem;
padding: 4px 10px;
border-radius: 4px;
border: 1px solid var(--color-status-bad);
background-color: var(--color-status-bad);
color: #fff;
cursor: pointer;
transition: opacity 0.15s ease;
}
.delete-popover-confirm:hover { opacity: 0.85; }
@media (max-width: 639px) {
.delete-popover { max-width: calc(100vw - 32px); }
.delete-popover-cancel,
.delete-popover-confirm { padding: 8px 14px; }
}

/* ── Trash / lock icons on tiles and rows ─────────────────── */
.status-icon-trash { color: var(--color-status-muted); }
.tile-status-row button.status-icon-trash:hover {
color: var(--color-status-bad);
opacity: 1;
}

/* ── Widget data modal ────────────────────────────────────── */
.widget-modal {
position: fixed;
inset: 0;
z-index: 500;
display: flex;
align-items: center;
justify-content: center;
}
.widget-modal.hidden { display: none; }

.widget-modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
}

.widget-modal-panel {
position: relative;
z-index: 501;
width: min(480px, calc(100vw - 32px));
max-height: 80vh;
display: flex;
flex-direction: column;
background-color: var(--color-bg-surface);
border: 1px solid var(--color-accent);
border-radius: var(--radius-tile);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
overflow: hidden;
}

.widget-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 18px;
border-bottom: 1px solid var(--color-border);
flex-shrink: 0;
}
.widget-modal-title {
font-size: 0.95rem;
font-weight: 600;
color: var(--color-text-primary);
margin: 0;
}
.widget-modal-close {
background: none;
border: none;
color: var(--color-text-muted);
font-size: 1.4rem;
line-height: 1;
cursor: pointer;
padding: 4px;
min-width: 32px;
min-height: 32px;
display: flex;
align-items: center;
justify-content: center;
transition: color 0.15s ease;
}
.widget-modal-close:hover { color: var(--color-text-secondary); }

.widget-modal-body {
flex: 1;
overflow-y: auto;
padding: 16px 18px;
}
.widget-modal-body .drawer-widget-grid {
grid-template-columns: repeat(3, 1fr);
margin-top: 0;
}
.widget-modal-no-data {
font-size: 0.85rem;
color: var(--color-text-muted);
font-style: italic;
text-align: center;
padding: 20px 0;
}

@media (max-width: 480px) {
.widget-modal-header,
.widget-modal-body { padding: 10px 14px; }
.widget-modal-close { min-width: 44px; min-height: 44px; font-size: 1.6rem; }
.widget-modal-body .drawer-widget-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 360px) {
.widget-modal-body .drawer-widget-grid { grid-template-columns: 1fr; }
}

/* ── Edit page section headings ───────────────────────────── */
.edit-section-heading {
font-size: 0.85rem;
font-weight: 600;
color: var(--color-text-muted);
margin-top: 1.75rem;
margin-bottom: 0.75rem;
padding-bottom: 4px;
border-bottom: 1px solid var(--color-border);
letter-spacing: 0.03em;
}
.edit-section-heading:first-child { margin-top: 0; }

.edit-section-fields { display: flex; flex-direction: column; gap: 0.75rem; }

.edit-helper {
font-size: 0.75rem;
color: var(--color-text-muted);
font-style: italic;
margin-top: 3px;
}

/* ── Group mode tab toggle ────────────────────────────────── */
.group-mode-tabs {
display: inline-flex;
border: 1px solid var(--color-border-input);
border-radius: 6px;
overflow: hidden;
}
.group-tab {
display: inline-block;
padding: 5px 16px;
font-size: 0.82rem;
color: var(--color-text-muted);
cursor: pointer;
background: none;
transition: background-color 0.15s ease, color 0.15s ease;
user-select: none;
line-height: 1.4;
}
.group-tab + .group-tab { border-left: 1px solid var(--color-border-input); }

#select_existing:checked + .group-tab { background-color: var(--color-accent); color: #fff; }
#add_new:checked + .group-tab { background-color: var(--color-accent); color: #fff; }

/* ── Edit page delete button ──────────────────────────────── */
.edit-delete-btn {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 0.82rem;
padding: 5px 14px;
border-radius: 4px;
border: 1px solid var(--color-border-input);
background: none;
color: var(--color-text-muted);
cursor: pointer;
transition: border-color 0.15s ease, color 0.15s ease;
}
.edit-delete-btn:hover {
border-color: var(--color-status-bad);
color: var(--color-status-bad);
}
Loading
Loading