Compare commits
	
		
			3 commits
		
	
	
		
			6ed78e6459
			...
			f543ad1cf4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f543ad1cf4 | |||
| 58adb46323 | |||
| de1bb7dfde | 
					 8 changed files with 144 additions and 5 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		
		Reference in a new issue