mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-10-29 08:04:58 +00:00
feat(ui): phase 5 — graphs, widgets, theming, PWA, boards, i18n
P5a: 24h uptime bar strip + latency chart on monitor detail, iframe
embed widget, generic JSON-path widget, accent/wallpaper/custom CSS
settings, PWA manifest + service worker, demo GIF.
P5b: transport picker + per-transport fields + test button in
settings; per-monitor alert-rule fields in dialogs.
P5c: container cpu/mem columns, SMART/ZFS/GPU sections on system
detail, subdomains section on domain detail.
P5d: login/setup page + auth middleware client handling, board
switcher + /b/:slug routes, full string extraction to typed t() keys
with {param} interpolation, complete cs locale, language select.
Authored By: TDvorak <[email protected]>
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
f3143af351
commit
e0ef60eca6
49 files changed
+2131
-426
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { ChevronDown, GripVertical, Pencil, Trash2 } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { t } from "@/i18n";
|
||||
import { useItemDnd, useSectionDnd } from "@/board/dnd";
|
||||
import { IconImg, ItemAnchor, StatusChip, hostOf } from "@/board/primitives";
|
||||
import { WidgetContent } from "@/widgets";
|
||||
@@ -28,7 +29,7 @@ function SectionView({ section, onToggle, onDelete, children }: SectionViewProps
|
||||
<div className="group/sec flex cursor-pointer select-none items-center gap-3 px-0.5 py-1">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Drag section"
|
||||
aria-label={t("renderer.dragSection")}
|
||||
className="cursor-grab text-text-faint opacity-0 transition-opacity group-hover/sec:opacity-100 active:cursor-grabbing"
|
||||
{...dnd.attributes}
|
||||
{...dnd.listeners}
|
||||
@@ -54,7 +55,7 @@ function SectionView({ section, onToggle, onDelete, children }: SectionViewProps
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Delete section"
|
||||
aria-label={t("renderer.deleteSection")}
|
||||
onClick={onDelete}
|
||||
className="text-text-faint opacity-0 transition-opacity hover:text-destructive group-hover/sec:opacity-100"
|
||||
>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ChevronDown, GripVertical, Pencil, Trash2 } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { t } from "@/i18n";
|
||||
import { useItemDnd, useSectionDnd } from "@/board/dnd";
|
||||
import { IconImg, ItemAnchor, StatusChip, hostOf } from "@/board/primitives";
|
||||
import { WidgetContent } from "@/widgets";
|
||||
@@ -32,7 +33,7 @@ function SectionView({ section, onToggle, onDelete, children }: SectionViewProps
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Drag section"
|
||||
aria-label={t("renderer.dragSection")}
|
||||
className="cursor-grab text-text-faint opacity-0 transition-opacity group-hover/sec:opacity-100 active:cursor-grabbing"
|
||||
{...dnd.attributes}
|
||||
{...dnd.listeners}
|
||||
@@ -41,7 +42,7 @@ function SectionView({ section, onToggle, onDelete, children }: SectionViewProps
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Delete section"
|
||||
aria-label={t("renderer.deleteSection")}
|
||||
onClick={onDelete}
|
||||
className="text-text-faint opacity-0 transition-opacity hover:text-destructive group-hover/sec:opacity-100"
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { ChevronDown, GripVertical, Pencil, Trash2 } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { t } from "@/i18n";
|
||||
import { useItemDnd, useSectionDnd } from "@/board/dnd";
|
||||
import { useBoard } from "@/board/store";
|
||||
import { IconImg, ItemAnchor, hostOf } from "@/board/primitives";
|
||||
@@ -37,10 +38,10 @@ function BoardView({ children }: BoardViewProps) {
|
||||
}).format(now);
|
||||
|
||||
const cells: [string, string, string][] = [
|
||||
["Local time", time, date.split(",")[0] ?? ""],
|
||||
[t("renderer.localTime"), time, date.split(",")[0] ?? ""],
|
||||
["Services", String(services.length), `${board.sections.length} sections`],
|
||||
["Up", String(up), "responding"],
|
||||
["Down", String(down), down === 0 ? "all green" : "offline"],
|
||||
[t("renderer.down"), String(down), down === 0 ? t("renderer.allGreen") : t("renderer.offline")],
|
||||
["Widgets", String(widgets.length), "registered"],
|
||||
];
|
||||
|
||||
@@ -77,7 +78,7 @@ function SectionView({ section, index, onToggle, onDelete, children }: SectionVi
|
||||
<div className="group/sec mb-0.5 flex cursor-pointer select-none items-baseline gap-3.5 border-b border-border-strong pb-2.5">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Drag section"
|
||||
aria-label={t("renderer.dragSection")}
|
||||
className="cursor-grab self-center text-text-faint opacity-0 transition-opacity group-hover/sec:opacity-100 active:cursor-grabbing"
|
||||
{...dnd.attributes}
|
||||
{...dnd.listeners}
|
||||
@@ -96,7 +97,7 @@ function SectionView({ section, index, onToggle, onDelete, children }: SectionVi
|
||||
{section.name}
|
||||
</h2>
|
||||
<span className="font-mono text-[11px] text-text-faint">
|
||||
{section.items.length} {section.items.length === 1 ? "entry" : "entries"}
|
||||
{section.items.length} {t(section.items.length === 1 ? "renderer.entry" : "renderer.entries")}
|
||||
</span>
|
||||
<ChevronDown
|
||||
size={14}
|
||||
@@ -105,7 +106,7 @@ function SectionView({ section, index, onToggle, onDelete, children }: SectionVi
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Delete section"
|
||||
aria-label={t("renderer.deleteSection")}
|
||||
onClick={onDelete}
|
||||
className="self-center text-text-faint opacity-0 transition-opacity hover:text-destructive group-hover/sec:opacity-100"
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { ChevronDown, Pencil, Trash2 } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { t } from "@/i18n";
|
||||
import { useItemDnd, useSectionDnd } from "@/board/dnd";
|
||||
import { useBoard } from "@/board/store";
|
||||
import { IconImg, ItemAnchor, hostOf } from "@/board/primitives";
|
||||
@@ -42,7 +43,7 @@ function BoardView({ children }: BoardViewProps) {
|
||||
const panels: { head: string; v: string; s: string; live?: boolean; wide?: boolean }[] = [
|
||||
{ head: "clock --local", v: time, s: date, live: true, wide: true },
|
||||
{ head: "services --up", v: String(up), s: `of ${probed} probed` },
|
||||
{ head: "services --down", v: String(down), s: down === 0 ? "all green" : "needs attention" },
|
||||
{ head: "services --down", v: String(down), s: down === 0 ? t("renderer.allGreen") : t("renderer.needsAttention") },
|
||||
{ head: "widgets", v: String(widgets.length), s: "registered" },
|
||||
{ head: "endpoints", v: String(endpoints), s: "urls indexed" },
|
||||
{ head: "uptime --probes", v: probed > 0 ? `${Math.round((up / probed) * 100)}%` : "—", s: `${up}/${probed} responding` },
|
||||
@@ -56,7 +57,7 @@ function BoardView({ children }: BoardViewProps) {
|
||||
<span className="font-medium text-text-dim">{services.length} services</span>
|
||||
{` indexed · `}
|
||||
<span className="text-up">{up} up</span>
|
||||
{down > 0 ? <span>{` · `}<span className="text-down">{down} down</span></span> : ` · all green`}
|
||||
{down > 0 ? <span>{` · `}<span className="text-down">{t("renderer.downCount", { n: down })}</span></span> : ` · ${t("renderer.allGreen")}`}
|
||||
{` · `}
|
||||
<span className="font-medium text-text-dim">{widgets.length} widgets</span>
|
||||
{`\ndrag rows to reorder · click rows with multiple endpoints to choose`}
|
||||
@@ -70,7 +71,7 @@ function BoardView({ children }: BoardViewProps) {
|
||||
>
|
||||
<div className="flex items-center gap-1.5 border-b border-border px-3 py-2 text-[10px] uppercase tracking-[0.1em] text-text-faint">
|
||||
{p.head}
|
||||
{p.live ? <span className="ml-auto text-[9px] text-up">● live</span> : null}
|
||||
{p.live ? <span className="ml-auto text-[9px] text-up">● {t("renderer.live")}</span> : null}
|
||||
</div>
|
||||
<div className="px-3 py-3">
|
||||
<div className={cn("font-medium leading-none tracking-tight tabular-nums", p.wide ? "text-[26px]" : "text-[22px]")}>{p.v}</div>
|
||||
@@ -108,7 +109,7 @@ function SectionView({ section, onToggle, onDelete, children }: SectionViewProps
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Drag section"
|
||||
aria-label={t("renderer.dragSection")}
|
||||
className="cursor-grab text-text-faint opacity-0 transition-opacity group-hover/sec:opacity-100 active:cursor-grabbing"
|
||||
{...dnd.attributes}
|
||||
{...dnd.listeners}
|
||||
@@ -117,7 +118,7 @@ function SectionView({ section, onToggle, onDelete, children }: SectionViewProps
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Delete section"
|
||||
aria-label={t("renderer.deleteSection")}
|
||||
onClick={onDelete}
|
||||
className="text-text-faint opacity-0 transition-opacity hover:text-destructive group-hover/sec:opacity-100"
|
||||
>
|
||||
|
||||
Reference in new issue
Block a user