feat: eine Startseite, und die Sicherung geht in die Nextcloud
STARTSEITE. Wer das Dashboard oeffnet, landete bisher in der Galerie -- mit 228 Kacheln und ohne Antwort auf die erste Frage, die man beim Oeffnen hat: laeuft alles? Jetzt zuerst eine Uebersicht: Zahlen, was heute dazukam, ein Bilderstreifen der letzten Uploads, die letzten acht Schritte aus dem Verlauf, die groessten Ordner, die Zugaenge -- und die ADRESSVORLAGE zum Kopieren, weil das die haeufigste Frage ueberhaupt ist. Die Galerie zieht auf /galerie um. Alles in EINER Antwort (/api/dash/uebersicht, gemessen 14 ms). Vier Einzelabfragen waeren vier Runden ueber den Proxy und koennten auseinander- laufen: Dateizahl aus der einen und letzte Ereignisse aus der naechsten Antwort sind zwei verschiedene Zeitpunkte. Der Router zog dabei nach src/router.ts um. Der Grund ist mehr als Geschmack: die Startseite braucht eine Navigationsfunktion, die anderen Seiten nicht -- und sobald EINE Seite eine Eigenschaft mehr hat, passt die gemeinsame Liste in App.tsx nicht mehr. Genau daran ist der erste Versuch gescheitert (TypeScript hat es gefangen). Als Modulfunktion hat wieder jede Seite dieselbe Signatur. SICHERUNG IN DIE NEXTCLOUD. Bisher lagen die Archive auf derselben Platte wie die Daten -- gegen einen Plattenausfall half das gar nicht. Jetzt gehen sie per WebDAV hinauf, mit curl statt rclone: Nextcloud spricht WebDAV, curl spricht WebDAV, und curl ist ohnehin da. Auch dort wird NACHGEZAEHLT: die Kopie wird zurueckgeholt und die Pruefsumme verglichen. Ein PUT, der 201 sagt, hat nichts bewiesen -- abgeschnittene Uebertragungen sehen genauso aus. Und ausgeduennt wird ebenfalls, sonst waechst die Nextcloud still voll. Die Zugangsdaten liegen NEBEN den Sicherungen (C:\backup\d4rk_media\ nextcloud.txt), nicht im Repo: sie gehoeren zur Maschine, nicht zum Code. Fehlt die Datei, sagt das Skript das einmal und sichert lokal weiter -- eine Sicherung, die wegen eines fehlenden Passworts GAR NICHT stattfindet, waere der schlechtere Tausch. Dabei einmal reingefallen: die Pruefung brach mit "sh: set: Illegal option -" ab, nachdem ein Werkzeug die Datei mit CRLF gespeichert hatte -- die Shell im Container liest dann "set -e\r". Sieht aus wie ein Tippfehler und ist keiner. Das Skript entfernt die Wagenruecklaeufe jetzt selbst. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -805,6 +805,68 @@ dashRoutes.get('/stand', (c) => {
|
||||
return c.json({ medien: m.n, neuest: m.neuest, ereignis: e.letzte })
|
||||
})
|
||||
|
||||
/** Alles fuer die Startseite in EINER Antwort.
|
||||
*
|
||||
* Vier Einzelabfragen beim Oeffnen waeren vier Runden ueber den Proxy, nur um
|
||||
* eine Uebersicht zu zeigen. Und sie koennten auseinanderlaufen: die Zahl der
|
||||
* Dateien aus der einen und die letzten Ereignisse aus der naechsten Antwort
|
||||
* sind zwei verschiedene Zeitpunkte. */
|
||||
dashRoutes.get('/uebersicht', (c) => {
|
||||
const m = db
|
||||
.prepare('SELECT COUNT(*) AS files, COALESCE(SUM(size), 0) AS bytes FROM media')
|
||||
.get() as { files: number; bytes: number }
|
||||
|
||||
const heute = now() - 86_400_000
|
||||
const woche = now() - 7 * 86_400_000
|
||||
|
||||
const neu = db.prepare('SELECT COUNT(*) AS n FROM events WHERE kind = ? AND at > ?')
|
||||
const heuteHoch = (neu.get('upload', heute) as { n: number }).n
|
||||
const wocheHoch = (neu.get('upload', woche) as { n: number }).n
|
||||
|
||||
const letzte = db
|
||||
.prepare('SELECT * FROM events ORDER BY at DESC, id DESC LIMIT 8')
|
||||
.all() as MediaEvent[]
|
||||
|
||||
// Die letzten Bilder fuer eine Streifenvorschau. Nur Bilder: ein PDF als
|
||||
// graue Kachel in einer Uebersicht ist Fuellmaterial.
|
||||
const bilder = (
|
||||
db
|
||||
.prepare(
|
||||
`SELECT * FROM media WHERE mime LIKE 'image/%'
|
||||
ORDER BY updated_at DESC LIMIT 12`,
|
||||
)
|
||||
.all() as Media[]
|
||||
).map((row) => ({ ...row, url: publicUrlFor(row.path) }))
|
||||
|
||||
const ordner = db
|
||||
.prepare(
|
||||
`SELECT CASE WHEN instr(path, '/') > 0
|
||||
THEN substr(path, 1, instr(path, '/') - 1)
|
||||
ELSE '(Wurzel)' END AS folder,
|
||||
COUNT(*) AS files, SUM(size) AS bytes
|
||||
FROM media GROUP BY folder ORDER BY bytes DESC LIMIT 5`,
|
||||
)
|
||||
.all() as { folder: string; files: number; bytes: number }[]
|
||||
|
||||
const tokens = db
|
||||
.prepare('SELECT id, name, prefix, can_delete, last_used_at FROM tokens ORDER BY last_used_at DESC')
|
||||
.all() as Pick<Token, 'id' | 'name' | 'prefix' | 'can_delete' | 'last_used_at'>[]
|
||||
|
||||
return c.json({
|
||||
files: m.files,
|
||||
bytes: m.bytes,
|
||||
heuteHoch,
|
||||
wocheHoch,
|
||||
publicUrl: config.publicUrl,
|
||||
filePrefix: config.filePrefix,
|
||||
letzte,
|
||||
bilder,
|
||||
ordner,
|
||||
tokens,
|
||||
discord: discordConfig().bereit,
|
||||
})
|
||||
})
|
||||
|
||||
dashRoutes.get('/stats', (c) => {
|
||||
const media = db
|
||||
.prepare('SELECT COUNT(*) AS files, COALESCE(SUM(size), 0) AS bytes FROM media')
|
||||
|
||||
Reference in New Issue
Block a user