diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml
index b53ee84..54719b0 100644
--- a/.gitea/workflows/build.yaml
+++ b/.gitea/workflows/build.yaml
@@ -27,8 +27,14 @@ jobs:
- name: Build and push
run: |
SHA=$(git rev-parse --short HEAD)
+ FULL_SHA=$(git rev-parse HEAD)
IMAGE="git.kubebase.de/${{ gitea.repository }}"
- docker build --no-cache -t "${IMAGE}:sha-${SHA}" -t "${IMAGE}:latest" .
+ if [ "${{ gitea.ref_type }}" = "tag" ]; then VERSION="${{ gitea.ref_name }}"; else VERSION=""; fi
+ docker build --no-cache \
+ --build-arg COMMIT_SHA="$FULL_SHA" \
+ --build-arg BUILD_DATE="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
+ --build-arg VERSION="$VERSION" \
+ -t "${IMAGE}:sha-${SHA}" -t "${IMAGE}:latest" .
docker push "${IMAGE}:sha-${SHA}"
docker push "${IMAGE}:latest"
diff --git a/Dockerfile b/Dockerfile
index fe4ddab..f93d394 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -43,6 +43,13 @@ COPY --from=builder /app/artifacts/toolrate/dist artifacts/toolrate/dist
ENV NODE_ENV=production
ENV STATIC_DIR=/app/artifacts/toolrate/dist/public
+ARG COMMIT_SHA=""
+ARG BUILD_DATE=""
+ARG VERSION=""
+ENV COMMIT_SHA=${COMMIT_SHA}
+ENV BUILD_DATE=${BUILD_DATE}
+ENV VERSION=${VERSION}
+
EXPOSE 8080
CMD ["sh", "-c", "pnpm --filter @workspace/db run push-force && node --enable-source-maps artifacts/api-server/dist/index.mjs"]
diff --git a/artifacts/api-server/src/routes/health.ts b/artifacts/api-server/src/routes/health.ts
index c0a1446..00b380b 100644
--- a/artifacts/api-server/src/routes/health.ts
+++ b/artifacts/api-server/src/routes/health.ts
@@ -8,4 +8,14 @@ router.get("/healthz", (_req, res) => {
res.json(data);
});
+router.get("/version", (_req, res) => {
+ const retention = Number(process.env["TRASH_RETENTION_DAYS"] ?? "0");
+ res.json({
+ version: process.env["VERSION"] || "dev",
+ commitSha: process.env["COMMIT_SHA"] || null,
+ buildDate: process.env["BUILD_DATE"] || null,
+ trashRetentionDays: Number.isFinite(retention) ? retention : 0,
+ });
+});
+
export default router;
diff --git a/artifacts/toolrate/src/components/layout.tsx b/artifacts/toolrate/src/components/layout.tsx
index 3a1b175..254ee10 100644
--- a/artifacts/toolrate/src/components/layout.tsx
+++ b/artifacts/toolrate/src/components/layout.tsx
@@ -1,6 +1,7 @@
import { Link, useLocation } from "wouter";
import { LayoutDashboard, Wrench, PlusCircle, BarChart3, LogIn, LogOut, User, ShieldCheck, AlertTriangle, Trash2 } from "lucide-react";
import { useAuth } from "@/hooks/use-auth";
+import { useGetVersion, getGetVersionQueryKey } from "@workspace/api-client-react";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { ThemeToggle } from "@/components/theme-toggle";
@@ -8,6 +9,9 @@ import { ThemeToggle } from "@/components/theme-toggle";
export function Layout({ children }: { children: React.ReactNode }) {
const [location] = useLocation();
const { user, isLoading, isAuthenticated, isAdmin, isLocalMode, tier, hasFeature, login, logout } = useAuth();
+ const { data: version } = useGetVersion({
+ query: { queryKey: getGetVersionQueryKey(), staleTime: Infinity, retry: false },
+ });
const links = [
{ href: "/", label: "Dashboard", icon: LayoutDashboard },
@@ -95,6 +99,24 @@ export function Layout({ children }: { children: React.ReactNode }) {
)}
+
diff --git a/artifacts/toolrate/src/pages/admin.tsx b/artifacts/toolrate/src/pages/admin.tsx
index 808f4d5..0cd3f10 100644
--- a/artifacts/toolrate/src/pages/admin.tsx
+++ b/artifacts/toolrate/src/pages/admin.tsx
@@ -6,8 +6,10 @@ import {
useUpdateUser,
useDeleteUser,
useListAuditLogs,
+ useGetVersion,
getListUsersQueryKey,
getListAuditLogsQueryKey,
+ getGetVersionQueryKey,
} from "@workspace/api-client-react";
import { useQueryClient } from "@tanstack/react-query";
import { useAuth } from "@/hooks/use-auth";
@@ -22,7 +24,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
import { Skeleton } from "@/components/ui/skeleton";
import { useToast } from "@/hooks/use-toast";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import { Users, ScrollText, ShieldAlert, Plus, Pencil, Trash2, Clock, AlertTriangle, Wrench } from "lucide-react";
+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";
@@ -49,6 +51,9 @@ export default function Admin() {
{ limit: 100 },
{ query: { queryKey: getListAuditLogsQueryKey({ limit: 100 }), enabled: isAdmin } },
);
+ const { data: versionInfo } = useGetVersion({
+ query: { queryKey: getGetVersionQueryKey(), staleTime: Infinity, retry: false, enabled: isAdmin },
+ });
const createUser = useCreateUser();
const updateUser = useUpdateUser();
@@ -154,6 +159,9 @@ export default function Admin() {
Audit Log
+
+ System
+
@@ -274,6 +282,53 @@ export default function Admin() {
+
+
+
+
+ System
+ Build information of the currently live deployment.
+
+
+
+
+ Version
+ {versionInfo?.version || "dev"}
+
+
+
+ Build date
+
+ {versionInfo?.buildDate ? format(new Date(versionInfo.buildDate), "dd.MM.yyyy HH:mm") : "—"}
+
+
+
+ Trash retention
+
+ {(versionInfo?.trashRetentionDays ?? 0) > 0
+ ? `${versionInfo?.trashRetentionDays} days`
+ : "Keep forever"}
+
+
+
+
+
+
diff --git a/lib/api-client-react/src/generated/api.schemas.ts b/lib/api-client-react/src/generated/api.schemas.ts
index 290c94b..134a95a 100644
--- a/lib/api-client-react/src/generated/api.schemas.ts
+++ b/lib/api-client-react/src/generated/api.schemas.ts
@@ -9,6 +9,15 @@ export interface HealthStatus {
status: string;
}
+export interface VersionInfo {
+ version: string;
+ /** @nullable */
+ commitSha?: string | null;
+ /** @nullable */
+ buildDate?: string | null;
+ trashRetentionDays?: number;
+}
+
export type AuthModeMode = typeof AuthModeMode[keyof typeof AuthModeMode];
diff --git a/lib/api-client-react/src/generated/api.ts b/lib/api-client-react/src/generated/api.ts
index 9632ed6..2f8b7c2 100644
--- a/lib/api-client-react/src/generated/api.ts
+++ b/lib/api-client-react/src/generated/api.ts
@@ -47,7 +47,8 @@ import type {
TrashToolsInput,
User,
UserCreateInput,
- UserRoleUpdate
+ UserRoleUpdate,
+ VersionInfo
} from './api.schemas';
import { customFetch } from '../custom-fetch';
@@ -140,6 +141,84 @@ export function useHealthCheck>,
+export const getGetVersionUrl = () => {
+
+
+
+
+ return `/api/version`
+}
+
+/**
+ * Returns the running build version, commit SHA and build date
+ * @summary Build version information
+ */
+export const getVersion = async ( options?: RequestInit): Promise => {
+
+ return customFetch(getGetVersionUrl(),
+ {
+ ...options,
+ method: 'GET'
+
+
+ }
+);}
+
+
+
+
+
+export const getGetVersionQueryKey = () => {
+ return [
+ `/api/version`
+ ] as const;
+ }
+
+
+export const getGetVersionQueryOptions = >, TError = ErrorType>( options?: { query?:UseQueryOptions>, TError, TData>, request?: SecondParameter}
+) => {
+
+const {query: queryOptions, request: requestOptions} = options ?? {};
+
+ const queryKey = queryOptions?.queryKey ?? getGetVersionQueryKey();
+
+
+
+ const queryFn: QueryFunction>> = ({ signal }) => getVersion({ signal, ...requestOptions });
+
+
+
+
+
+ return { queryKey, queryFn, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: QueryKey }
+}
+
+export type GetVersionQueryResult = NonNullable>>
+export type GetVersionQueryError = ErrorType
+
+
+/**
+ * @summary Build version information
+ */
+
+export function useGetVersion>, TError = ErrorType>(
+ options?: { query?:UseQueryOptions>, TError, TData>, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey } {
+
+ const queryOptions = getGetVersionQueryOptions(options)
+
+ const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey };
+
+ return { ...query, queryKey: queryOptions.queryKey };
+}
+
+
+
+
+
+
+
export const getListToolsUrl = (params?: ListToolsParams,) => {
const normalizedParams = new URLSearchParams();
diff --git a/lib/api-spec/openapi.yaml b/lib/api-spec/openapi.yaml
index d8f4eb2..fad5a1b 100644
--- a/lib/api-spec/openapi.yaml
+++ b/lib/api-spec/openapi.yaml
@@ -37,6 +37,20 @@ paths:
schema:
$ref: "#/components/schemas/HealthStatus"
+ /version:
+ get:
+ operationId: getVersion
+ tags: [health]
+ summary: Build version information
+ description: Returns the running build version, commit SHA and build date
+ responses:
+ "200":
+ description: Version information
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/VersionInfo"
+
/tools:
get:
operationId: listTools
@@ -660,6 +674,19 @@ components:
required:
- status
+ VersionInfo:
+ type: object
+ required: [version]
+ properties:
+ version:
+ type: string
+ commitSha:
+ type: ["string", "null"]
+ buildDate:
+ type: ["string", "null"]
+ trashRetentionDays:
+ type: integer
+
AuthMode:
type: object
required: [mode]
diff --git a/lib/api-zod/src/generated/api.ts b/lib/api-zod/src/generated/api.ts
index d1a9c50..e8e5aba 100644
--- a/lib/api-zod/src/generated/api.ts
+++ b/lib/api-zod/src/generated/api.ts
@@ -17,6 +17,18 @@ export const HealthCheckResponse = zod.object({
})
+/**
+ * Returns the running build version, commit SHA and build date
+ * @summary Build version information
+ */
+export const GetVersionResponse = zod.object({
+ "version": zod.string(),
+ "commitSha": zod.string().nullish(),
+ "buildDate": zod.string().nullish(),
+ "trashRetentionDays": zod.number().optional()
+})
+
+
/**
* @summary List all tools
*/
diff --git a/lib/api-zod/src/generated/types/index.ts b/lib/api-zod/src/generated/types/index.ts
index ad49d0a..2b8b151 100644
--- a/lib/api-zod/src/generated/types/index.ts
+++ b/lib/api-zod/src/generated/types/index.ts
@@ -46,3 +46,4 @@ export * from './userRoleUpdate';
export * from './userRoleUpdateRole';
export * from './userRoleUpdateTier';
export * from './userTier';
+export * from './versionInfo';
diff --git a/lib/api-zod/src/generated/types/versionInfo.ts b/lib/api-zod/src/generated/types/versionInfo.ts
new file mode 100644
index 0000000..154bed0
--- /dev/null
+++ b/lib/api-zod/src/generated/types/versionInfo.ts
@@ -0,0 +1,16 @@
+/**
+ * Generated by orval v8.9.1 🍺
+ * Do not edit manually.
+ * Api
+ * ToolRate API — Tool listing and rating platform
+ * OpenAPI spec version: 0.1.0
+ */
+
+export interface VersionInfo {
+ version: string;
+ /** @nullable */
+ commitSha?: string | null;
+ /** @nullable */
+ buildDate?: string | null;
+ trashRetentionDays?: number;
+}