Compare commits
3 Commits
6ed78e6459
...
f543ad1cf4
Author | SHA1 | Date | |
---|---|---|---|
f543ad1cf4 | |||
58adb46323 | |||
de1bb7dfde |
45
packages/client/src/components/BuildInfoCard.tsx
Normal file
45
packages/client/src/components/BuildInfoCard.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { getBuildInfo } from "../util/env";
|
||||
import { Clock, GitBranch, GitCommit, Tag } from "lucide-react";
|
||||
|
||||
export default function BuildInfoCard() {
|
||||
const buildInfo = getBuildInfo();
|
||||
|
||||
return (
|
||||
<Card className="w-full">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl font-bold">Build Information</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4">
|
||||
<div className="flex items-center space-x-4">
|
||||
<GitBranch className="h-5 w-5 text-muted-foreground" />
|
||||
<div>
|
||||
<p className="text-sm font-medium leading-none">Git Branch</p>
|
||||
<p className="text-sm text-muted-foreground">{buildInfo.gitBranch}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<GitCommit className="h-5 w-5 text-muted-foreground" />
|
||||
<div>
|
||||
<p className="text-sm font-medium leading-none">Git Hash</p>
|
||||
<p className="text-sm text-muted-foreground">{buildInfo.gitHash}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<Clock className="h-5 w-5 text-muted-foreground" />
|
||||
<div>
|
||||
<p className="text-sm font-medium leading-none">Build Time</p>
|
||||
<p className="text-sm text-muted-foreground">{buildInfo.buildTime}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<Tag className="h-5 w-5 text-muted-foreground" />
|
||||
<div>
|
||||
<p className="text-sm font-medium leading-none">Build Version</p>
|
||||
<p className="text-sm text-muted-foreground">{buildInfo.buildVersion}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
@ -22,7 +22,7 @@ export function DescTagItem({
|
||||
{items.length === 0 ? "N/A" : items.map(
|
||||
(x, i) =>
|
||||
<>
|
||||
<StyledLink key={x} to={`/search?allow_tag=${name}:${x}`}>{x}</StyledLink>
|
||||
<StyledLink key={x} to={`/search?allow_tag=${name.toLowerCase()}:${x}`}>{x}</StyledLink>
|
||||
{i + 1 < items.length && <span className="">, </span>}
|
||||
</>
|
||||
)}
|
||||
|
@ -1,7 +1,17 @@
|
||||
import useSWR from "swr";
|
||||
import useSWR, { useSWRConfig } from "swr";
|
||||
import type { Document } from "dbtype";
|
||||
import { fetcher } from "./fetcher";
|
||||
|
||||
export function useGalleryDoc(id: string) {
|
||||
return useSWR<Document>(`/api/doc/${id}`, fetcher);
|
||||
}
|
||||
|
||||
export function useRehashDoc() {
|
||||
const { mutate } = useSWRConfig();
|
||||
return async (id: string) => {
|
||||
await fetch(`/api/doc/${id}/_rehash`, {
|
||||
method: "POST",
|
||||
});
|
||||
mutate(`/api/doc/${id}`);
|
||||
};
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { useGalleryDoc } from "../hook/useGalleryDoc.ts";
|
||||
import { useGalleryDoc, useRehashDoc } from "../hook/useGalleryDoc.ts";
|
||||
import TagBadge from "@/components/gallery/TagBadge";
|
||||
import StyledLink from "@/components/gallery/StyledLink";
|
||||
import { Link } from "wouter";
|
||||
import { classifyTags } from "../lib/classifyTags.tsx";
|
||||
import { DescTagItem, DescItem } from "../components/gallery/DescItem.tsx";
|
||||
import { Button } from "@/components/ui/button.tsx";
|
||||
|
||||
export interface ContentInfoPageProps {
|
||||
params: {
|
||||
@ -14,6 +15,7 @@ export interface ContentInfoPageProps {
|
||||
|
||||
export function ContentInfoPage({ params }: ContentInfoPageProps) {
|
||||
const { data, error, isLoading } = useGalleryDoc(params.id);
|
||||
const rehashDoc = useRehashDoc();
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="p-4">Loading...</div>
|
||||
@ -43,7 +45,13 @@ export function ContentInfoPage({ params }: ContentInfoPageProps) {
|
||||
alt={data.title} />
|
||||
</div>
|
||||
</Link>
|
||||
<Card className="flex-1">
|
||||
<Card className="flex-1 relative">
|
||||
<div className="absolute top-0 right-0 p-2">
|
||||
<Button variant="ghost" onClick={async () => {
|
||||
// Rehash
|
||||
await rehashDoc(params.id);
|
||||
}}>Rehash</Button>
|
||||
</div>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<StyledLink to={contentLocation}>
|
||||
|
@ -2,6 +2,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { type TernaryDarkMode, useTernaryDarkMode, useMediaQuery } from "usehooks-ts";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import BuildInfoCard from "@/components/BuildInfoCard";
|
||||
|
||||
function LightModeView() {
|
||||
return <div className="items-center rounded-md border-2 border-muted p-1 hover:border-accent">
|
||||
@ -46,7 +47,7 @@ export function SettingPage() {
|
||||
const isSystemDarkMode = useMediaQuery("(prefers-color-scheme: dark)");
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="p-4 space-y-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-2xl">Settings</CardTitle>
|
||||
@ -85,6 +86,7 @@ export function SettingPage() {
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<BuildInfoCard />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
13
packages/client/src/util/env.ts
Normal file
13
packages/client/src/util/env.ts
Normal file
@ -0,0 +1,13 @@
|
||||
declare const __GIT_BRANCH__: string;
|
||||
declare const __GIT_HASH__: string;
|
||||
declare const __BUILD_TIME__: string;
|
||||
declare const __BUILD_VERSION__: string;
|
||||
|
||||
export function getBuildInfo() {
|
||||
return {
|
||||
gitBranch: __GIT_BRANCH__,
|
||||
gitHash: __GIT_HASH__,
|
||||
buildTime: __BUILD_TIME__,
|
||||
buildVersion: __BUILD_VERSION__,
|
||||
};
|
||||
}
|
@ -2,11 +2,41 @@ import { defineConfig, loadEnv } from 'vite'
|
||||
import path from 'node:path'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
|
||||
import { execSync } from "child_process";
|
||||
|
||||
function getCurrentGitHash(): string {
|
||||
const gitHash = execSync("git rev-parse HEAD")
|
||||
.toString()
|
||||
.trim();
|
||||
return gitHash;
|
||||
}
|
||||
|
||||
function getBuildTime(): string {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function getBuildVersion(): string {
|
||||
return process.env.BUILD_VERSION ?? getCurrentGitHash();
|
||||
}
|
||||
|
||||
function getCurrentGitBranch(): string {
|
||||
const gitBranch = execSync("git rev-parse --abbrev-ref HEAD")
|
||||
.toString()
|
||||
.trim();
|
||||
return gitBranch;
|
||||
}
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), "");
|
||||
return {
|
||||
plugins: [react()],
|
||||
define: {
|
||||
__BUILD_TIME__: `"${getBuildTime()}"`,
|
||||
__BUILD_VERSION__: `"${getBuildVersion()}"`,
|
||||
__GIT_BRANCH__: `"${getCurrentGitBranch()}"`,
|
||||
__GIT_HASH__: `"${getCurrentGitHash()}"`,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
|
@ -15,6 +15,8 @@ import { AllContentRouter } from "./all.ts";
|
||||
import type { ContentLocation } from "./context.ts";
|
||||
import { sendError } from "./error_handler.ts";
|
||||
import { ParseQueryArgString, ParseQueryArray, ParseQueryBoolean, ParseQueryNumber } from "./util.ts";
|
||||
import { oshash } from "src/util/oshash.ts";
|
||||
|
||||
|
||||
const ContentIDHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => {
|
||||
const num = Number.parseInt(ctx.params.num);
|
||||
@ -154,6 +156,34 @@ const ContentHandler = (controller: DocumentAccessor) => async (ctx: Context, ne
|
||||
await next();
|
||||
};
|
||||
|
||||
function RehashContentHandler(controller: DocumentAccessor) {
|
||||
return async (ctx: Context, next: Next) => {
|
||||
const num = Number.parseInt(ctx.params.num);
|
||||
const c = await controller.findById(num);
|
||||
if (c === undefined || c.deleted_at !== null) {
|
||||
return sendError(404);
|
||||
}
|
||||
const filepath = join(c.basepath, c.filename);
|
||||
let new_hash: string;
|
||||
try {
|
||||
new_hash = (await oshash(filepath)).toString();
|
||||
}
|
||||
catch (e) {
|
||||
// if file is not found, return 404
|
||||
if ( (e as NodeJS.ErrnoException).code === "ENOENT") {
|
||||
return sendError(404, "file not found");
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
const r = await controller.update({
|
||||
id: num,
|
||||
content_hash: new_hash,
|
||||
});
|
||||
ctx.body = JSON.stringify(r);
|
||||
ctx.type = "json";
|
||||
};
|
||||
}
|
||||
|
||||
export const getContentRouter = (controller: DocumentAccessor) => {
|
||||
const ret = new Router();
|
||||
ret.get("/search", PerCheck(Per.QueryContent), ContentQueryHandler(controller));
|
||||
@ -167,6 +197,7 @@ export const getContentRouter = (controller: DocumentAccessor) => {
|
||||
ret.del("/:num(\\d+)", AdminOnly, DeleteContentHandler(controller));
|
||||
ret.all("/:num(\\d+)/(.*)", PerCheck(Per.QueryContent), ContentHandler(controller));
|
||||
ret.use("/:num(\\d+)", PerCheck(Per.QueryContent), new AllContentRouter().routes());
|
||||
ret.post("/:num(\\d+)/_rehash", AdminOnly, RehashContentHandler(controller));
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user