Compare commits

...

7 Commits

Author SHA1 Message Date
opencode f3b4fa553b docs: fill v0.9.2 release notes commit hash
Build & Push Docker Image / build (push) Successful in 1m47s
2026-08-05 07:44:48 +02:00
opencode e164d51574 feat: compare limit 9, format-aware import placeholder, help links
- Raise compare limit from 8 to 9 (server + client, toast on overflow)
- Make import textarea placeholder match selected format (CSV/JSON/YAML/auto)
- Add GuideHelp links to admin create/edit user dialogs and tool import dialog
2026-08-05 07:40:47 +02:00
opencode e66a332270 fix(docs): release file without releases/ prefix to avoid double path
Build & Push Docker Image / build (push) Successful in 3m1s
index.json stored file='releases/vX.Y.Z.de.md' but ReleaseNoteView
prepends 'releases/' again, producing releases/releases/... (404).
Store the bare filename so the view resolves docs/releases/vX.Y.Z.de.md.
2026-08-05 00:32:56 +02:00
opencode 48d25b668d docs: fill release notes commit hash
Build & Push Docker Image / build (push) Failing after 57s
2026-08-05 00:15:00 +02:00
opencode 6eb3dfd8f9 docs: cut to v0.9.0 with unified .de/.en naming
- Remove all prior release notes and version snapshots (v0.6.0-v0.8.6)
- Rename handbook to *.de.md; keep *.en.md
- New release notes v0.9.0.de.md/.en.md with v0.9.0 snapshot
- generate-docs.mjs: .de/.en convention incl. search index and en-fallback
- docs.tsx: release fallback now uses version.de.md
- Wording: neutral tagline, anonymous user, no gender forms
- Add docs/examples/import-tools.yaml and link from administration docs
2026-08-05 00:13:41 +02:00
opencode ca980fce09 docs: add v0.8.6 release notes
Build & Push Docker Image / build (push) Successful in 2m16s
2026-08-04 23:13:09 +02:00
opencode 8f2fd89847 feat(import): bulk tool import (CSV/JSON/YAML) for admins; NetBox-style help button
Build & Push Docker Image / build (push) Successful in 2m49s
- Add POST /admin/tools/import with format auto-detect, CSV delimiters
  (comma/semicolon/tab), per-row validation via CreateToolBody, bulk insert,
  audit log entries per imported tool; gated by new 'tool-import' feature
  flag (premium/enterprise; admins always pass)
- Add tool-import-dialog UI (format tabs, delimiter select, textarea, file
  upload, result/error list) behind hasFeature('tool-import')
- Replace FieldHelp question marks and bare GuideHelp links with a NetBox-style
  'Hilfe/Help' outline button (HelpCircle + text) in form headers only
- Sync locales to 482 keys per language (de/en), update handbook docs
  (administration import section, index/plaene feature tables), regenerate
  API client + zod schemas, add yaml dependency
2026-08-04 23:09:11 +02:00
187 changed files with 3668 additions and 3729 deletions
+1
View File
@@ -23,6 +23,7 @@
"openid-client": "6.8.4",
"pino": "10.3.1",
"pino-http": "11.0.0",
"yaml": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
@@ -2,8 +2,8 @@ import { type Request, type Response, type NextFunction } from "express";
const TIER_FEATURES: Record<string, string[]> = {
free: ["browse", "rate", "search"],
premium: ["browse", "rate", "search", "similar-tools", "costs", "redundancy", "analytics-advanced", "trash", "compare", "watchlist"],
enterprise: ["browse", "rate", "search", "similar-tools", "costs", "redundancy", "analytics-advanced", "trash", "compare", "watchlist", "sso", "audit-export", "api-access"],
premium: ["browse", "rate", "search", "similar-tools", "costs", "redundancy", "analytics-advanced", "trash", "compare", "watchlist", "tool-import"],
enterprise: ["browse", "rate", "search", "similar-tools", "costs", "redundancy", "analytics-advanced", "trash", "compare", "watchlist", "tool-import", "sso", "audit-export", "api-access"],
};
export function getEntitlements(tier: string | undefined, role: string | undefined): string[] {
+183
View File
@@ -1,9 +1,12 @@
import { Router, type IRouter } from "express";
import { eq, and, sql } from "drizzle-orm";
import { z } from "zod";
import { parse as parseYaml } from "yaml";
import { db, toolsTable, ratingsTable, toolCostsTable, toolRelationsTable } from "@workspace/db";
import { requireAdmin } from "../middleware/auth";
import { requireFeature } from "../middleware/feature";
import { writeAuditLog } from "../lib/audit";
import { CreateToolBody } from "@workspace/api-zod";
const EvaluateBody = z.object({
toolId: z.coerce.number().int().positive(),
@@ -219,4 +222,184 @@ router.post("/admin/redundancy/evaluate", requireAdmin, async (req, res): Promis
res.json({ ok: true });
});
const ToolImportBody = z.object({
format: z.enum(["auto", "csv", "json", "yaml"]).default("auto"),
delimiter: z.enum(["auto", "comma", "semicolon", "tab"]).default("auto"),
data: z.string().min(1),
});
const TOOL_FIELDS = ["name", "description", "category", "websiteUrl", "iconUrl", "features", "tags"] as const;
function splitList(value: unknown): string[] {
if (Array.isArray(value)) {
return value.map((v) => String(v).trim()).filter(Boolean);
}
if (typeof value === "string") {
return value
.split(/[|;,]/)
.map((v) => v.trim())
.filter(Boolean);
}
return [];
}
function normalizeTool(raw: Record<string, unknown>): Record<string, unknown> {
const out: Record<string, unknown> = {};
for (const key of TOOL_FIELDS) {
if (key in raw) out[key] = raw[key];
}
if ("features" in out) out.features = splitList(out.features);
if ("tags" in out) out.tags = splitList(out.tags);
return out;
}
function parseCsv(data: string, delimiter: string): Record<string, unknown>[] {
let sep = ",";
if (delimiter === "semicolon") sep = ";";
else if (delimiter === "tab") sep = "\t";
else if (delimiter === "auto") {
const firstLine = data.split(/\r?\n/)[0] ?? "";
const candidates = [",", ";", "\t"];
const counts = candidates.map((c) => (firstLine.match(new RegExp(`\\${c === "\t" ? "\\t" : c}`, "g")) ?? []).length);
const max = Math.max(...counts);
if (max > 0) sep = candidates[counts.indexOf(max)];
}
const lines = data.split(/\r?\n/).filter((l) => l.trim().length > 0);
if (lines.length === 0) return [];
const header = parseCsvLine(lines[0], sep);
const rows: Record<string, unknown>[] = [];
for (let i = 1; i < lines.length; i++) {
const cells = parseCsvLine(lines[i], sep);
const row: Record<string, unknown> = {};
header.forEach((h, idx) => {
const key = h.trim();
if (key) row[key] = cells[idx] ?? "";
});
rows.push(row);
}
return rows;
}
function parseCsvLine(line: string, sep: string): string[] {
const cells: string[] = [];
let cur = "";
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const ch = line[i];
if (inQuotes) {
if (ch === '"') {
if (line[i + 1] === '"') {
cur += '"';
i++;
} else {
inQuotes = false;
}
} else {
cur += ch;
}
} else if (ch === '"') {
inQuotes = true;
} else if (ch === sep) {
cells.push(cur);
cur = "";
} else {
cur += ch;
}
}
cells.push(cur);
return cells;
}
router.post(
"/admin/tools/import",
requireAdmin,
requireFeature("tool-import"),
async (req, res): Promise<void> => {
const parsed = ToolImportBody.safeParse(req.body);
if (!parsed.success) {
res.status(400).json({ error: parsed.error.message });
return;
}
const { format, delimiter, data } = parsed.data;
let rawRows: Record<string, unknown>[] = [];
let effectiveFormat = format;
if (format === "auto") {
const trimmed = data.trimStart();
if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
effectiveFormat = "json";
} else if (trimmed.includes(":") && !trimmed.startsWith("\"")) {
effectiveFormat = "yaml";
} else {
effectiveFormat = "csv";
}
}
try {
if (effectiveFormat === "json") {
const json = JSON.parse(data);
if (Array.isArray(json)) {
rawRows = json.filter((r) => r && typeof r === "object") as Record<string, unknown>[];
} else if (json && typeof json === "object") {
rawRows = [json as Record<string, unknown>];
} else {
res.status(400).json({ error: "JSON data must be an object or an array of objects" });
return;
}
} else if (effectiveFormat === "yaml") {
const yaml = parseYaml(data);
if (Array.isArray(yaml)) {
rawRows = yaml.filter((r) => r && typeof r === "object") as Record<string, unknown>[];
} else if (yaml && typeof yaml === "object") {
rawRows = [yaml as Record<string, unknown>];
} else {
res.status(400).json({ error: "YAML data must be an object or a list of objects" });
return;
}
} else {
rawRows = parseCsv(data, delimiter);
}
} catch (err) {
res.status(400).json({ error: `Failed to parse ${effectiveFormat} data: ${(err as Error).message}` });
return;
}
const errors: { row: number; error: string }[] = [];
const validRows: { name: string; description: string; category: string; websiteUrl?: string; iconUrl?: string; features: string[]; tags: string[]; createdBy: string }[] = [];
const user = req.session.user!;
const createdBy = user.preferred_username || user.name || user.sub;
rawRows.forEach((raw, idx) => {
const normalized = normalizeTool(raw);
const check = CreateToolBody.safeParse(normalized);
if (!check.success) {
errors.push({ row: idx + 1, error: check.error.message });
return;
}
validRows.push({
name: check.data.name,
description: check.data.description,
category: check.data.category,
websiteUrl: check.data.websiteUrl,
iconUrl: check.data.iconUrl,
features: check.data.features ?? [],
tags: check.data.tags ?? [],
createdBy,
});
});
let imported = 0;
if (validRows.length > 0) {
const inserted = await db.insert(toolsTable).values(validRows).returning({ id: toolsTable.id, name: toolsTable.name, category: toolsTable.category });
imported = inserted.length;
for (const tool of inserted) {
await writeAuditLog(req, "tool", tool.id, "create", { name: tool.name, category: tool.category, source: "import" });
}
}
res.json({ imported, total: rawRows.length, errors });
},
);
export default router;
+2 -2
View File
@@ -268,8 +268,8 @@ router.get("/compare", requireFeature("compare"), async (req, res): Promise<void
res.status(400).json({ error: "Provide at least one ids value, e.g. ?ids=1,2,3" });
return;
}
if (ids.length > 8) {
res.status(400).json({ error: "Maximum of 8 tools can be compared" });
if (ids.length > 9) {
res.status(400).json({ error: "Maximum of 9 tools can be compared" });
return;
}
const tools = await db.select().from(toolsTable).where(and(isNull(toolsTable.deletedAt), inArray(toolsTable.id, ids)));
@@ -15,7 +15,6 @@ import {
import { useQueryClient } from "@tanstack/react-query";
import { useAuth } from "@/hooks/use-auth";
import { Button } from "@/components/ui/button";
import { GuideHelp } from "@/components/guide-help";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
@@ -24,12 +23,13 @@ import { Checkbox } from "@/components/ui/checkbox";
import { Skeleton } from "@/components/ui/skeleton";
import { useToast } from "@/hooks/use-toast";
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
import { Search, Trash2, ExternalLink, Pencil, Star } from "lucide-react";
import { ToolImportDialog } from "@/components/tool-import-dialog";
import { Search, Trash2, ExternalLink, Pencil, Star, Upload } from "lucide-react";
import { format } from "date-fns";
export function AdminToolsTab() {
const { t } = useTranslation();
const { isAdmin, isLoading: authLoading } = useAuth();
const { isAdmin, hasFeature, isLoading: authLoading } = useAuth();
const { toast } = useToast();
const queryClient = useQueryClient();
@@ -37,6 +37,7 @@ export function AdminToolsTab() {
const [search, setSearch] = useState("");
const [selected, setSelected] = useState<Set<number>>(new Set());
const [confirmTrash, setConfirmTrash] = useState(false);
const [importOpen, setImportOpen] = useState(false);
useEffect(() => {
const t = setTimeout(() => setSearch(searchInput), 300);
@@ -106,15 +107,22 @@ export function AdminToolsTab() {
<Card>
<CardHeader className="flex flex-row items-center justify-between gap-4 flex-wrap">
<div className="space-y-1.5">
<CardTitle className="flex items-center gap-2">
{t("adminTools.allTools")}
<GuideHelp guide="administration" label={t("adminTools.allTools")} />
</CardTitle>
<CardTitle>{t("adminTools.allTools")}</CardTitle>
<CardDescription>
{selectedIds.length > 0 ? t("adminTools.selected", { count: selectedIds.length }) : t("adminTools.toolCount", { count: allTools.length })}
</CardDescription>
</div>
<div className="flex items-center gap-2 flex-wrap">
{hasFeature("tool-import") && (
<Button
size="sm"
variant="outline"
onClick={() => setImportOpen(true)}
data-testid="button-tool-import"
>
<Upload className="w-4 h-4 mr-2" /> {t("adminTools.importButton")}
</Button>
)}
<div className="relative">
<Search className="w-4 h-4 absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" />
<Input
@@ -233,6 +241,15 @@ export function AdminToolsTab() {
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<ToolImportDialog
open={importOpen}
onOpenChange={setImportOpen}
onImported={() => {
toast({ title: t("adminTools.toastImportDone") });
invalidate();
}}
/>
</Card>
);
}
@@ -1,32 +0,0 @@
import { HelpCircle } from "lucide-react";
import { Link } from "wouter";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
export function FieldHelp({
schema,
field,
children,
}: {
schema: string;
field: string;
children?: React.ReactNode;
}) {
const label = children ?? field;
return (
<Tooltip>
<TooltipTrigger asChild>
<Link
href={`/docs/reference/schemas/${schema}#${field}`}
target="_blank"
rel="noreferrer"
aria-label={`Help: ${label}`}
data-testid={`help-${schema}-${field}`}
className="inline-flex shrink-0 text-muted-foreground hover:text-foreground transition-colors"
>
<HelpCircle className="h-3.5 w-3.5" />
</Link>
</TooltipTrigger>
<TooltipContent>{label} Details in der Dokumentation</TooltipContent>
</Tooltip>
);
}
@@ -1,5 +1,7 @@
import { BookOpen } from "lucide-react";
import { HelpCircle } from "lucide-react";
import { Link } from "wouter";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
export function GuideHelp({
@@ -9,21 +11,24 @@ export function GuideHelp({
guide: string;
label: string;
}) {
const { t } = useTranslation();
return (
<Tooltip>
<TooltipTrigger asChild>
<Button asChild variant="outline" size="sm" className="gap-1.5 text-muted-foreground">
<Link
href={`/docs/handbook/${guide}`}
target="_blank"
rel="noreferrer"
aria-label={`Help: ${label}`}
aria-label={`${t("common.help")}: ${label}`}
data-testid={`guide-${guide}`}
className="inline-flex shrink-0 text-muted-foreground hover:text-foreground transition-colors"
>
<BookOpen className="h-3.5 w-3.5" />
<HelpCircle className="h-4 w-4" />
{t("common.help")}
</Link>
</Button>
</TooltipTrigger>
<TooltipContent>{label} Anleitung in der Dokumentation</TooltipContent>
<TooltipContent>{label} {t("common.guideTooltip")}</TooltipContent>
</Tooltip>
);
}
@@ -0,0 +1,196 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useImportTools, type ToolImportResponse } from "@workspace/api-client-react";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Label } from "@/components/ui/label";
import { GuideHelp } from "@/components/guide-help";
import { Upload, FileUp, CheckCircle2, AlertCircle } from "lucide-react";
type Format = "auto" | "csv" | "json" | "yaml";
type Delimiter = "auto" | "comma" | "semicolon" | "tab";
export function ToolImportDialog({
open,
onOpenChange,
onImported,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
onImported: () => void;
}) {
const { t } = useTranslation();
const importTools = useImportTools();
const [format, setFormat] = useState<Format>("auto");
const [delimiter, setDelimiter] = useState<Delimiter>("auto");
const [data, setData] = useState("");
const [result, setResult] = useState<ToolImportResponse | null>(null);
function handleFile(file: File) {
const reader = new FileReader();
reader.onload = () => {
setData(String(reader.result ?? ""));
};
reader.readAsText(file);
}
function handleImport() {
importTools.mutate(
{ data: { format, delimiter, data } },
{
onSuccess: (res) => {
setResult(res);
if (res.imported > 0) onImported();
},
},
);
}
function handleClose(open: boolean) {
if (!importTools.isPending) {
onOpenChange(open);
if (!open) {
setData("");
setResult(null);
setFormat("auto");
setDelimiter("auto");
}
}
}
const canSubmit = data.trim().length > 0 && !importTools.isPending;
const placeholder =
format === "csv"
? t("adminTools.importTextareaPlaceholderCsv")
: format === "json"
? t("adminTools.importTextareaPlaceholderJson")
: format === "yaml"
? t("adminTools.importTextareaPlaceholderYaml")
: t("adminTools.importTextareaPlaceholderAuto");
return (
<Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-2xl">
<DialogHeader className="flex flex-row items-center justify-between gap-2">
<DialogTitle className="flex items-center gap-2">
<Upload className="w-4 h-4" />
{t("adminTools.importTitle")}
</DialogTitle>
<GuideHelp guide="administration" label={t("adminTools.importTitle")} />
</DialogHeader>
<div className="space-y-4 py-2">
<p className="text-sm text-muted-foreground">{t("adminTools.importSub")}</p>
<Tabs value={format} onValueChange={(v) => setFormat(v as Format)}>
<TabsList className="w-full grid grid-cols-4">
<TabsTrigger value="auto">{t("adminTools.importFormatAuto")}</TabsTrigger>
<TabsTrigger value="csv">CSV</TabsTrigger>
<TabsTrigger value="json">JSON</TabsTrigger>
<TabsTrigger value="yaml">YAML</TabsTrigger>
</TabsList>
<TabsContent value="auto" className="text-xs text-muted-foreground pt-2">
{t("adminTools.importFormatAutoSub")}
</TabsContent>
<TabsContent value="csv" className="pt-2 space-y-3">
<div className="space-y-2">
<Label>{t("adminTools.importDelimiter")}</Label>
<Select value={delimiter} onValueChange={(v) => setDelimiter(v as Delimiter)}>
<SelectTrigger className="w-52">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="auto">{t("adminTools.importDelimiterAuto")}</SelectItem>
<SelectItem value="comma">{t("adminTools.importDelimiterComma")}</SelectItem>
<SelectItem value="semicolon">{t("adminTools.importDelimiterSemicolon")}</SelectItem>
<SelectItem value="tab">{t("adminTools.importDelimiterTab")}</SelectItem>
</SelectContent>
</Select>
</div>
<p className="text-xs text-muted-foreground">{t("adminTools.importCsvSub")}</p>
</TabsContent>
<TabsContent value="json" className="pt-2 space-y-3">
<p className="text-xs text-muted-foreground">{t("adminTools.importJsonSub")}</p>
</TabsContent>
<TabsContent value="yaml" className="pt-2 space-y-3">
<p className="text-xs text-muted-foreground">{t("adminTools.importYamlSub")}</p>
</TabsContent>
</Tabs>
<div className="flex items-center gap-3">
<Button type="button" variant="outline" size="sm" className="gap-2" onClick={() => document.getElementById("tool-import-file")?.click()}>
<FileUp className="w-4 h-4" />
{t("adminTools.importFile")}
</Button>
<input
id="tool-import-file"
type="file"
accept=".csv,.json,.yaml,.yml,.txt,text/plain,application/json"
className="hidden"
onChange={(e) => {
const f = e.target.files?.[0];
if (f) handleFile(f);
e.target.value = "";
}}
/>
</div>
<Textarea
value={data}
onChange={(e) => setData(e.target.value)}
placeholder={placeholder}
className="min-h-[220px] font-mono text-sm"
data-testid="input-tool-import-data"
/>
{importTools.isError && (
<div className="flex items-start gap-2 rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2 text-sm text-destructive">
<AlertCircle className="w-4 h-4 mt-0.5 shrink-0" />
<span>{importTools.error?.data?.error ?? importTools.error?.message}</span>
</div>
)}
{result && (
<div className="space-y-2">
<div className="flex items-center gap-2 rounded-md border border-primary/20 bg-primary/5 px-3 py-2 text-sm">
<CheckCircle2 className="w-4 h-4 text-primary shrink-0" />
<span>{t("adminTools.importDone", { count: result.imported, total: result.total })}</span>
</div>
{result.errors.length > 0 && (
<div className="space-y-1">
<p className="text-sm font-medium flex items-center gap-1.5">
<AlertCircle className="w-4 h-4 text-amber-500" />
{t("adminTools.importErrors", { count: result.errors.length })}
</p>
<div className="max-h-40 overflow-y-auto rounded-md border bg-muted/40 divide-y divide-border">
{result.errors.map((err, i) => (
<div key={i} className="flex items-start gap-2 px-3 py-1.5 text-xs">
<span className="font-mono text-muted-foreground shrink-0">
{t("adminTools.importErrorRow", { row: err.row })}:
</span>
<span className="text-foreground break-words">{err.error}</span>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
<DialogFooter>
<Button variant="outline" onClick={() => handleClose(false)} disabled={importTools.isPending}>
{t("common.cancel")}
</Button>
<Button onClick={handleImport} disabled={!canSubmit}>
{importTools.isPending ? t("adminTools.importing") : t("adminTools.importButton")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
@@ -164,11 +164,9 @@ export function UserMenu() {
<Dialog open={pwOpen} onOpenChange={setPwOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
{t("auth.changePassword")}
<DialogHeader className="flex flex-row items-start justify-between gap-4 space-y-0">
<DialogTitle>{t("auth.changePassword")}</DialogTitle>
<GuideHelp guide="konto" label={t("auth.changePassword")} />
</DialogTitle>
</DialogHeader>
{!user?.isLocal ? (
<div className="space-y-4 py-2">
+29 -4
View File
@@ -57,6 +57,7 @@
"language": "Sprache",
"viewDetails": "Details ansehen",
"guideTooltip": "Anleitung in der Dokumentation",
"help": "Hilfe",
"viewMode": "Ansichtsmodus",
"viewGrid": "Raster",
"viewTable": "Tabelle",
@@ -68,7 +69,7 @@
},
"home": {
"welcome": "Willkommen bei toolr",
"tagline": "Die Community-Plattform, auf der Ingenieure die Tools, die sie täglich nutzen, ehrlich bewerten.",
"tagline": "Bewerte dein Tool.",
"totalTools": "Tools gesamt",
"totalRatings": "Bewertungen gesamt",
"avgRating": "Ø Bewertung",
@@ -100,6 +101,7 @@
"compareCount": "Vergleichen ({{count}})",
"addToCompare": "Zum Vergleich hinzufügen",
"removeFromCompare": "Aus dem Vergleich entfernen",
"compareLimitReached": "Maximal 9 Tools können verglichen werden.",
"comparePremiumTitle": "Vergleichen ist ein Premium-Feature",
"comparePremiumSub": "Das Vergleichen von Tools nebeneinander ist für Premium- und Enterprise-Nutzer verfügbar. Erweitere deinen Plan, um es freizuschalten.",
"notNow": "Später",
@@ -188,7 +190,7 @@
"nameOptional": "Name (optional)",
"nameLabel": "Name",
"anonymousPlaceholder": "Anonym",
"anonymousEngineer": "Anonymer Ingenieur",
"anonymousEngineer": "Anonymer Benutzer",
"beFirstToReview": "Sei der Erste, der seine Gedanken zu diesem Tool teilt.",
"shareExperience": "Teile deine Erfahrung mit {{name}}",
"score": "Punktestand",
@@ -304,7 +306,6 @@
"parameter": "Parameter",
"noResults": "Keine Treffer",
"fields": "Felder",
"fieldHelpHint": "Hinweis: Formular-Felder verlinken per ?-Icon direkt zu den jeweiligen Zeilen dieser Tabelle.",
"name": "Name",
"in": "In",
"requestBody": "Request-Body"
@@ -402,7 +403,31 @@
"moveToTrashAction": "In den Papierkorb verschieben",
"toastMoved": "Tools in den Papierkorb verschoben",
"toastMovedSub": "{{count}} Tool(s) in den Papierkorb verschoben.",
"toastMoveFailed": "Verschieben in den Papierkorb fehlgeschlagen"
"toastMoveFailed": "Verschieben in den Papierkorb fehlgeschlagen",
"importTitle": "Tools importieren",
"importSub": "Mehrere Tools auf einmal importieren — als CSV, JSON oder YAML (Premium-Feature).",
"importButton": "Importieren",
"importFormatAuto": "Auto",
"importFormatAutoSub": "Das Format wird automatisch erkannt (CSV, JSON oder YAML).",
"importDelimiter": "CSV-Trenner",
"importDelimiterAuto": "Automatisch",
"importDelimiterComma": "Komma",
"importDelimiterSemicolon": "Semikolon",
"importDelimiterTab": "Tabulator",
"importCsvSub": "Spalten: name, description, category, websiteUrl, iconUrl, features, tags. Features/Tags durch | getrennt. Erste Zeile enthält die Spaltennamen.",
"importJsonSub": "JSON-Objekt oder Array von Objekten mit den Feldern name, description, category, websiteUrl, iconUrl, features, tags.",
"importYamlSub": "YAML-Liste von Objekten (oder ein einzelnes Objekt) mit den Feldern name, description, category, websiteUrl, iconUrl, features, tags.",
"importFile": "Datei laden",
"importTextareaPlaceholder": "name;description;category;features;tags\nMein Tool;Eine tolle Beschreibung;Monitoring;Deployment|Open Source;devops",
"importTextareaPlaceholderCsv": "name;description;category;features;tags\nMein Tool;Eine tolle Beschreibung;Monitoring;Deployment|Open Source;devops",
"importTextareaPlaceholderJson": "[\n {\n \"name\": \"Mein Tool\",\n \"description\": \"Eine tolle Beschreibung\",\n \"category\": \"Monitoring\",\n \"features\": [\"Deployment\", \"Open Source\"],\n \"tags\": [\"devops\"]\n }\n]",
"importTextareaPlaceholderYaml": "- name: Mein Tool\n description: Eine tolle Beschreibung\n category: Monitoring\n features:\n - Deployment\n - Open Source\n tags:\n - devops",
"importTextareaPlaceholderAuto": "name;description;category\nMein Tool;Eine tolle Beschreibung;Monitoring",
"importing": "Wird importiert…",
"importDone": "{{count}} von {{total}} Tools erfolgreich importiert.",
"importErrors": "{{count}} Zeilen mit Fehlern:",
"importErrorRow": "Zeile {{row}}",
"toastImportDone": "Tools erfolgreich importiert"
},
"analytics": {
"title": "Plattform-Analysen",
+29 -4
View File
@@ -57,6 +57,7 @@
"language": "Language",
"viewDetails": "View details",
"guideTooltip": "Guide in the documentation",
"help": "Help",
"viewMode": "View mode",
"viewGrid": "Grid",
"viewTable": "Table",
@@ -68,7 +69,7 @@
},
"home": {
"welcome": "Welcome to toolr",
"tagline": "The community hub where engineers honestly rate the tools they use daily.",
"tagline": "Rate your Tool.",
"totalTools": "Total Tools",
"totalRatings": "Total Ratings",
"avgRating": "Avg Rating",
@@ -100,6 +101,7 @@
"compareCount": "Compare ({{count}})",
"addToCompare": "Add to compare",
"removeFromCompare": "Remove from compare",
"compareLimitReached": "A maximum of 9 tools can be compared.",
"comparePremiumTitle": "Compare is a Premium feature",
"comparePremiumSub": "Comparing tools side-by-side is available to Premium and Enterprise users. Upgrade your plan to unlock it.",
"notNow": "Not now",
@@ -188,7 +190,7 @@
"nameOptional": "Name (Optional)",
"nameLabel": "Name",
"anonymousPlaceholder": "Anonymous",
"anonymousEngineer": "Anonymous Engineer",
"anonymousEngineer": "Anonymous User",
"beFirstToReview": "Be the first to share your thoughts on this tool.",
"shareExperience": "Share your experience with {{name}}",
"score": "Score",
@@ -304,7 +306,6 @@
"parameter": "Parameter",
"noResults": "No results",
"fields": "Fields",
"fieldHelpHint": "Note: form fields link via the ? icon directly to the respective rows of this table.",
"name": "Name",
"in": "In",
"requestBody": "Request Body"
@@ -402,7 +403,31 @@
"moveToTrashAction": "Move to trash",
"toastMoved": "Tools moved to trash",
"toastMovedSub": "{{count}} tool(s) moved to trash.",
"toastMoveFailed": "Failed to move to trash"
"toastMoveFailed": "Failed to move to trash",
"importTitle": "Import tools",
"importSub": "Import multiple tools at once — as CSV, JSON or YAML (premium feature).",
"importButton": "Import",
"importFormatAuto": "Auto",
"importFormatAutoSub": "The format is detected automatically (CSV, JSON or YAML).",
"importDelimiter": "CSV delimiter",
"importDelimiterAuto": "Auto-detect",
"importDelimiterComma": "Comma",
"importDelimiterSemicolon": "Semicolon",
"importDelimiterTab": "Tab",
"importCsvSub": "Columns: name, description, category, websiteUrl, iconUrl, features, tags. Separate features/tags with |. The first line holds the column names.",
"importJsonSub": "A JSON object or array of objects with the fields name, description, category, websiteUrl, iconUrl, features, tags.",
"importYamlSub": "A YAML list of objects (or a single object) with the fields name, description, category, websiteUrl, iconUrl, features, tags.",
"importFile": "Upload file",
"importTextareaPlaceholder": "name;description;category;features;tags\nMy Tool;A great description;Monitoring;Deployment|Open Source;devops",
"importTextareaPlaceholderCsv": "name;description;category;features;tags\nMy Tool;A great description;Monitoring;Deployment|Open Source;devops",
"importTextareaPlaceholderJson": "[\n {\n \"name\": \"My Tool\",\n \"description\": \"A great description\",\n \"category\": \"Monitoring\",\n \"features\": [\"Deployment\", \"Open Source\"],\n \"tags\": [\"devops\"]\n }\n]",
"importTextareaPlaceholderYaml": "- name: My Tool\n description: A great description\n category: Monitoring\n features:\n - Deployment\n - Open Source\n tags:\n - devops",
"importTextareaPlaceholderAuto": "name;description;category\nMy Tool;A great description;Monitoring",
"importing": "Importing…",
"importDone": "{{count}} of {{total}} tools imported successfully.",
"importErrors": "{{count}} rows with errors:",
"importErrorRow": "Row {{row}}",
"toastImportDone": "Tools imported successfully"
},
"analytics": {
"title": "Platform Analytics",
+5 -2
View File
@@ -29,6 +29,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Users, ScrollText, ShieldAlert, Plus, Pencil, Trash2, Clock, AlertTriangle, Wrench, Server } from "lucide-react";
import { format } from "date-fns";
import { AdminToolsTab } from "@/components/admin-tools-tab";
import { GuideHelp } from "@/components/guide-help";
import { useTranslation } from "react-i18next";
export default function Admin() {
@@ -359,8 +360,9 @@ export default function Admin() {
<Dialog open={createOpen} onOpenChange={setCreateOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogHeader className="flex flex-row items-center justify-between gap-2">
<DialogTitle>{t("admin.createNewUser")}</DialogTitle>
<GuideHelp guide="administration" label={t("admin.createNewUser")} />
</DialogHeader>
<div className="space-y-4 py-2">
<div className="space-y-2">
@@ -412,8 +414,9 @@ export default function Admin() {
<Dialog open={!!editUser} onOpenChange={(open) => { if (!open) { setEditUser(null); setEditPassword(""); } }}>
<DialogContent className="sm:max-w-sm">
<DialogHeader>
<DialogHeader className="flex flex-row items-center justify-between gap-2">
<DialogTitle>{t("admin.editUser", { username: editUser?.username })}</DialogTitle>
<GuideHelp guide="administration" label={t("admin.editUser", { username: editUser?.username })} />
</DialogHeader>
<div className="space-y-4 py-2">
<div className="space-y-2">
+1 -3
View File
@@ -7,7 +7,6 @@ import {
} from "@workspace/api-client-react";
import { useTranslation } from "react-i18next";
import { Layout } from "@/components/layout";
import { GuideHelp } from "@/components/guide-help";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import {
@@ -51,9 +50,8 @@ export default function Analytics() {
<Layout>
<div className="space-y-6 pb-10">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2 flex items-center gap-2">
<h1 className="text-3xl font-bold tracking-tight mb-2">
{t("analytics.title")}
<GuideHelp guide="analytics" label={t("analytics.title")} />
</h1>
<p className="text-muted-foreground">{t("analytics.subtitle")}</p>
</div>
+1 -3
View File
@@ -8,7 +8,6 @@ import { Link, useSearch } from "wouter";
import { Layout } from "@/components/layout";
import { useAuth } from "@/hooks/use-auth";
import { RatingStars } from "@/components/rating-stars";
import { GuideHelp } from "@/components/guide-help";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
@@ -102,9 +101,8 @@ export default function Compare() {
<Layout>
<div className="space-y-6 pb-10">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2 flex items-center gap-2">
<h1 className="text-3xl font-bold tracking-tight mb-2">
{t("compare.title")}
<GuideHelp guide="vergleichen" label={t("compare.title")} />
</h1>
<p className="text-muted-foreground">{t("compare.subtitle", { count: list.length })}</p>
</div>
+1 -6
View File
@@ -30,7 +30,6 @@ import {
ExternalLink,
FileText,
GitBranch,
HelpCircle,
Library,
Menu,
Search,
@@ -591,10 +590,6 @@ function SchemaView({ schema }: { schema: SchemaModel }) {
{schema.description && <p className="text-muted-foreground">{schema.description}</p>}
</div>
<FieldTable fields={schema.fields} />
<p className="text-xs text-muted-foreground">
<HelpCircle className="h-3.5 w-3.5 inline mr-1" />
{t("docs.fieldHelpHint")}
</p>
</div>
<Toc headings={headings} title={t("docs.fields")} />
</div>
@@ -745,7 +740,7 @@ function ReleasesView({ versions }: { versions: ReleaseDoc[] }) {
function ReleaseNoteView({ version, doc }: { version: string; doc: ReleaseDoc | null }) {
const locale = useDocsLocale();
const file = doc ? `releases/${localizedFile(doc.file, doc.fileEn, locale)}` : `releases/${version}.md`;
const file = doc ? `releases/${localizedFile(doc.file, doc.fileEn, locale)}` : `releases/${version}.de.md`;
return (
<div className="flex flex-col lg:flex-row gap-8">
<div className="flex-1 min-w-0 max-w-3xl">
+1 -3
View File
@@ -7,7 +7,6 @@ import {
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { Layout } from "@/components/layout";
import { GuideHelp } from "@/components/guide-help";
import { ToolCard } from "@/components/tool-card";
import { Star, Wrench, MessageSquare, ArrowRight, Plus } from "lucide-react";
import { Link } from "wouter";
@@ -25,9 +24,8 @@ export default function Home() {
<div className="space-y-8 pb-8">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-end gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2 flex items-center gap-2">
<h1 className="text-3xl font-bold tracking-tight mb-2">
{t("home.welcome")}
<GuideHelp guide="getting-started" label={t("home.welcome")} />
</h1>
<p className="text-muted-foreground">{t("home.tagline")}</p>
</div>
+1 -5
View File
@@ -1,7 +1,6 @@
import { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Layout } from "@/components/layout";
import { GuideHelp } from "@/components/guide-help";
import { Card, CardContent } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -47,10 +46,7 @@ export default function RedundancyPage() {
<div className="space-y-6">
<div className="flex items-center gap-3">
<AlertTriangle className="w-6 h-6 text-amber-500" />
<h1 className="text-3xl font-bold flex items-center gap-2">
{t("redundancy.title")}
<GuideHelp guide="redundanz" label={t("redundancy.title")} />
</h1>
<h1 className="text-3xl font-bold">{t("redundancy.title")}</h1>
</div>
<p className="text-muted-foreground">
{t("redundancy.subtitle")}
+6 -12
View File
@@ -60,7 +60,6 @@ import {
} from "@/components/ui/alert-dialog";
import { customFetch } from "@workspace/api-client-react";
import { recordRecentTool } from "@/lib/recent-tools";
import { FieldHelp } from "@/components/field-help";
import { GuideHelp } from "@/components/guide-help";
const ratingSchema = z.object({
@@ -616,9 +615,8 @@ export default function ToolDetail() {
<Dialog open={costDialogOpen} onOpenChange={(o) => { if (!o) setCostDialogOpen(false); }}>
<DialogContent>
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<DialogTitle>
{editCost ? t("detail.edit") + " " + t("detail.costs") : t("detail.addCost")}
<GuideHelp guide="kosten" label={t("detail.costs")} />
</DialogTitle>
<DialogDescription>{t("detail.costDialogSub")}</DialogDescription>
</DialogHeader>
@@ -768,12 +766,12 @@ export default function ToolDetail() {
{isReviewFormOpen && (
<Card className="border-primary shadow-sm">
<CardHeader>
<CardTitle className="flex items-center gap-2">
{t("detail.addReview")}
<GuideHelp guide="bewerten" label={t("detail.addReview")} />
</CardTitle>
<CardHeader className="flex flex-row items-start justify-between gap-4">
<div className="space-y-1.5">
<CardTitle>{t("detail.addReview")}</CardTitle>
<CardDescription>{t("detail.shareExperience", { name: tool.name })}</CardDescription>
</div>
<GuideHelp guide="bewerten" label={t("detail.addReview")} />
</CardHeader>
<CardContent>
<Form {...form}>
@@ -786,7 +784,6 @@ export default function ToolDetail() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("detail.usefulness")}
<FieldHelp schema="RatingInput" field="usefulness">{t("detail.usefulness")}</FieldHelp>
</FormLabel>
<div className="py-2">
<RatingStars
@@ -807,7 +804,6 @@ export default function ToolDetail() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("detail.usability")}
<FieldHelp schema="RatingInput" field="usability">{t("detail.usability")}</FieldHelp>
</FormLabel>
<div className="py-2">
<RatingStars
@@ -830,7 +826,6 @@ export default function ToolDetail() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("detail.commentOptional")}
<FieldHelp schema="RatingInput" field="comment">{t("detail.commentLabel")}</FieldHelp>
</FormLabel>
<FormControl>
<Textarea
@@ -851,7 +846,6 @@ export default function ToolDetail() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("detail.nameOptional")}
<FieldHelp schema="RatingInput" field="reviewerName">{t("detail.nameLabel")}</FieldHelp>
</FormLabel>
<FormControl>
<Input placeholder={t("detail.anonymousPlaceholder")} {...field} />
+4 -10
View File
@@ -30,7 +30,6 @@ import { CategoryCombobox } from "@/components/category-combobox";
import { FeatureInput } from "@/components/feature-input";
import { TagInput } from "@/components/tag-input";
import { useAuth } from "@/hooks/use-auth";
import { FieldHelp } from "@/components/field-help";
import { GuideHelp } from "@/components/guide-help";
import { useTranslation } from "react-i18next";
@@ -163,13 +162,15 @@ export default function ToolEdit() {
</Card>
) : (
<Card>
<CardHeader>
<CardHeader className="flex flex-row items-start justify-between gap-4">
<div className="space-y-1.5">
<CardTitle className="flex items-center gap-2">
<Pencil className="w-5 h-5 text-primary" />
{t("toolForm.toolDetails")}
<GuideHelp guide="tool-bearbeiten" label={t("toolForm.toolDetails")} />
</CardTitle>
<CardDescription>{t("toolForm.toolDetailsEditSub")}</CardDescription>
</div>
<GuideHelp guide="tool-bearbeiten" label={t("toolForm.toolDetails")} />
</CardHeader>
<CardContent>
<Form {...form}>
@@ -182,7 +183,6 @@ export default function ToolEdit() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("toolForm.name")}
<FieldHelp schema="ToolInput" field="name">{t("toolForm.name")}</FieldHelp>
</FormLabel>
<FormControl>
<Input placeholder={t("toolForm.name")} {...field} />
@@ -198,7 +198,6 @@ export default function ToolEdit() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("toolForm.category")}
<FieldHelp schema="ToolInput" field="category">{t("toolForm.category")}</FieldHelp>
</FormLabel>
<FormControl>
<CategoryCombobox value={field.value} onChange={field.onChange} />
@@ -216,7 +215,6 @@ export default function ToolEdit() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("toolForm.websiteUrlOptional")}
<FieldHelp schema="ToolInput" field="websiteUrl">{t("toolForm.websiteUrl")}</FieldHelp>
</FormLabel>
<FormControl>
<Input placeholder={t("toolForm.urlPlaceholder")} type="url" {...field} />
@@ -233,7 +231,6 @@ export default function ToolEdit() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("toolForm.iconUrlOptional")}
<FieldHelp schema="ToolInput" field="iconUrl">{t("toolForm.iconUrl")}</FieldHelp>
</FormLabel>
<FormControl>
<div className="flex items-center gap-3">
@@ -269,7 +266,6 @@ export default function ToolEdit() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("toolForm.description")}
<FieldHelp schema="ToolInput" field="description">{t("toolForm.description")}</FieldHelp>
</FormLabel>
<FormControl>
<Textarea
@@ -288,7 +284,6 @@ export default function ToolEdit() {
<div>
<h3 className="text-lg font-medium inline-flex items-center gap-1.5">
{t("toolForm.features")}
<FieldHelp schema="ToolInput" field="features">{t("toolForm.features")}</FieldHelp>
</h3>
<p className="text-sm text-muted-foreground">{t("toolForm.featuresEditSub")}</p>
</div>
@@ -335,7 +330,6 @@ export default function ToolEdit() {
<div>
<h3 className="text-lg font-medium inline-flex items-center gap-1.5">
{t("toolForm.tags")}
<FieldHelp schema="ToolInput" field="tags">{t("toolForm.tags")}</FieldHelp>
</h3>
<p className="text-sm text-muted-foreground">{t("toolForm.tagsEditSub")}</p>
</div>
+4 -10
View File
@@ -18,7 +18,6 @@ import { CategoryCombobox } from "@/components/category-combobox";
import { FeatureInput } from "@/components/feature-input";
import { TagInput } from "@/components/tag-input";
import { useAuth } from "@/hooks/use-auth";
import { FieldHelp } from "@/components/field-help";
import { GuideHelp } from "@/components/guide-help";
import { useTranslation } from "react-i18next";
@@ -126,13 +125,15 @@ export default function ToolNew() {
)}
<Card>
<CardHeader>
<CardHeader className="flex flex-row items-start justify-between gap-4">
<div className="space-y-1.5">
<CardTitle className="flex items-center gap-2">
<Wrench className="w-5 h-5 text-primary" />
{t("toolForm.toolDetails")}
<GuideHelp guide="tool-anlegen" label={t("toolForm.toolDetails")} />
</CardTitle>
<CardDescription>{t("toolForm.toolDetailsNewSub")}</CardDescription>
</div>
<GuideHelp guide="tool-anlegen" label={t("toolForm.toolDetails")} />
</CardHeader>
<CardContent>
<Form {...form}>
@@ -145,7 +146,6 @@ export default function ToolNew() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("toolForm.name")}
<FieldHelp schema="ToolInput" field="name">{t("toolForm.name")}</FieldHelp>
</FormLabel>
<FormControl>
<Input placeholder={t("toolForm.namePlaceholder")} {...field} data-testid="input-tool-name" />
@@ -162,7 +162,6 @@ export default function ToolNew() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("toolForm.category")}
<FieldHelp schema="ToolInput" field="category">{t("toolForm.category")}</FieldHelp>
</FormLabel>
<FormControl>
<CategoryCombobox
@@ -183,7 +182,6 @@ export default function ToolNew() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("toolForm.websiteUrlOptional")}
<FieldHelp schema="ToolInput" field="websiteUrl">{t("toolForm.websiteUrl")}</FieldHelp>
</FormLabel>
<FormControl>
<Input placeholder={t("toolForm.urlPlaceholder")} type="url" {...field} data-testid="input-tool-url" />
@@ -200,7 +198,6 @@ export default function ToolNew() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("toolForm.iconUrlOptional")}
<FieldHelp schema="ToolInput" field="iconUrl">{t("toolForm.iconUrl")}</FieldHelp>
</FormLabel>
<FormControl>
<div className="flex items-center gap-3">
@@ -237,7 +234,6 @@ export default function ToolNew() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("toolForm.description")}
<FieldHelp schema="ToolInput" field="description">{t("toolForm.description")}</FieldHelp>
</FormLabel>
<FormControl>
<Textarea
@@ -257,7 +253,6 @@ export default function ToolNew() {
<div>
<h3 className="text-lg font-medium inline-flex items-center gap-1.5">
{t("toolForm.features")}
<FieldHelp schema="ToolInput" field="features">{t("toolForm.features")}</FieldHelp>
</h3>
<p className="text-sm text-muted-foreground">{t("toolForm.featuresNewSub")}</p>
</div>
@@ -313,7 +308,6 @@ export default function ToolNew() {
<div>
<h3 className="text-lg font-medium inline-flex items-center gap-1.5">
{t("toolForm.tags")}
<FieldHelp schema="ToolInput" field="tags">{t("toolForm.tags")}</FieldHelp>
</h3>
<p className="text-sm text-muted-foreground">{t("toolForm.tagsHelp")}</p>
</div>
+12 -8
View File
@@ -6,7 +6,6 @@ import {
} from "@workspace/api-client-react";
import { useVirtualizer } from "@tanstack/react-virtual";
import { Layout } from "@/components/layout";
import { GuideHelp } from "@/components/guide-help";
import { ToolCard } from "@/components/tool-card";
import { ToolCardWide } from "@/components/tool-card-wide";
import { ToolRow, TABLE_GRID } from "@/components/tool-row";
@@ -19,6 +18,7 @@ import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent,
import { useBrowsePreferences, isViewMode, isDensity } from "@/hooks/use-browse-preferences";
import { useAuth } from "@/hooks/use-auth";
import { useWatchlist } from "@/hooks/use-watchlist";
import { useToast } from "@/hooks/use-toast";
import { CompareBar } from "@/components/compare-bar";
import { useTranslation } from "react-i18next";
import { Input } from "@/components/ui/input";
@@ -92,12 +92,20 @@ export default function ToolsBrowse() {
const { serverView, serverDensity, localView, localDensity, persist } = useBrowsePreferences();
const { hasFeature } = useAuth();
const { isWatched, toggle: toggleWatchlist, canWatchlist } = useWatchlist();
const { toast } = useToast();
const [compareIds, setCompareIds] = useState<number[]>([]);
const [compareUpsellOpen, setCompareUpsellOpen] = useState(false);
function toggleCompare(id: number) {
setCompareIds((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));
setCompareIds((prev) => {
if (prev.includes(id)) return prev.filter((x) => x !== id);
if (prev.length >= 9) {
toast({ title: t("browse.compareLimitReached"), variant: "destructive" });
return prev;
}
return [...prev, id];
});
}
const [search, setSearch] = useState(initialSearch);
@@ -231,9 +239,8 @@ export default function ToolsBrowse() {
<div className="space-y-6 pb-8">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-end gap-4">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2 flex items-center gap-2">
<h1 className="text-3xl font-bold tracking-tight mb-2">
{t("nav.browseTools")}
<GuideHelp guide="tools-finden" label={t("browse.subtitle")} />
</h1>
<p className="text-muted-foreground">{t("browse.subtitle")}</p>
</div>
@@ -525,10 +532,7 @@ export default function ToolsBrowse() {
<AlertDialog open={compareUpsellOpen} onOpenChange={setCompareUpsellOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2">
{t("browse.comparePremiumTitle")}
<GuideHelp guide="plaene" label={t("browse.comparePremiumTitle")} />
</AlertDialogTitle>
<AlertDialogTitle>{t("browse.comparePremiumTitle")}</AlertDialogTitle>
<AlertDialogDescription>
{t("browse.comparePremiumSub")}
</AlertDialogDescription>
+1 -3
View File
@@ -15,7 +15,6 @@ import {
import { useQueryClient } from "@tanstack/react-query";
import { useAuth } from "@/hooks/use-auth";
import { Layout } from "@/components/layout";
import { GuideHelp } from "@/components/guide-help";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Badge } from "@/components/ui/badge";
@@ -157,9 +156,8 @@ export default function Trash() {
<Layout>
<div className="space-y-6 pb-10">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2 flex items-center gap-2">
<h1 className="text-3xl font-bold tracking-tight mb-2">
{t("trash.title")}
<GuideHelp guide="papierkorb" label={t("trash.title")} />
</h1>
<p className="text-muted-foreground">{t("trash.subtitle")}</p>
</div>
+1 -3
View File
@@ -6,7 +6,6 @@ import { useAuth } from "@/hooks/use-auth";
import { useWatchlist } from "@/hooks/use-watchlist";
import { Layout } from "@/components/layout";
import { ToolCard } from "@/components/tool-card";
import { GuideHelp } from "@/components/guide-help";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { ShieldAlert, Bookmark } from "lucide-react";
@@ -43,9 +42,8 @@ export default function Watchlist() {
<Layout>
<div className="space-y-6 pb-10">
<div>
<h1 className="text-3xl font-bold tracking-tight mb-2 flex items-center gap-2">
<h1 className="text-3xl font-bold tracking-tight mb-2">
{t("watchlist.title")}
<GuideHelp guide="watchlist" label={t("watchlist.title")} />
</h1>
<p className="text-muted-foreground">{t("watchlist.subtitle")}</p>
</div>
+295
View File
@@ -0,0 +1,295 @@
# Beispiel-YAML zum Import in den Admin-Bereich (Tab „Tools" -> Importieren).
# Erwartetes Format pro Eintrag:
# name (Pflicht)
# description, category, websiteUrl, iconUrl (optional)
# features, tags (Listen oder | -getrennte Strings)
# Das Format wird automatisch erkannt; bei CSV den Trenner wählen.
# Fehlerhafte Zeilen werden übersprungen und mit Zeilennummer aufgelistet.
- name: Slack
description: Team-Kommunikation mit Channels, Direktnachrichten und App-Integrationen
category: Kommunikation
websiteUrl: https://slack.com
features:
- Channels
- Videoanrufe
- App-Integrationen
tags:
- communication
- teamwork
- name: Microsoft Teams
description: Chat, Besprechungen, Dateien und Zusammenarbeit in einer Plattform
category: Kommunikation
websiteUrl: https://www.microsoft.com/de-de/microsoft-365/microsoft-teams
features:
- Videokonferenzen
- Teamkanäle
- Office-Integration
tags:
- communication
- meetings
- name: Zoom
description: Videokonferenzen und Webinare für Teams und externe Partner
category: Kommunikation
websiteUrl: https://zoom.us
features:
- Videokonferenzen
- Webinare
- Bildschirmfreigabe
tags:
- video
- meetings
- name: Jira
description: Agile Projekt- und Issue-Verfolgung für Entwicklung und Betrieb
category: Projektmanagement
websiteUrl: https://www.atlassian.com/software/jira
features:
- Backlog
- Sprints
- Kanban-Boards
tags:
- agile
- project-management
- name: Confluence
description: Kollaborative Wissensdatenbank und Dokumentation für Teams
category: Projektmanagement
websiteUrl: https://www.atlassian.com/software/confluence
features:
- Wikis
- Seitenbausteine
- Berechtigungen
tags:
- documentation
- knowledge-base
- name: GitLab
description: DevOps-Plattform mit Git-Repositorys, CI/CD und Code-Review
category: Entwicklung
websiteUrl: https://about.gitlab.com
features:
- Git-Repositorys
- CI/CD
- Merge Requests
tags:
- devops
- git
- name: GitHub
description: Versionsverwaltung, Code-Review und Collaboration auf Git-Basis
category: Entwicklung
websiteUrl: https://github.com
features:
- Repositories
- Pull Requests
- Actions
tags:
- git
- collaboration
- name: Jenkins
description: Automatisierte Build- und Deploy-Pipelines für Softwareprojekte
category: CI/CD
websiteUrl: https://www.jenkins.io
features:
- Pipelines
- Plugins
- Job-Automatisierung
tags:
- ci
- cd
- name: Docker
description: Container-Plattform zum Paketieren und Ausführen von Anwendungen
category: Entwicklung
websiteUrl: https://www.docker.com
features:
- Container
- Images
- Compose
tags:
- containers
- virtualization
- name: Grafana
description: Observability- und Dashboard-Plattform für Metriken, Logs und Alerts
category: Monitoring
websiteUrl: https://grafana.com
features:
- Dashboards
- Alerts
- Datenquellen
tags:
- observability
- monitoring
- name: Prometheus
description: Open-Source-Systeme zur Zeitreihen-Metrikerfassung und -Auswertung
category: Monitoring
websiteUrl: https://prometheus.io
features:
- Metriken
- Alerting
- Time-Series-Datenbank
tags:
- monitoring
- metrics
- name: Datadog
description: Cloud-basierte Observability für Infrastruktur, APM und Logs
category: Monitoring
websiteUrl: https://www.datadoghq.com
features:
- Infrastruktur-Monitoring
- APM
- Log-Management
tags:
- observability
- saas
- name: PagerDuty
description: Incident-Management und On-Call-Benachrichtigungen für Teams
category: Betrieb
websiteUrl: https://www.pagerduty.com
features:
- Alerting
- On-Call-Pläne
- Incident-Response
tags:
- incident-management
- on-call
- name: Sentry
description: Fehlerüberwachung und Performance-Monitoring für Anwendungen
category: Monitoring
websiteUrl: https://sentry.io
features:
- Error-Tracking
- Releases
- Performance
tags:
- error-monitoring
- debugging
- name: Terraform
description: Infrastructure-as-Code zur deklarativen Bereitstellung von Ressourcen
category: Infrastruktur
websiteUrl: https://www.terraform.io
features:
- Modules
- State-Management
- Provider
tags:
- iac
- infrastructure
- name: Ansible
description: Agentenlose Automatisierung für Konfiguration, Deploy und Orchestrierung
category: Infrastruktur
websiteUrl: https://www.ansible.com
features:
- Playbooks
- Inventories
- Modules
tags:
- automation
- configuration
- name: Elasticsearch
description: Verteilter Such- und Analytics-Server für Logs, Metriken und Daten
category: Datenbank
websiteUrl: https://www.elastic.co
features:
- Volltextsuche
- Aggregationen
- Skalierung
tags:
- search
- analytics
- name: Kubernetes
description: Container-Orchestrierung für Deployment, Skalierung und Betrieb
category: Infrastruktur
websiteUrl: https://kubernetes.io
features:
- Pods
- Services
- Autoskalierung
tags:
- containers
- orchestration
- name: Sonarqube
description: Statische Code-Analyse und Qualitätsmessung für Softwareprojekte
category: Entwicklung
websiteUrl: https://www.sonarsource.com/products/sonarqube
features:
- Code-Qualität
- Security-Scans
- Quality Gates
tags:
- code-quality
- static-analysis
- name: Power BI
description: Business-Intelligence-Tool für Dashboards, Berichte und Datenanalyse
category: Analytics
websiteUrl: https://powerbi.microsoft.com
features:
- Dashboards
- Berichte
- Datenmodelle
tags:
- bi
- analytics
- name: Google Workspace
description: Produktivitäts-Suite mit E-Mail, Docs, Drive und Meet
category: Produktivität
websiteUrl: https://workspace.google.com
features:
- Gmail
- Google Docs
- Google Drive
tags:
- suite
- productivity
- name: Microsoft 365
description: Office-Anwendungen, E-Mail und Cloud-Dienste für Unternehmen
category: Produktivität
websiteUrl: https://www.microsoft.com/microsoft-365
features:
- Word
- Excel
- Outlook
tags:
- suite
- productivity
- name: Slack Enterprise Grid
description: Slack-Verwaltung für große Organisationen mit erweiterten Sicherheitsfunktionen
category: Kommunikation
websiteUrl: https://slack.com
features:
- Enterprise-Admin
- Compliance
- Multi-Workspace
tags:
- communication
- enterprise
- name: Atlassian Rovo
description: KI-Assistent für Suche und Zusammenarbeit über Atlassian-Produkte
category: Projektmanagement
websiteUrl: https://www.atlassian.com/software/rovo
features:
- KI-Suche
- Agenten
- Integrationen
tags:
- ai
- search
@@ -36,6 +36,49 @@ Zentraler Zugriff auf den Tool-Katalog.
- **Massenaktion:** mehrere Tools auswählen und in den Papierkorb verschieben
(Bestätigungsdialog; soft gelöschte Tools sind aus allen öffentlichen Ansichten
entfernt und können wiederhergestellt oder endgültig gelöscht werden).
- **Importieren** (Premium): über die Schaltfläche **Importieren** oben rechts
können mehrere Tools gleichzeitig angelegt werden. Das Format wird automatisch
erkannt (CSV, JSON oder YAML); für CSV lässt sich der Trenner wählen
(automatisch, Komma, Semikolon oder Tabulator). Die Daten lassen sich direkt
ins Formular einfügen oder als Datei hochladen.
Unterstützte Felder: `name` (Pflicht), `description`, `category`, `websiteUrl`,
`iconUrl`, `features`, `tags` (`features`/`tags` sind mit `|` getrennte Listen).
**CSV-Beispiel:**
```csv
name;description;category;features;tags
Grafana;Observability- und Dashboard-Plattform;Monitoring;Dashboards|Alerts;devops
Prometheus;Zeitreihen-Datenbank;Monitoring;Metrics;devops
```
**JSON-Beispiel:**
```json
[
{ "name": "Grafana", "category": "Monitoring", "features": ["Dashboards"] },
{ "name": "Prometheus", "category": "Monitoring" }
]
```
**YAML-Beispiel:**
```yaml
- name: Grafana
category: Monitoring
features:
- Dashboards
- name: Prometheus
category: Monitoring
```
Eine umfangreichere, fertig importierbare Beispieldatei liegt unter
`docs/examples/import-tools.yaml` (24 gängige Enterprise-Tools).
Beim Import werden einzelne fehlerhafte Zeilen übersprungen und mit Zeilennummer
aufgelistet; korrekte Zeilen werden trotzdem angelegt. Jeder Import erzeugt
Audit-Log-Einträge.
## Tab „Audit-Log"
+43
View File
@@ -36,6 +36,49 @@ Central access to the tool catalog.
- **Bulk action:** select multiple tools and move them to the trash
(confirmation dialog; soft-deleted tools are removed from all public views
and can be restored or permanently deleted).
- **Import** (premium): via the **Import** button at the top right, multiple
tools can be created at once. The format is detected automatically (CSV,
JSON or YAML); for CSV the delimiter can be chosen (auto-detect, comma,
semicolon or tab). Data can be pasted directly into the form or uploaded
as a file.
Supported fields: `name` (required), `description`, `category`, `websiteUrl`,
`iconUrl`, `features`, `tags` (`features`/`tags` are `|`-separated lists).
**CSV example:**
```csv
name;description;category;features;tags
Grafana;Observability and dashboard platform;Monitoring;Dashboards|Alerts;devops
Prometheus;Time-series database;Monitoring;Metrics;devops
```
**JSON example:**
```json
[
{ "name": "Grafana", "category": "Monitoring", "features": ["Dashboards"] },
{ "name": "Prometheus", "category": "Monitoring" }
]
```
**YAML example:**
```yaml
- name: Grafana
category: Monitoring
features:
- Dashboards
- name: Prometheus
category: Monitoring
```
A larger, ready-to-import example file is located at
`docs/examples/import-tools.yaml` (24 common enterprise tools).
During import, individual faulty rows are skipped and listed with their row
number; correct rows are still created. Every import produces audit-log
entries.
## "Audit log" tab
@@ -6,7 +6,7 @@ order: 1
# Willkommen bei toolr
toolr ist eine Plattform zum **Entdecken, Bewerten und Vergleichen von
Entwicklungstools**. Nutzer:innen pflegen einen gemeinsamen Katalog von Tools,
Entwicklungstools**. Nutzer pflegen einen gemeinsamen Katalog von Tools,
vergeben Bewertungen (Nützlichkeit & Bedienbarkeit) und nutzen Statistiken, um
die richtige Wahl zu treffen.
@@ -16,13 +16,14 @@ die richtige Wahl zu treffen.
| --- | --- | --- |
| **Tools durchsuchen** | Katalog filtern, sortieren und durchsuchen | Alle |
| **Tool anlegen** | Neues Tool mit Beschreibung, Kategorie, Features & Tags eintragen | Angemeldet |
| **Tool bearbeiten/löschen** | Eigene Tools pflegen (Ersteller:in oder Admin) | Angemeldet |
| **Tool bearbeiten/löschen** | Eigene Tools pflegen (Ersteller oder Admin) | Angemeldet |
| **Bewerten** | Nützlichkeit & Bedienbarkeit (15) plus Kommentar vergeben | Angemeldet |
| **Merkliste** | Tools als Favoriten speichern | Premium |
| **Vergleichen** | Tools nebeneinander gegenüberstellen | Premium |
| **Kosten erfassen** | Lizenz- und Kostenmodelle je Tool eintragen | Premium |
| **Analytics** | Statistiken, Top-Tools, Verteilungen | Alle |
| **Papierkorb** | Soft-gelöschte Tools wiederherstellen oder endgültig löschen | Premium |
| **Import** | Mehrere Tools per CSV, JSON oder YAML anlegen | Admin (Premium) |
| **Admin** | Nutzerverwaltung, Audit-Log, Systeminformationen | Admin |
| **Redundanz** | Automatische Doppelungs-Erkennung | Admin |
+1
View File
@@ -22,6 +22,7 @@ tools**. Users maintain a shared catalog of tools, submit ratings
| **Record costs** | Enter license and cost models per tool | Premium |
| **Analytics** | Statistics, top tools, distributions | Everyone |
| **Trash** | Restore soft-deleted tools or delete them permanently | Premium |
| **Import** | Create multiple tools via CSV, JSON or YAML | Admin (premium) |
| **Admin** | User management, audit log, system information | Admin |
| **Redundancy detection** | Automatic duplicate detection | Admin |
@@ -26,6 +26,7 @@ Premium/Enterprise schalten folgende Features frei:
| `watchlist` | Favoritenliste | [Merkliste](/docs/handbook/watchlist) |
| `trash` | Papierkorb (soft gelöschte Tools) | [Papierkorb](/docs/handbook/papierkorb) |
| `costs` | Kosten-/Lizenzmodelle erfassen | [Kosten erfassen](/docs/handbook/kosten) |
| `tool-import` | Tools per CSV/JSON/YAML importieren (Admin) | [Administration](/docs/handbook/administration) |
Fehlt dir ein Feature, zeigt die App einen **Upgrade-Hinweis** mit Link zur
Tarifverwaltung.
+1
View File
@@ -26,6 +26,7 @@ Premium/Enterprise unlock the following features:
| `watchlist` | Favorites list | [Watchlist](/docs/handbook/watchlist) |
| `trash` | Trash (soft-deleted tools) | [Trash](/docs/handbook/papierkorb) |
| `costs` | Record cost/license models | [Recording costs](/docs/handbook/kosten) |
| `tool-import` | Import tools via CSV/JSON/YAML (admin) | [Administration](/docs/handbook/administration) |
If you are missing a feature, the app shows an **upgrade notice** with a link
to the plan management.
@@ -47,7 +47,7 @@ Mit **Filter zurücksetzen** bzw. **Alle entfernen** räumst du sie wieder auf.
- **Ansicht wechseln:** Raster / Tabelle / Zeilen.
- **Dichte:** gemütlich / kompakt (Schieberegler).
Deine Auswahl wird gespeichert — lokal im Browser und für angemeldete Nutzer:innen
Deine Auswahl wird gespeichert — lokal im Browser und für angemeldete Nutzer
zusätzlich serverseitig in den Präferenzen. Ansicht, Dichte, Suche, Filter und
Sortierung werden dabei in die URL übernommen, sodass du Ergebnisse teilen
kannst.
-37
View File
@@ -1,37 +0,0 @@
# v0.6.0 — Release Notes
**Date:** 2026-08-03 · **Tag:** [`v0.6.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.6.0)
## New features
- Complete modernization of all dependencies to the current major versions
(TypeScript 7, Vite 8, React 19.2, openid-client 6, zod 4, recharts 3, react-day-picker 10).
## Fixes & improvements
- CI build fixed via `allowBuilds` configuration for pnpm 11
(build scripts for esbuild & Co. are no longer blocked).
- Image tagging simplified: only `latest` and `v*` tags, no more `nightly-*`/`sha-*` tags.
- All dependencies pinned exactly; automatic updates via Renovate prepared
(`renovate.json`, `docs/dependency-policy.md`).
## API changes
- No breaking changes to the API. openid-client internally migrated to v6
(auth flow behaves identically).
## Operations / upgrade
- **Env vars:** unchanged. Node image pinned to `node:24.18.1-alpine`.
- **Migration:** no database migration required.
- **Breaking changes:** none.
## Known limitations
- `typedoc` (indirect orval dependency) shows a peer-dependency warning
(expects TypeScript 5.x/6.x, 7.x is installed) — harmless for build and runtime.
## Links
- Commit: [`2f66fff`](https://git.kubebase.de/admin/tool-evaluator/commit/2f66fff)
- Tag: [`v0.6.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.6.0)
-37
View File
@@ -1,37 +0,0 @@
# v0.6.0 — Release Notes
**Datum:** 2026-08-03 · **Tag:** [`v0.6.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.6.0)
## Neue Features
- Vollständige Modernisierung aller Abhängigkeiten auf die aktuellen Hauptversionen
(TypeScript 7, Vite 8, React 19.2, openid-client 6, zod 4, recharts 3, react-day-picker 10).
## Fixes & Verbesserungen
- CI-Build durch `allowBuilds`-Konfiguration für pnpm 11 repariert
(Build-Scripts für esbuild & Co. werden nicht mehr blockiert).
- Image-Tagging vereinfacht: nur noch `latest` und `v*`-Tags, keine `nightly-*`/`sha-*`-Tags.
- Alle Dependencies exakt gepinnt; automatische Updates via Renovate vorbereitet
(`renovate.json`, `docs/dependency-policy.md`).
## API-Änderungen
- Keine Breaking Changes an der API. openid-client intern auf v6 migriert
(auth-Fluss verhält sich identisch).
## Betrieb / Upgrade
- **Env-Vars:** unverändert. Node-Image auf `node:24.18.1-alpine` gepinnt.
- **Migration:** keine Datenbank-Migration erforderlich.
- **Breaking Changes:** keine.
## Bekannte Einschränkungen
- `typedoc` (indirekte orval-Abhängigkeit) zeigt eine Peer-Dependency-Warnung
(erwartet TypeScript 5.x/6.x, installiert ist 7.x) — harmlos für Build & Laufzeit.
## Links
- Commit: [`2f66fff`](https://git.kubebase.de/admin/tool-evaluator/commit/2f66fff)
- Tag: [`v0.6.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.6.0)
-35
View File
@@ -1,35 +0,0 @@
# v0.7.0 — Release Notes
**Date:** 2026-08-03 · **Tag:** [`v0.7.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.7.0)
## New features
- Version-bound **release documentation** in the app under `/docs`
(index + detail page per version, Markdown from `docs/releases/`).
- Generated release template (`docs/releases/TEMPLATE.md`) and
sync step for the frontend build.
## Fixes & improvements
- `tsx` bumped to 4.23.4 — last outdated dependency in the workspace
(`pnpm outdated -r` is now empty).
## API changes
- No breaking changes to the API.
## Operations / upgrade
- **Env vars:** unchanged.
- **Migration:** none.
- **Breaking changes:** none.
## Known limitations
- The documentation is currently limited to release notes; a complete
API/field reference will follow in v0.8.0.
## Links
- Commit: [`520f917`](https://git.kubebase.de/admin/tool-evaluator/commit/520f917)
- Tag: [`v0.7.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.7.0)
-35
View File
@@ -1,35 +0,0 @@
# v0.7.0 — Release Notes
**Datum:** 2026-08-03 · **Tag:** [`v0.7.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.7.0)
## Neue Features
- Version-gebundene **Release-Dokumentation** in der App unter `/docs`
(Index + Detailseite je Version, Markdown aus `docs/releases/`).
- Generiertes Release-Vorlage (`docs/releases/TEMPLATE.md`) und
Sync-Schritt für den Frontend-Build.
## Fixes & Verbesserungen
- `tsx` auf 4.23.4 angehoben — letzte veraltete Abhängigkeit im Workspace
(`pnpm outdated -r` ist jetzt leer).
## API-Änderungen
- Keine Breaking Changes an der API.
## Betrieb / Upgrade
- **Env-Vars:** unverändert.
- **Migration:** keine.
- **Breaking Changes:** keine.
## Bekannte Einschränkungen
- Die Doku ist bisher auf Release-Notes beschränkt; eine vollständige
API-/Feld-Referenz folgt in v0.8.0.
## Links
- Commit: [`520f917`](https://git.kubebase.de/admin/tool-evaluator/commit/520f917)
- Tag: [`v0.7.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.7.0)
-46
View File
@@ -1,46 +0,0 @@
# v0.8.0 — Release Notes
**Date:** 2026-08-03 · **Tag:** [`v0.8.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.0)
## New features
- **Complete documentation site** in mkdocs look under `/docs`:
- **Manual** with understandable explanations of all features
(Getting started, creating a tool, evaluations, comparing, watchlist,
analytics, administration, data model).
- **Automatically generated reference** from `lib/api-spec/openapi.yaml`:
all endpoints and data fields (type, required status, constraints) —
this guarantees that *every* feature is documented.
- **Search** across the manual, endpoints, and fields.
- **Version dropdown**: older releases keep their full field/endpoint
reference as a snapshot.
- **Repo link** to the source in the top right.
- **Help buttons (?) in forms** (NetBox style): next to each field,
an icon jumps directly to the field description in the docs.
## Fixes & improvements
- Docs generator `scripts/src/generate-docs.mjs` replaces the previous
`sync-release-docs.mjs` (OpenAPI parsing, manual, search index, snapshots).
- Documentation for v0.7.0 backfilled.
## API changes
- No breaking changes to the API.
## Operations / upgrade
- **Env vars:** unchanged.
- **Migration:** none.
- **Breaking changes:** none.
## Known limitations
- Manual & reference apply to the current version; older versions
show their release notes and a reference snapshot, if generated at
release time (`node scripts/src/generate-docs.mjs --snapshot vX.Y.Z`).
## Links
- Commit: [`6c92b63`](https://git.kubebase.de/admin/tool-evaluator/commit/6c92b63)
- Tag: [`v0.8.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.0)
-46
View File
@@ -1,46 +0,0 @@
# v0.8.0 — Release Notes
**Datum:** 2026-08-03 · **Tag:** [`v0.8.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.0)
## Neue Features
- **Vollständige Dokumentations-Site** in mkdocs-Optik unter `/docs`:
- **Handbuch** mit verständlichen Erklärungen zu allen Features
(Erste Schritte, Tool anlegen, Bewertungen, Vergleichen, Merkliste,
Analytics, Administration, Datenmodell).
- **Automatisch generierte Referenz** aus `lib/api-spec/openapi.yaml`:
alle Endpunkte und Datenfelder (Typ, Pflichtstatus, Constraints) —
damit ist garantiert, dass *jedes* Feature dokumentiert ist.
- **Suche** über Handbuch, Endpunkte und Felder.
- **Versions-Dropdown**: ältere Releases behalten ihre vollständige
Feld-/Endpunkt-Referenz als Snapshot.
- **Repo-Link** oben rechts zur Quelle.
- **Hilfe-Buttons (?) in Formularen** (NetBox-Stil): neben jedem Feld
springt ein Icon direkt zur Feldbeschreibung in der Doku.
## Fixes & Verbesserungen
- Doku-Generator `scripts/src/generate-docs.mjs` ersetzt den bisherigen
`sync-release-docs.mjs` (OpenAPI-Parsing, Handbuch, Suchindex, Snapshots).
- Dokumentation für v0.7.0 nachgezogen.
## API-Änderungen
- Keine Breaking Changes an der API.
## Betrieb / Upgrade
- **Env-Vars:** unverändert.
- **Migration:** keine.
- **Breaking Changes:** keine.
## Bekannte Einschränkungen
- Handbuch & Referenz gelten für die aktuelle Version; ältere Versionen
zeigen ihre Release-Notes und einen Referenz-Snapshot, sofern beim Release
erzeugt (`node scripts/src/generate-docs.mjs --snapshot vX.Y.Z`).
## Links
- Commit: [`6c92b63`](https://git.kubebase.de/admin/tool-evaluator/commit/6c92b63)
- Tag: [`v0.8.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.0)
-47
View File
@@ -1,47 +0,0 @@
# v0.8.1 — Release Notes
**Date:** 2026-08-03 · **Tag:** [`v0.8.1`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.1)
## New features
- **Standalone docs page**: The documentation is now available as its own,
mkdocs-like page at `toolr.kubebase.de/docs` — without the app shell
(own header with repo link, version dropdown, search, theme toggle
and link back to the app).
- **Navigation renamed**: The sidebar entry is now called **"Help"**
and leads to the standalone docs.
- **User guide completely revised**: 17 manual pages with
step-by-step instructions for all features (finding tools, creating a
tool, evaluating, watchlist, comparing, costs, analytics, plans,
administration, redundancy, trash, keyboard shortcuts, data model).
## Fixes & improvements
- Reference links are now case-insensitive
(schema/endpoint slugs such as `toolinput` and `ToolInput` both work).
- Manual links to endpoint anchors corrected (PascalCase operation IDs).
- Outdated, broken manual links (`vergleichen`, `watchlist` …) replaced.
- Documentation table headers and notice texts in the docs page
internationalized via i18n (de/en).
## API changes
- No changes to the API.
## Operations / upgrade
- **Env vars:** unchanged.
- **Migration:** none.
- **Breaking changes:** none. The docs page is reachable under `/docs` as
before; only the presentation is now standalone.
## Known limitations
- Manual & reference apply to the current version; older versions show
their release notes and a reference snapshot, if generated at release time
(`node scripts/src/generate-docs.mjs --snapshot vX.Y.Z`).
## Links
- Commit: [`fdf2d74`](https://git.kubebase.de/admin/tool-evaluator/commit/fdf2d74)
- Tag: [`v0.8.1`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.1)
-47
View File
@@ -1,47 +0,0 @@
# v0.8.1 — Release Notes
**Datum:** 2026-08-03 · **Tag:** [`v0.8.1`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.1)
## Neue Features
- **Standalone-Doku-Seite**: Die Dokumentation steht jetzt als eigene,
mkdocs-artige Seite unter `toolr.kubebase.de/docs` — ohne die App-Shell
(eigene Kopfzeile mit Repo-Link, Versions-Dropdown, Suche, Theme-Umschalter
und Link zurück zur App).
- **Navigation umbenannt**: Der Seitenleisten-Eintrag heißt jetzt **„Hilfe"**
und führt zur Standalone-Doku.
- **User Guide komplett überarbeitet**: 17 Handbuch-Seiten mit
Schritt-für-Schritt-Anleitungen für alle Funktionen (Tools finden, Tool
anlegen, Bewerten, Merkliste, Vergleichen, Kosten, Analytics, Pläne,
Administration, Redundanz, Papierkorb, Tastenkürzel, Datenmodell).
## Fixes & Verbesserungen
- Referenz-Links sind jetzt unabhängig von Groß-/Kleinschreibung
(Schema-/Endpoint-Slugs wie `toolinput` und `ToolInput` funktionieren beide).
- Handbuch-Links auf Endpunkt-Anker korrigiert (PascalCase-OperationIds).
- Veraltete, kaputte Handbuch-Links (`vergleichen`, `watchlist` …) ersetzt.
- Dokumentations-Tabellenkopfzeilen und Hinweistexte in der Doku-Seite über
i18n internationalisiert (de/en).
## API-Änderungen
- Keine Änderungen an der API.
## Betrieb / Upgrade
- **Env-Vars:** unverändert.
- **Migration:** keine.
- **Breaking Changes:** keine. Die Doku-Seite ist unter `/docs` erreichbar wie
bisher; lediglich die Darstellung ist nun eigenständig.
## Bekannte Einschränkungen
- Handbuch & Referenz gelten für die aktuelle Version; ältere Versionen zeigen
ihre Release-Notes und einen Referenz-Snapshot, sofern beim Release erzeugt
(`node scripts/src/generate-docs.mjs --snapshot vX.Y.Z`).
## Links
- Commit: [`fdf2d74`](https://git.kubebase.de/admin/tool-evaluator/commit/fdf2d74)
- Tag: [`v0.8.1`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.1)
-35
View File
@@ -1,35 +0,0 @@
# v0.8.2 — Release Notes
**Date:** 2026-08-04 · **Tag:** [`v0.8.2`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.2)
## Fixes & improvements
- **Mobile navigation in the docs**: The left navigation bar (manual,
endpoints, schemas) was completely hidden below `lg` (1024px).
There is now a menu button in the header that opens a side
navigation drawer with the same content — on small screens
the docs remain fully navigable.
- **Version dropdown shows the selected version**: When switching to a
different release (`/docs/releases/vX.Y.Z`), the dropdown wrongly stayed on
"current". The displayed version is now also derived from the releases route.
## API changes
- No changes to the API.
## Operations / upgrade
- **Env vars:** unchanged.
- **Migration:** none.
- **Breaking changes:** none.
## Known limitations
- Manual & reference apply to the current version; older versions show
their release notes and a reference snapshot, if generated at release time
(`node scripts/src/generate-docs.mjs --snapshot vX.Y.Z`).
## Links
- Commit: [`33cf261`](https://git.kubebase.de/admin/tool-evaluator/commit/33cf261)
- Tag: [`v0.8.2`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.2)
-36
View File
@@ -1,36 +0,0 @@
# v0.8.2 — Release Notes
**Datum:** 2026-08-04 · **Tag:** [`v0.8.2`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.2)
## Fixes & Verbesserungen
- **Mobile Navigation in der Doku**: Die linke Navigationsleiste (Handbuch,
Endpunkte, Schemas) wurde unterhalb von `lg` (1024px) komplett ausgeblendet.
Es gibt jetzt einen Menü-Button im Kopfbereich, der ein seitliches
Navigations-Drawer mit demselben Inhalt öffnet — auf kleinen Bildschirmen
bleibt die Doku voll navigierbar.
- **Versions-Dropdown zeigt die gewählte Version an**: Beim Wechsel zu einem
anderen Release (`/docs/releases/vX.Y.Z`) blieb das Dropdown fälschlich auf
„aktuell". Die angezeigte Version wird jetzt auch aus der releases-Route
abgeleitet.
## API-Änderungen
- Keine Änderungen an der API.
## Betrieb / Upgrade
- **Env-Vars:** unverändert.
- **Migration:** keine.
- **Breaking Changes:** keine.
## Bekannte Einschränkungen
- Handbuch & Referenz gelten für die aktuelle Version; ältere Versionen zeigen
ihre Release-Notes und einen Referenz-Snapshot, sofern beim Release erzeugt
(`node scripts/src/generate-docs.mjs --snapshot vX.Y.Z`).
## Links
- Commit: [`33cf261`](https://git.kubebase.de/admin/tool-evaluator/commit/33cf261)
- Tag: [`v0.8.2`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.2)
File diff suppressed because it is too large Load Diff
-49
View File
@@ -1,49 +0,0 @@
# v0.8.3 — Release Notes
**Date:** 2026-08-04 · **Tag:** [`v0.8.3`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.3)
## New features
- **Version-based documentation**: Each release now carries a
complete docs snapshot (manual + API reference). Via the
version dropdown you can reach the docs of the respective version
(`/docs/vX.Y.Z/…`) — including the manual and reference as they
applied at release time. The last 7 versions remain available.
- **Help buttons in forms (NetBox style)**: For evaluation, tool
create/edit, costs, comparing, watchlist and analytics there is now
a help icon that leads directly to the matching guide in the manual.
## Fixes & improvements
- **Gitea tag links fixed**: `…/tags/vX.Y.Z` was a 404 page; the
correct URL is `/releases/tag/vX.Y.Z` (in all release notes and the
docs page).
- Version switching now leads to the docs of the selected version instead of
just the release page; old `/docs/vX.Y.Z` redirects are removed.
- Docs generator: `--snapshot` creates manual + reference per version; on
build, the snapshots of the last 7 versions are included.
## API changes
- No changes to the API.
## Operations / upgrade
- **Env vars:** unchanged.
- **Migration:** none.
- **Breaking changes:** none. New release notes must use the correct
tag link (see `docs/releases/TEMPLATE.md`). For versions before
this release, snapshots can be created retroactively:
`node scripts/src/generate-docs.mjs --snapshot vX.Y.Z`.
## Known limitations
- Older releases without a snapshot (e.g. v0.6.0) only show their release notes,
no manual/reference. Backfill with `--snapshot`.
- The docs version corresponds to the state of the docs at the time the
snapshot was created; manual changes apply from the respective next release.
## Links
- Commit: [`3bb4598`](https://git.kubebase.de/admin/tool-evaluator/commit/3bb4598)
- Tag: [`v0.8.3`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.3)
-49
View File
@@ -1,49 +0,0 @@
# v0.8.3 — Release Notes
**Datum:** 2026-08-04 · **Tag:** [`v0.8.3`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.3)
## Neue Features
- **Versionsbasierte Dokumentation**: Jedes Release trägt jetzt einen
vollständigen Doku-Snapshot (Handbuch + API-Referenz). Über das
Versions-Dropdown gelangst du zur Doku der jeweiligen Version
(`/docs/vX.Y.Z/…`) — inklusive Handbuch und Referenz, wie sie zum
Release-Zeitpunkt galten. Die letzten 7 Versionen bleiben verfügbar.
- **Hilfe-Buttons in Formularen (NetBox-Stil)**: Bei Bewertung, Tool
anlegen/bearbeiten, Kosten, Vergleichen, Merkliste und Analytics gibt es
jetzt ein Hilfe-Icon, das direkt zur passenden Anleitung im Handbuch führt.
## Fixes & Verbesserungen
- **Gitea-Tag-Links korrigiert**: `…/tags/vX.Y.Z` war eine 404-Seite; die
korrekte URL lautet `/releases/tag/vX.Y.Z` (in allen Release-Notes und der
Doku-Seite).
- Versions-Wechsel führt jetzt zur Doku der gewählten Version statt direkt
nur zur Release-Seite; alte `/docs/vX.Y.Z`-Redirects entfallen.
- Doku-Generator: `--snapshot` erzeugt Handbuch + Referenz pro Version; beim
Build werden die Snapshots der letzten 7 Versionen eingespielt.
## API-Änderungen
- Keine Änderungen an der API.
## Betrieb / Upgrade
- **Env-Vars:** unverändert.
- **Migration:** keine.
- **Breaking Changes:** keine. Neue Release-Notes müssen den korrekten
Tag-Link verwenden (siehe `docs/releases/TEMPLATE.md`). Für Versionen vor
diesem Release können Snapshots nachträglich erzeugt werden:
`node scripts/src/generate-docs.mjs --snapshot vX.Y.Z`.
## Bekannte Einschränkungen
- Ältere Releases ohne Snapshot (z. B. v0.6.0) zeigen nur ihre Release-Notes,
kein Handbuch/keine Referenz. Mit `--snapshot` nachpflegen.
- Die Doku-Version entspricht dem Doku-Stand zum Zeitpunkt der Erstellung des
Snapshots; Änderungen am Handbuch gelten ab dem jeweils nächsten Release.
## Links
- Commit: [`3bb4598`](https://git.kubebase.de/admin/tool-evaluator/commit/3bb4598)
- Tag: [`v0.8.3`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.3)
-45
View File
@@ -1,45 +0,0 @@
# v0.8.4 — Release Notes
**Date:** 2026-08-04 · **Tag:** [`v0.8.4`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.4)
## New features
- **Full app localization (German/English)**: All pages and components are
now translated — tool detail page, admin area, trash, redundancy dashboard,
compare, watchlist, analytics, create and edit tool, admin tools tab,
category combobox and theme toggle. The language picker in the top right
switches the whole UI.
- **Bilingual documentation**: The manual (all 17 pages) and the release
notes are now available in German and English. The docs page has a
language switcher that switches content, navigation titles and search.
Versioned docs snapshots (v0.8.1, v0.8.2) also contain the English pages.
## Fixes & improvements
- All remaining hardcoded UI strings replaced with `t()` calls; the locales
are in sync (453 keys per language, 0 missing).
- API reference: table headers (Name/In), required badges and request body
labels in the docs are translated.
- Docs search respects the active language (separate index per language).
## API changes
- No changes to the API.
## Operations / upgrade
- **Env vars:** unchanged.
- **Migration:** none.
- **Breaking changes:** none. The docs generator output now includes
additional English files (`*.en.md`) and `search.en.json`; these are
generated automatically during the build.
## Known limitations
- Versioned docs snapshots before v0.8.1 (e.g. v0.6.0) do not contain
English manual pages; the German version is used as fallback there.
## Links
- Commit: [`3de59cc`](https://git.kubebase.de/admin/tool-evaluator/commit/3de59cc)
- Tag: [`v0.8.4`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.4)
-48
View File
@@ -1,48 +0,0 @@
# v0.8.4 — Release Notes
**Datum:** 2026-08-04 · **Tag:** [`v0.8.4`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.4)
## Neue Features
- **Komplette Lokalisierung der App (Deutsch/Englisch)**: Alle Seiten und
Komponenten sind jetzt übersetzt — Tool-Detailseite, Verwaltung, Papierkorb,
Redundanz-Dashboard, Vergleichen, Merkliste, Analytics, Tool anlegen und
bearbeiten, Admin-Bereich inkl. Tools-Tab, Kategorie-Combobox und
Theme-Umschalter. Die Sprachauswahl oben rechts wechselt die gesamte
Oberfläche.
- **Dokumentation zweisprachig**: Handbuch (alle 17 Seiten) und Release-Notes
sind jetzt in Deutsch und Englisch verfügbar. In der Doku gibt es einen
Sprach-Umschalter, der Inhalte, Navigationstitel und Suche umschaltet.
Auch versionierte Doku-Snapshots (v0.8.1, v0.8.2) enthalten die englischen
Seiten.
## Fixes & Verbesserungen
- Alle verbliebenen hartkodierten UI-Strings durch `t()`-Aufrufe ersetzt;
die Locales sind synchron (453 Keys pro Sprache, 0 fehlend).
- API-Referenz: Tabellen-Header (Name/In), Pflicht-Badges und
Request-Body-Label der Doku übersetzt.
- Suche in der Doku berücksichtigt die aktive Sprache (eigener Index pro
Sprache).
## API-Änderungen
- Keine Änderungen an der API.
## Betrieb / Upgrade
- **Env-Vars:** unverändert.
- **Migration:** keine.
- **Breaking Changes:** keine. Die Doku-Generator-Ausgabe umfasst jetzt
zusätzliche englische Dateien (`*.en.md`) und `search.en.json`; beim Build
werden diese automatisch erzeugt.
## Bekannte Einschränkungen
- Versionierte Doku-Snapshots vor v0.8.1 (z. B. v0.6.0) enthalten keine
englischen Handbuch-Seiten; dort greift die deutsche Fassung als Fallback.
## Links
- Commit: [`3de59cc`](https://git.kubebase.de/admin/tool-evaluator/commit/3de59cc)
- Tag: [`v0.8.4`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.4)
-45
View File
@@ -1,45 +0,0 @@
# v0.8.5 — Release Notes
**Date:** 2026-08-04 · **Tag:** [`v0.8.5`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.5)
## New features
- **Help buttons across the app**: The main pages (Home, Browse tools, Trash,
Redundancy dashboard, Admin tools) as well as the premium dialog and the
password dialog now show a book icon next to the title that links directly
to the matching manual page (following the existing buttons on the tool
detail, create, edit, compare, watchlist and analytics pages).
## Fixes & improvements
- **Translated submit button when creating a tool**: The "Submit Tool" /
"Adding Tool…" button was still hardcoded and appeared in English in the
German UI. It is now translated, e.g. "Tool einreichen" / "Füge Tool
hinzu…" in German.
- **More translated UI strings**: View mode (Grid/Table/Rows), list density
(Compact/Comfortable), minimum rating in the filter and the password hint
"min. 6 characters" are now translated correctly.
- **Consistent docs terminology**: The manual now consistently calls the
watchlist "Merkliste" in all German pages and release notes; the English
version stays "Watchlist". The `watchlist` permission name and the URLs
are unchanged.
- All locales are in sync (462 keys per language, 0 missing).
## API changes
- No changes to the API.
## Operations / upgrade
- **Env vars:** unchanged.
- **Migration:** none.
- **Breaking changes:** none.
## Known limitations
- None.
## Links
- Commit: [`70e72da`](https://git.kubebase.de/admin/tool-evaluator/commit/70e72da)
- Tag: [`v0.8.5`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.5)
-46
View File
@@ -1,46 +0,0 @@
# v0.8.5 — Release Notes
**Datum:** 2026-08-04 · **Tag:** [`v0.8.5`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.5)
## Neue Features
- **Hilfe-Buttons in der gesamten App**: Auf den Hauptseiten (Start, Tools
durchsuchen, Papierkorb, Redundanz-Dashboard, Admin-Tools) sowie in
Premium-Dialog und Passwort-Dialog gibt es jetzt ein Buch-Icon neben dem
Titel, das direkt zur passenden Anleitung im Handbuch verlinkt (analog zu
den bestehenden Buttons auf Tool-Detail-, Anlegen-, Bearbeiten-,
Vergleichen-, Merkliste- und Analytics-Seiten).
## Fixes & Verbesserungen
- **Submit-Button beim Tool-Anlegen übersetzt**: Der Button „Submit Tool" /
„Adding Tool…" war noch hartkodiert und erschien auf Deutsch auf Englisch.
Jetzt: „Tool einreichen" / „Füge Tool hinzu…" (bzw. die englischen
Entsprechungen).
- **Weitere übersetzte UI-Strings**: Ansichtsmodus (Raster/Tabelle/Zeilen),
Listendichte (Kompakt/Komfortabel), Mindestbewertung im Filter sowie der
Passwort-Hinweis „min. 6 Zeichen" werden jetzt korrekt übersetzt.
- **Doku-Terminologie konsistent**: Das Handbuch heißt die Merkliste jetzt
einheitlich „Merkliste" (statt „Watchlist") in allen deutschen Seiten und
Release-Notes; die englische Fassung bleibt „Watchlist". Die
Permissions-Bezeichnung `watchlist` und die URLs sind unverändert.
- Alle Locales synchron (462 Keys pro Sprache, 0 fehlend).
## API-Änderungen
- Keine Änderungen an der API.
## Betrieb / Upgrade
- **Env-Vars:** unverändert.
- **Migration:** keine.
- **Breaking Changes:** keine.
## Bekannte Einschränkungen
- Keine.
## Links
- Commit: [`70e72da`](https://git.kubebase.de/admin/tool-evaluator/commit/70e72da)
- Tag: [`v0.8.5`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.5)
+46
View File
@@ -0,0 +1,46 @@
# v0.9.0 — Release Notes
**Datum:** 2026-08-05 · **Tag:** [`v0.9.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.9.0)
## Neue Features
- **Neuausrichtung der Versionierung:** Die Dokumentation startet mit dieser
Version neu bei `v0.9.0`. Ältere Release-Notizen und Versionen-Snapshots
(v0.6.0v0.8.6) wurden entfernt. Das Handbuch und die Referenz (API/Admin)
sind jetzt wieder für jede Version verfügbar — auch für neuere Versionen.
- **Sprachsuffixe:** Alle Dokumentationsdateien verwenden jetzt einheitlich die
Suffixe `.de.md` und `.en.md`. Damit lassen sich später weitere Sprachen
ergänzen; die App liefert automatisch die passende Sprachfassung.
- **Import — fertige Beispieldatei:** Unter `docs/examples/import-tools.yaml`
liegt eine sofort importierbare YAML-Beispieldatei mit 24 gängigen
Enterprise-Tools (Kategorien wie Kommunikation, Monitoring, CI/CD,
Infrastruktur). Die Datei ist in der Administration-Dokumentation verlinkt.
## Fixes & Verbesserungen
- Der Versionsumschalter zeigt die Dokumentation jetzt auch für neuere
Versionen korrekt an (vorher fehlende Versionen-Snapshots machten Handbuch und
Referenz nicht verfügbar).
- Wording bereinigt: Die Tagline und Platzhaltertexte ohne Berufsbezeichnung
(„Bewerte dein Tool." / „Rate your Tool.", „Anonymer Benutzer" /
„Anonymous User"). Es werden keine Gender-Sonderformen mehr verwendet
(`Nutzer:innen``Nutzer`, `Ersteller:in``Ersteller`).
## API-Änderungen
- Keine Änderungen gegenüber v0.8.6 (siehe `lib/api-spec/openapi.yaml`).
## Betrieb / Upgrade
- **Migration:** keine. Bereits vorhandene Tools-, Bewertungs- und Konto-Daten
bleiben unverändert.
- **Breaking Changes:** keine.
## Bekannte Einschränkungen
- Keine im Rahmen dieses Releases.
## Links
- Commit: [`6eb3dfd`](https://git.kubebase.de/admin/tool-evaluator/commit/6eb3dfd)
- Tag: [`v0.9.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.9.0)
+46
View File
@@ -0,0 +1,46 @@
# v0.9.0 — Release Notes
**Date:** 2026-08-05 · **Tag:** [`v0.9.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.9.0)
## New Features
- **Versioning reset:** Documentation starts fresh at `v0.9.0` with this
release. Older release notes and version snapshots (v0.6.0v0.8.6) have been
removed. The handbook and reference (API/admin) are now available again for
every version — including newer versions.
- **Language suffixes:** All documentation files now consistently use the
`.de.md` and `.en.md` suffixes. This makes it easy to add more languages
later; the app automatically serves the matching language variant.
- **Import — ready-to-use example file:** A ready-to-import YAML example file
with 24 common enterprise tools is provided at
`docs/examples/import-tools.yaml` (categories such as communication,
monitoring, CI/CD, infrastructure). The file is linked from the
administration documentation.
## Fixes & Polish
- The version switcher now displays documentation correctly for newer versions
as well (previously, missing version snapshots made the handbook and
reference unavailable).
- Wording cleanup: tagline and placeholder texts no longer reference a specific
job title ("Rate your Tool.", "Anonymous User"). No gender-specific forms are
used anymore.
## API Changes
- No changes compared to v0.8.6 (see `lib/api-spec/openapi.yaml`).
## Operations / Upgrade
- **Migration:** none. Existing tool, rating, and account data remain
unchanged.
- **Breaking Changes:** none.
## Known Limitations
- None within the scope of this release.
## Links
- Commit: [`6eb3dfd`](https://git.kubebase.de/admin/tool-evaluator/commit/6eb3dfd)
- Tag: [`v0.9.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.9.0)
@@ -32,6 +32,49 @@ Zentraler Zugriff auf den Tool-Katalog.
- **Massenaktion:** mehrere Tools auswählen und in den Papierkorb verschieben
(Bestätigungsdialog; soft gelöschte Tools sind aus allen öffentlichen Ansichten
entfernt und können wiederhergestellt oder endgültig gelöscht werden).
- **Importieren** (Premium): über die Schaltfläche **Importieren** oben rechts
können mehrere Tools gleichzeitig angelegt werden. Das Format wird automatisch
erkannt (CSV, JSON oder YAML); für CSV lässt sich der Trenner wählen
(automatisch, Komma, Semikolon oder Tabulator). Die Daten lassen sich direkt
ins Formular einfügen oder als Datei hochladen.
Unterstützte Felder: `name` (Pflicht), `description`, `category`, `websiteUrl`,
`iconUrl`, `features`, `tags` (`features`/`tags` sind mit `|` getrennte Listen).
**CSV-Beispiel:**
```csv
name;description;category;features;tags
Grafana;Observability- und Dashboard-Plattform;Monitoring;Dashboards|Alerts;devops
Prometheus;Zeitreihen-Datenbank;Monitoring;Metrics;devops
```
**JSON-Beispiel:**
```json
[
{ "name": "Grafana", "category": "Monitoring", "features": ["Dashboards"] },
{ "name": "Prometheus", "category": "Monitoring" }
]
```
**YAML-Beispiel:**
```yaml
- name: Grafana
category: Monitoring
features:
- Dashboards
- name: Prometheus
category: Monitoring
```
Eine umfangreichere, fertig importierbare Beispieldatei liegt unter
`docs/examples/import-tools.yaml` (24 gängige Enterprise-Tools).
Beim Import werden einzelne fehlerhafte Zeilen übersprungen und mit Zeilennummer
aufgelistet; korrekte Zeilen werden trotzdem angelegt. Jeder Import erzeugt
Audit-Log-Einträge.
## Tab „Audit-Log"
@@ -32,6 +32,49 @@ Central access to the tool catalog.
- **Bulk action:** select multiple tools and move them to the trash
(confirmation dialog; soft-deleted tools are removed from all public views
and can be restored or permanently deleted).
- **Import** (premium): via the **Import** button at the top right, multiple
tools can be created at once. The format is detected automatically (CSV,
JSON or YAML); for CSV the delimiter can be chosen (auto-detect, comma,
semicolon or tab). Data can be pasted directly into the form or uploaded
as a file.
Supported fields: `name` (required), `description`, `category`, `websiteUrl`,
`iconUrl`, `features`, `tags` (`features`/`tags` are `|`-separated lists).
**CSV example:**
```csv
name;description;category;features;tags
Grafana;Observability and dashboard platform;Monitoring;Dashboards|Alerts;devops
Prometheus;Time-series database;Monitoring;Metrics;devops
```
**JSON example:**
```json
[
{ "name": "Grafana", "category": "Monitoring", "features": ["Dashboards"] },
{ "name": "Prometheus", "category": "Monitoring" }
]
```
**YAML example:**
```yaml
- name: Grafana
category: Monitoring
features:
- Dashboards
- name: Prometheus
category: Monitoring
```
A larger, ready-to-import example file is located at
`docs/examples/import-tools.yaml` (24 common enterprise tools).
During import, individual faulty rows are skipped and listed with their row
number; correct rows are still created. Every import produces audit-log
entries.
## "Audit log" tab
@@ -6,7 +6,7 @@ Besuch bis zum Anlegen und Bewerten eines Tools.
## 1. Anmelden
Die meisten Aktionen (Tool anlegen, bewerten, Watchlist, Vergleichen) erfordern
Die meisten Aktionen (Tool anlegen, bewerten, Merkliste, Vergleichen) erfordern
ein Konto. Klicke unten links auf **Anmelden**. Je nach Konfiguration der
Instanz hast du zwei Möglichkeiten:
@@ -47,7 +47,7 @@ Siehe [Bewerten](/docs/handbook/bewerten).
## 5. Weiterführend
- [Tools vergleichen](/docs/handbook/vergleichen)
- [Watchlist](/docs/handbook/watchlist)
- [Merkliste](/docs/handbook/watchlist)
- [Analytics](/docs/handbook/analytics)
- [Pläne & Berechtigungen](/docs/handbook/plaene)
- [Administration](/docs/handbook/administration)
@@ -2,7 +2,7 @@
# Willkommen bei toolr
toolr ist eine Plattform zum **Entdecken, Bewerten und Vergleichen von
Entwicklungstools**. Nutzer:innen pflegen einen gemeinsamen Katalog von Tools,
Entwicklungstools**. Nutzer pflegen einen gemeinsamen Katalog von Tools,
vergeben Bewertungen (Nützlichkeit & Bedienbarkeit) und nutzen Statistiken, um
die richtige Wahl zu treffen.
@@ -12,13 +12,14 @@ die richtige Wahl zu treffen.
| --- | --- | --- |
| **Tools durchsuchen** | Katalog filtern, sortieren und durchsuchen | Alle |
| **Tool anlegen** | Neues Tool mit Beschreibung, Kategorie, Features & Tags eintragen | Angemeldet |
| **Tool bearbeiten/löschen** | Eigene Tools pflegen (Ersteller:in oder Admin) | Angemeldet |
| **Tool bearbeiten/löschen** | Eigene Tools pflegen (Ersteller oder Admin) | Angemeldet |
| **Bewerten** | Nützlichkeit & Bedienbarkeit (15) plus Kommentar vergeben | Angemeldet |
| **Watchlist** | Tools als Favoriten speichern | Premium |
| **Merkliste** | Tools als Favoriten speichern | Premium |
| **Vergleichen** | Tools nebeneinander gegenüberstellen | Premium |
| **Kosten erfassen** | Lizenz- und Kostenmodelle je Tool eintragen | Premium |
| **Analytics** | Statistiken, Top-Tools, Verteilungen | Alle |
| **Papierkorb** | Soft-gelöschte Tools wiederherstellen oder endgültig löschen | Premium |
| **Import** | Mehrere Tools per CSV, JSON oder YAML anlegen | Admin (Premium) |
| **Admin** | Nutzerverwaltung, Audit-Log, Systeminformationen | Admin |
| **Redundanz** | Automatische Doppelungs-Erkennung | Admin |
@@ -18,6 +18,7 @@ tools**. Users maintain a shared catalog of tools, submit ratings
| **Record costs** | Enter license and cost models per tool | Premium |
| **Analytics** | Statistics, top tools, distributions | Everyone |
| **Trash** | Restore soft-deleted tools or delete them permanently | Premium |
| **Import** | Create multiple tools via CSV, JSON or YAML | Admin (premium) |
| **Admin** | User management, audit log, system information | Admin |
| **Redundancy detection** | Automatic duplicate detection | Admin |
@@ -1,7 +1,7 @@
[
{
"slug": "index",
"file": "index.md",
"file": "index.de.md",
"title": "Überblick",
"order": 1,
"fileEn": "index.en.md",
@@ -9,7 +9,7 @@
},
{
"slug": "getting-started",
"file": "getting-started.md",
"file": "getting-started.de.md",
"title": "Erste Schritte",
"order": 2,
"fileEn": "getting-started.en.md",
@@ -17,7 +17,7 @@
},
{
"slug": "konto",
"file": "konto.md",
"file": "konto.de.md",
"title": "Anmelden & Konto",
"order": 3,
"fileEn": "konto.en.md",
@@ -25,7 +25,7 @@
},
{
"slug": "tools-finden",
"file": "tools-finden.md",
"file": "tools-finden.de.md",
"title": "Tools finden & durchsuchen",
"order": 4,
"fileEn": "tools-finden.en.md",
@@ -33,7 +33,7 @@
},
{
"slug": "tool-anlegen",
"file": "tool-anlegen.md",
"file": "tool-anlegen.de.md",
"title": "Tool anlegen",
"order": 5,
"fileEn": "tool-anlegen.en.md",
@@ -41,7 +41,7 @@
},
{
"slug": "tool-bearbeiten",
"file": "tool-bearbeiten.md",
"file": "tool-bearbeiten.de.md",
"title": "Tool bearbeiten & löschen",
"order": 6,
"fileEn": "tool-bearbeiten.en.md",
@@ -49,7 +49,7 @@
},
{
"slug": "bewerten",
"file": "bewerten.md",
"file": "bewerten.de.md",
"title": "Bewerten",
"order": 7,
"fileEn": "bewerten.en.md",
@@ -57,15 +57,15 @@
},
{
"slug": "watchlist",
"file": "watchlist.md",
"title": "Watchlist",
"file": "watchlist.de.md",
"title": "Merkliste",
"order": 8,
"fileEn": "watchlist.en.md",
"titleEn": "Watchlist"
},
{
"slug": "vergleichen",
"file": "vergleichen.md",
"file": "vergleichen.de.md",
"title": "Vergleichen",
"order": 9,
"fileEn": "vergleichen.en.md",
@@ -73,7 +73,7 @@
},
{
"slug": "analytics",
"file": "analytics.md",
"file": "analytics.de.md",
"title": "Analytics",
"order": 10,
"fileEn": "analytics.en.md",
@@ -81,7 +81,7 @@
},
{
"slug": "plaene",
"file": "plaene.md",
"file": "plaene.de.md",
"title": "Pläne & Berechtigungen",
"order": 11,
"fileEn": "plaene.en.md",
@@ -89,7 +89,7 @@
},
{
"slug": "kosten",
"file": "kosten.md",
"file": "kosten.de.md",
"title": "Kosten erfassen",
"order": 12,
"fileEn": "kosten.en.md",
@@ -97,7 +97,7 @@
},
{
"slug": "administration",
"file": "administration.md",
"file": "administration.de.md",
"title": "Administration",
"order": 13,
"fileEn": "administration.en.md",
@@ -105,7 +105,7 @@
},
{
"slug": "redundanz",
"file": "redundanz.md",
"file": "redundanz.de.md",
"title": "Redundanz-Dashboard",
"order": 14,
"fileEn": "redundanz.en.md",
@@ -113,7 +113,7 @@
},
{
"slug": "papierkorb",
"file": "papierkorb.md",
"file": "papierkorb.de.md",
"title": "Papierkorb",
"order": 15,
"fileEn": "papierkorb.en.md",
@@ -121,7 +121,7 @@
},
{
"slug": "tastatur",
"file": "tastatur.md",
"file": "tastatur.de.md",
"title": "Tastenkürzel & Kommandopalette",
"order": 16,
"fileEn": "tastatur.en.md",
@@ -129,7 +129,7 @@
},
{
"slug": "datenmodell",
"file": "datenmodell.md",
"file": "datenmodell.de.md",
"title": "Datenmodell",
"order": 17,
"fileEn": "datenmodell.en.md",
@@ -22,7 +22,7 @@ Der aktive Modus steht im Endpunkt
Dein Profil (Avatar, Name, E-Mail, Tarif) siehst du unten links im
Benutzermenü. Dort stehen dir folgende Aktionen zur Verfügung:
- **Watchlist** — deine gespeicherten Tools (nur mit dem entsprechenden Tarif).
- **Merkliste** — deine gespeicherten Tools (nur mit dem entsprechenden Tarif).
- **Papierkorb** — wiederherstellbare, gelöschte Tools (Premium/Enterprise).
- **Passwort ändern** — für lokale Konten direkt in toolr; für OIDC-Konten wird
die Passwortverwaltung im Identitätsanbieter angeboten.
@@ -47,7 +47,7 @@ API-Referenz: [`POST /auth/me/password`](/docs/reference/endpoints/auth#changeMy
- die **Listenansicht** und **Dichte** im Bereich Tools durchsuchen anpassen
(siehe [Tools finden & durchsuchen](/docs/handbook/tools-finden)).
Deine Präferenzen (inkl. Watchlist) werden im Endpunkt
Deine Präferenzen (inkl. Merkliste) werden im Endpunkt
[`GET /auth/me/preferences`](/docs/reference/endpoints/auth#getMePreferences)
gespeichert und über [`PUT /auth/me/preferences`](/docs/reference/endpoints/auth#updateMePreferences)
aktualisiert.
@@ -9,7 +9,7 @@ Feature-Beschränkungen.
| Tarif | Beschreibung |
| --- | --- |
| **Free** | Grundfunktionen: suchen, filtern, ansehen, Analytics |
| **Premium** | Zusätzlich Watchlist, Vergleichen, Papierkorb, Kosten |
| **Premium** | Zusätzlich Merkliste, Vergleichen, Papierkorb, Kosten |
| **Enterprise** | Alle Premium-Features + erweiterter Support |
### Feature-Berechtigungen
@@ -19,9 +19,10 @@ Premium/Enterprise schalten folgende Features frei:
| Feature | Funktion | Mehr erfahren |
| --- | --- | --- |
| `compare` | Tools vergleichen | [Vergleichen](/docs/handbook/vergleichen) |
| `watchlist` | Favoritenliste | [Watchlist](/docs/handbook/watchlist) |
| `watchlist` | Favoritenliste | [Merkliste](/docs/handbook/watchlist) |
| `trash` | Papierkorb (soft gelöschte Tools) | [Papierkorb](/docs/handbook/papierkorb) |
| `costs` | Kosten-/Lizenzmodelle erfassen | [Kosten erfassen](/docs/handbook/kosten) |
| `tool-import` | Tools per CSV/JSON/YAML importieren (Admin) | [Administration](/docs/handbook/administration) |
Fehlt dir ein Feature, zeigt die App einen **Upgrade-Hinweis** mit Link zur
Tarifverwaltung.
@@ -22,6 +22,7 @@ Premium/Enterprise unlock the following features:
| `watchlist` | Favorites list | [Watchlist](/docs/handbook/watchlist) |
| `trash` | Trash (soft-deleted tools) | [Trash](/docs/handbook/papierkorb) |
| `costs` | Record cost/license models | [Recording costs](/docs/handbook/kosten) |
| `tool-import` | Import tools via CSV/JSON/YAML (admin) | [Administration](/docs/handbook/administration) |
If you are missing a feature, the app shows an **upgrade notice** with a link
to the plan management.
@@ -15,7 +15,7 @@ Ohne Eingabe zeigt die Palette:
- **Zuletzt angesehen** — die letzten 5 Tools, die du besucht hast.
- **Navigation** — Tools durchsuchen, Tool hinzufügen, Analytics sowie
(abhängig von Berechtigungen) Watchlist, Papierkorb und Admin.
(abhängig von Berechtigungen) Merkliste, Papierkorb und Admin.
### Suche
@@ -43,7 +43,7 @@ Mit **Filter zurücksetzen** bzw. **Alle entfernen** räumst du sie wieder auf.
- **Ansicht wechseln:** Raster / Tabelle / Zeilen.
- **Dichte:** gemütlich / kompakt (Schieberegler).
Deine Auswahl wird gespeichert — lokal im Browser und für angemeldete Nutzer:innen
Deine Auswahl wird gespeichert — lokal im Browser und für angemeldete Nutzer
zusätzlich serverseitig in den Präferenzen. Ansicht, Dichte, Suche, Filter und
Sortierung werden dabei in die URL übernommen, sodass du Ergebnisse teilen
kannst.
@@ -54,12 +54,12 @@ In der Tabellenansicht sind die Spalten **Tool**, **Bewertung** und **Anzahl
Bewertungen** sortierbar. Beim Überfahren einer Zeile erscheint eine Vorschau
mit Bewertungsdetails, Tags und Mini-Balken.
## Auswählen für Vergleich & Watchlist
## Auswählen für Vergleich & Merkliste
- Auf jeder Karte/Zeile findest du ein **Vergleichs-Icon**, mit dem du Tools zur
[Vergleichsleiste](/docs/handbook/vergleichen) hinzufügst.
- Das **Lesezeichen-Icon** speichert Tools in deiner
[Watchlist](/docs/handbook/watchlist) (nur mit dem entsprechenden Tarif).
[Merkliste](/docs/handbook/watchlist) (nur mit dem entsprechenden Tarif).
## API
@@ -1,10 +1,10 @@
# Watchlist
# Merkliste
Die **Watchlist** ist eine persönliche Favoritenliste. Tools darin kannst du
Die **Merkliste** ist eine persönliche Favoritenliste. Tools darin kannst du
jederzeit per Klick wieder aufrufen und vergleichen.
> Die Watchlist ist ein **Premium-Feature** (Premium/Enterprise) und steht
> Die Merkliste ist ein **Premium-Feature** (Premium/Enterprise) und steht
> Admins immer zur Verfügung.
## Voraussetzung
@@ -17,18 +17,18 @@ beim Lesezeichen ein Hinweis auf den Tarifwechsel
- Auf jeder Karte/Zeile im Bereich **Tools durchsuchen** findest du das
**Lesezeichen-Icon**.
- Ein Klick speichert das Tool in deiner Watchlist — das Icon wird gefüllt.
- Ein Klick speichert das Tool in deiner Merkliste — das Icon wird gefüllt.
- Ein erneuter Klick entfernt es wieder.
## Watchlist ansehen
## Merkliste ansehen
Öffne die Watchlist über das Benutzermenü oder die Seitenleiste. Sie zeigt alle
Öffne die Merkliste über das Benutzermenü oder die Seitenleiste. Sie zeigt alle
gespeicherten Tools als Karten. Das gefüllte Lesezeichen auf einer Karte
entfernt das Tool aus der Liste.
## Wo wird die Watchlist gespeichert?
## Wo wird die Merkliste gespeichert?
Die Watchlist ist eine Liste von Tool-IDs in deinen **Benutzerpräferenzen**.
Die Merkliste ist eine Liste von Tool-IDs in deinen **Benutzerpräferenzen**.
Damit ist sie geräteübergreifend mit deinem Konto verbunden.
API-Referenz: [`GET /auth/me/watchlist`](/docs/reference/endpoints/auth#getMeWatchlist).
API-Referenz: [`GET /auth/me/watchlist`](/docs/reference/endpoints/auth#getMeMerkliste).
@@ -2206,6 +2206,78 @@
}
]
},
{
"name": "ToolImportBody",
"description": "",
"fields": [
{
"name": "format",
"type": {
"kind": "type",
"value": "string"
},
"required": false,
"description": "",
"constraints": "auto, csv, json, yaml"
},
{
"name": "delimiter",
"type": {
"kind": "type",
"value": "string"
},
"required": false,
"description": "",
"constraints": "auto, comma, semicolon, tab"
},
{
"name": "data",
"type": {
"kind": "type",
"value": "string"
},
"required": true,
"description": "",
"constraints": ""
}
]
},
{
"name": "ToolImportResponse",
"description": "",
"fields": [
{
"name": "imported",
"type": {
"kind": "type",
"value": "integer"
},
"required": true,
"description": "",
"constraints": ""
},
{
"name": "total",
"type": {
"kind": "type",
"value": "integer"
},
"required": true,
"description": "",
"constraints": ""
},
{
"name": "errors",
"type": {
"kind": "array",
"value": "object"
},
"required": true,
"description": "",
"constraints": ""
}
]
},
{
"name": "ToolUpdate",
"description": "",
+27
View File
@@ -0,0 +1,27 @@
# v0.9.1 — Release Notes
**Datum:** 2026-08-05 · **Tag:** [`v0.9.1`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.9.1)
## Fixes & Verbesserungen
- **Dokumentation:** Release-Notizen werden wieder korrekt auf der Hilfe-Seite
angezeigt. Ein doppelter `releases/`-Pfad in der versionierten
Dokumentation lieferte zuvor eine leere Seite (404).
## API-Änderungen
- Keine.
## Betrieb / Upgrade
- **Migration:** keine. **Breaking Changes:** keine.
## Bekannte Einschränkungen
- Das Vergleichen war serverseitig auf 8 Tools begrenzt; wird in v0.9.2 auf 9
erhöht und im Client konsistent abgesichert.
## Links
- Commit: [`e66a332`](https://git.kubebase.de/admin/tool-evaluator/commit/e66a332)
- Tag: [`v0.9.1`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.9.1)

Some files were not shown because too many files have changed in this diff Show More