mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-06-04 07:22:56 +00:00
🚀 Dash - Homelab Dashboard
A clean, customizable homelab dashboard inspired by CasaOS. Features: - Empty-first dashboard (no demo data) - 3 themes: Light, Dark, CasaOS glassmorphism - Widgets: Clock (multi-timezone), Pi-hole, Memos, Immich, Image - Drag & drop app organization - Grid + list view for apps - Groups with collapse/expand - Proper widget refresh handling - Visual timezone picker - Square app cards with hover effects Stack: Go + Gin + PostgreSQL + Next.js 15 + React 19 + Tailwind CSS + shadcn/ui
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
-- +goose Up
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
CREATE TABLE groups (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name text NOT NULL,
|
||||
sort_order integer NOT NULL,
|
||||
collapsed boolean NOT NULL DEFAULT false,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE asset_files (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
original_name text NOT NULL,
|
||||
stored_name text NOT NULL,
|
||||
mime_type text NOT NULL,
|
||||
size_bytes integer NOT NULL,
|
||||
public_path text NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE services (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
group_id uuid NULL REFERENCES groups(id) ON DELETE SET NULL,
|
||||
name text NOT NULL,
|
||||
icon_url text NULL,
|
||||
icon_asset_id uuid NULL REFERENCES asset_files(id) ON DELETE SET NULL,
|
||||
sort_order integer NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE service_urls (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
service_id uuid NOT NULL REFERENCES services(id) ON DELETE CASCADE,
|
||||
label text NOT NULL,
|
||||
kind text NOT NULL CHECK (kind IN ('local', 'external', 'custom')),
|
||||
url text NOT NULL,
|
||||
sort_order integer NOT NULL,
|
||||
is_primary boolean NOT NULL DEFAULT false,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE widget_instances (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
type text NOT NULL CHECK (type IN ('clock', 'image', 'pihole')),
|
||||
title text NOT NULL,
|
||||
enabled boolean NOT NULL DEFAULT true,
|
||||
sort_order integer NOT NULL,
|
||||
config jsonb NOT NULL DEFAULT '{}',
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE widget_cache (
|
||||
widget_id uuid PRIMARY KEY REFERENCES widget_instances(id) ON DELETE CASCADE,
|
||||
status text NOT NULL CHECK (status IN ('fresh', 'stale', 'error')),
|
||||
data jsonb NULL,
|
||||
error text NULL,
|
||||
fetched_at timestamptz NULL,
|
||||
expires_at timestamptz NULL,
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX groups_sort_order_idx ON groups(sort_order);
|
||||
CREATE INDEX services_group_sort_order_idx ON services(group_id, sort_order);
|
||||
CREATE INDEX services_ungrouped_sort_order_idx ON services(sort_order) WHERE group_id IS NULL;
|
||||
CREATE INDEX service_urls_service_sort_order_idx ON service_urls(service_id, sort_order);
|
||||
CREATE INDEX widget_instances_enabled_sort_order_idx ON widget_instances(enabled, sort_order);
|
||||
CREATE INDEX asset_files_created_at_idx ON asset_files(created_at);
|
||||
|
||||
-- +goose StatementBegin
|
||||
CREATE OR REPLACE FUNCTION set_updated_at()
|
||||
RETURNS trigger AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = now();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
-- +goose StatementEnd
|
||||
|
||||
CREATE TRIGGER groups_set_updated_at BEFORE UPDATE ON groups
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
CREATE TRIGGER services_set_updated_at BEFORE UPDATE ON services
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
CREATE TRIGGER service_urls_set_updated_at BEFORE UPDATE ON service_urls
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
CREATE TRIGGER widget_instances_set_updated_at BEFORE UPDATE ON widget_instances
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
CREATE TRIGGER widget_cache_set_updated_at BEFORE UPDATE ON widget_cache
|
||||
FOR EACH ROW EXECUTE FUNCTION set_updated_at();
|
||||
|
||||
-- +goose Down
|
||||
DROP TRIGGER IF EXISTS widget_cache_set_updated_at ON widget_cache;
|
||||
DROP TRIGGER IF EXISTS widget_instances_set_updated_at ON widget_instances;
|
||||
DROP TRIGGER IF EXISTS service_urls_set_updated_at ON service_urls;
|
||||
DROP TRIGGER IF EXISTS services_set_updated_at ON services;
|
||||
DROP TRIGGER IF EXISTS groups_set_updated_at ON groups;
|
||||
DROP FUNCTION IF EXISTS set_updated_at();
|
||||
DROP TABLE IF EXISTS widget_cache;
|
||||
DROP TABLE IF EXISTS widget_instances;
|
||||
DROP TABLE IF EXISTS service_urls;
|
||||
DROP TABLE IF EXISTS services;
|
||||
DROP TABLE IF EXISTS asset_files;
|
||||
DROP TABLE IF EXISTS groups;
|
||||
@@ -0,0 +1,7 @@
|
||||
-- +goose Up
|
||||
ALTER TABLE widget_instances DROP CONSTRAINT IF EXISTS widget_instances_type_check;
|
||||
ALTER TABLE widget_instances ADD CONSTRAINT widget_instances_type_check CHECK (type IN ('clock', 'image', 'pihole', 'memos'));
|
||||
|
||||
-- +goose Down
|
||||
ALTER TABLE widget_instances DROP CONSTRAINT IF EXISTS widget_instances_type_check;
|
||||
ALTER TABLE widget_instances ADD CONSTRAINT widget_instances_type_check CHECK (type IN ('clock', 'image', 'pihole'));
|
||||
@@ -0,0 +1,7 @@
|
||||
-- +goose Up
|
||||
ALTER TABLE widget_instances DROP CONSTRAINT IF EXISTS widget_instances_type_check;
|
||||
ALTER TABLE widget_instances ADD CONSTRAINT widget_instances_type_check CHECK (type IN ('clock', 'image', 'pihole', 'memos', 'immich'));
|
||||
|
||||
-- +goose Down
|
||||
ALTER TABLE widget_instances DROP CONSTRAINT IF EXISTS widget_instances_type_check;
|
||||
ALTER TABLE widget_instances ADD CONSTRAINT widget_instances_type_check CHECK (type IN ('clock', 'image', 'pihole', 'memos'));
|
||||
@@ -0,0 +1,19 @@
|
||||
-- name: ListGroups :many
|
||||
SELECT id::text, name, sort_order, collapsed, created_at, updated_at
|
||||
FROM groups
|
||||
ORDER BY sort_order ASC, created_at ASC;
|
||||
|
||||
-- name: ListServices :many
|
||||
SELECT id::text, group_id, name, icon_url, icon_asset_id, sort_order, created_at, updated_at
|
||||
FROM services
|
||||
ORDER BY group_id NULLS FIRST, sort_order ASC, created_at ASC;
|
||||
|
||||
-- name: ListServiceURLs :many
|
||||
SELECT id::text, service_id::text, label, kind, url, sort_order, is_primary, created_at, updated_at
|
||||
FROM service_urls
|
||||
ORDER BY service_id, sort_order ASC, created_at ASC;
|
||||
|
||||
-- name: ListWidgets :many
|
||||
SELECT id::text, type, title, enabled, sort_order, config, created_at, updated_at
|
||||
FROM widget_instances
|
||||
ORDER BY sort_order ASC, created_at ASC;
|
||||
Reference in New Issue
Block a user