Skip to content

Commit d57f9dc

Browse files
authored
Merge pull request #41 from Ontos-AI/staging
Release 0525, ui fix
2 parents 4b7e611 + d16a560 commit d57f9dc

9 files changed

Lines changed: 149 additions & 81 deletions

File tree

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ NEXT_PUBLIC_API_URL=http://localhost:5005/api
55
NEXT_PUBLIC_AUTH_BASE_URL=/api/auth
66
BETTER_AUTH_URL=http://localhost:3000
77
BETTER_AUTH_SECRET=replace-with-a-random-secret-at-least-32-characters
8-
DATABASE_URL=postgres://postgres:postgres@localhost:5432/knowhere_dashboard
8+
DATABASE_URL=postgres://root:root123@localhost:5432/Knowhere
99
# Set this to true only for local/self-hosted PostgreSQL without SSL.
1010
# UNSAFE_DB_SSL_ENABLED=true
1111
BILLING_ENABLED=false

LOCALIZATION_GUIDE.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,6 @@
398398
| Cost | 花费 | 表头 |
399399
| Status | 状态 | 表头 |
400400
| Type | 类型 | 表头 |
401-
| OCR | OCR | 表头 |
402-
| Yes || 是/否 |
403-
| No || 是/否 |
404401
| No results. | 暂无数据 | 空状态 |
405402
| Previous | 上一页 | 分页 |
406403
| Next | 下一页 | 分页 |

README.md

Lines changed: 13 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,42 +13,36 @@ Knowhere API Dashboard is the Next.js web application for managing Knowhere API
1313
- PostgreSQL for the dashboard auth and account database
1414
- A reachable Knowhere API backend
1515

16-
## Local Setup
16+
## Local Development
1717

18-
Install dependencies:
18+
Install dependencies and create your local environment file:
1919

2020
```bash
2121
pnpm install
22+
cp .env.example .env.local
2223
```
2324

24-
Create local environment configuration:
25+
Fill in the required values in `.env.local`. For most local work, the important values are:
2526

26-
```bash
27-
cp .env.example .env.local
28-
```
27+
- `DATABASE_URL`: PostgreSQL database used by the dashboard.
28+
- `NEXT_PUBLIC_API_URL`: Knowhere API backend URL.
29+
- `NEXT_PUBLIC_APP_URL` and `BETTER_AUTH_URL`: usually `http://localhost:3000`.
30+
- `BETTER_AUTH_SECRET`: any random secret with at least 32 characters.
2931

30-
Fill in the required values in `.env.local`, then start the development server:
32+
Start the development server:
3133

3234
```bash
3335
pnpm dev
3436
```
3537

3638
The app runs on `http://localhost:3000` by default.
3739

38-
## Self-hosted Dashboard Flow
39-
40-
For the combined open-source stack, start the dashboard migration/bootstrap step before the API service runs its Alembic migrations. The dashboard owns the Better Auth user/auth schema and provides the normal first-user registration flow.
41-
42-
The default self-hosted flow is:
40+
## Self-hosted
4341

44-
1. Start PostgreSQL, Redis, object storage, and other shared dependencies.
45-
2. Start the dashboard migration/bootstrap step so Better Auth tables exist.
46-
3. Start the API with standalone mode disabled, then run API migrations.
47-
4. Register or sign in through the dashboard with email and password, or use Resend-backed magic-link login when email delivery is configured.
48-
5. Create and manage API keys from the dashboard.
49-
6. Process jobs through the API/worker with dashboard billing disabled.
42+
For self-hosted deployment, use the combined stack in the dedicated repository:
43+
https://github.com/Ontos-AI/knowhere-self-hosted
5044

51-
Use `BILLING_ENABLED=false` for the open-source self-hosted dashboard unless the matching paid billing endpoints are deployed and configured.
45+
This dashboard repository is useful when developing the web app directly. The self-hosted repository owns the end-to-end local stack and deployment instructions.
5246

5347
## Environment Variables
5448

@@ -122,26 +116,3 @@ The image runs the standard Next.js Node server with `pnpm start`. Runtime confi
122116
The public workflow runs lint, type-check, tests, and build on pull requests and pushes to `main` and `staging`.
123117

124118
This repository does not publish standalone public dashboard images. Public self-hosted image publishing is handled by the combined self-hosted release workflow.
125-
126-
## Deployment
127-
128-
Merging a pull request into `staging` or `main` triggers `.github/workflows/deploy.yml` through the branch push created by the merge. The workflow builds the dashboard image, pushes it to the configured AWS image registry, and updates the configured Kubernetes deployment with `kubectl set image`.
129-
130-
DevOps must configure these GitHub repository secrets:
131-
132-
| Name | Purpose |
133-
| --- | --- |
134-
| `AWS_ACCESS_KEY_ID` | AWS principal allowed to push images and update the cluster. |
135-
| `AWS_SECRET_ACCESS_KEY` | Secret key for the AWS principal. |
136-
| `AWS_EKS_PROD_CLUSTER_NAME` | Kubernetes cluster name used by `aws eks update-kubeconfig`. |
137-
| `AWS_EKS_PROD_REGION` | AWS region for the image registry and cluster. |
138-
| `DASHBOARD_IMAGE_REGISTRY` | Registry host, for example an AWS account registry host. |
139-
| `DASHBOARD_IMAGE_REPOSITORY` | Dashboard image repository path inside the registry. |
140-
| `DASHBOARD_KUBE_CONTAINER` | Container name inside the dashboard Deployment. |
141-
| `DASHBOARD_KUBE_DEPLOYMENT` | Dashboard Kubernetes Deployment name. |
142-
| `DASHBOARD_KUBE_NAMESPACE_STAGING` | Namespace updated when `staging` is deployed. |
143-
| `DASHBOARD_KUBE_NAMESPACE_PROD` | Namespace updated when `main` is deployed. |
144-
145-
The AWS principal must be able to authenticate to the image registry, push the dashboard image, call `eks:DescribeCluster`, and update the target deployment. The cluster must be able to pull the pushed image.
146-
147-
Runtime environment variables are still injected by the deployment platform, not by the Docker build. Because the container runs `pnpm db:generate` and `pnpm db:migrate` before `pnpm start`, the deployed pod must have `DATABASE_URL` and the required auth/app URL environment variables at startup. The container filesystem must allow writes to the app directory unless the migration generation step is moved out of container startup.

app/(dashboard)/usage/_components/usage-table.tsx

Lines changed: 83 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export type UsageRecord = {
3030
fileType: string;
3131
model: string;
3232
pages: number;
33-
ocr: boolean;
3433
status: string;
3534
statusKind: UsageStatusKind;
3635
duration: string;
@@ -53,6 +52,23 @@ type UsageTableProps = {
5352
};
5453

5554
type PaginationItem = number | "ellipsis";
55+
type FileTypeTheme = {
56+
readonly background: string;
57+
readonly border: string;
58+
readonly text: string;
59+
readonly darkBackground: string;
60+
readonly darkBorder: string;
61+
readonly darkText: string;
62+
};
63+
64+
type FileTypeBadgeStyle = React.CSSProperties & {
65+
readonly "--file-type-background": string;
66+
readonly "--file-type-border": string;
67+
readonly "--file-type-text": string;
68+
readonly "--file-type-dark-background": string;
69+
readonly "--file-type-dark-border": string;
70+
readonly "--file-type-dark-text": string;
71+
};
5672

5773
const tableHeaders = [
5874
{ key: "date", labelKey: "date", width: "210px" },
@@ -61,16 +77,19 @@ const tableHeaders = [
6177
{ key: "type", labelKey: "type", width: "80px" },
6278
{ key: "model", labelKey: "model", width: "144px" },
6379
{ key: "pages", labelKey: "pages", width: "60px" },
64-
{ key: "ocr", labelKey: "ocr", width: "56px" },
6580
{ key: "status", labelKey: "status", width: "80px" },
6681
{ key: "duration", labelKey: "duration", width: "100px" },
6782
{ key: "cost", labelKey: "cost", width: "120px" },
6883
] as const;
6984

70-
const tableGridTemplate = tableHeaders.map((header) => header.width).join(" ");
85+
const rowActionColumnWidth = "48px";
86+
const tableGridTemplate = [
87+
...tableHeaders.map((header) => header.width),
88+
rowActionColumnWidth,
89+
].join(" ");
7190
const tableMinWidth = tableHeaders.reduce(
7291
(total, header) => total + Number.parseInt(header.width, 10),
73-
0
92+
Number.parseInt(rowActionColumnWidth, 10)
7493
);
7594
const tableScrollThumbWidth: number = 151;
7695

@@ -79,58 +98,100 @@ const fileTypeColorMap = {
7998
background: "#fef2f2",
8099
border: "#ffe2e2",
81100
text: "#c10007",
101+
darkBackground: "#450a0a",
102+
darkBorder: "#7f1d1d",
103+
darkText: "#fca5a5",
82104
},
83105
docx: {
84106
background: "#eff6ff",
85107
border: "#dbeafe",
86108
text: "#1447e6",
109+
darkBackground: "#172554",
110+
darkBorder: "#1d4ed8",
111+
darkText: "#93c5fd",
87112
},
88113
jpg: {
89114
background: "#fdf4ff",
90115
border: "#fae8ff",
91116
text: "#a800b7",
117+
darkBackground: "#4a044e",
118+
darkBorder: "#86198f",
119+
darkText: "#f0abfc",
92120
},
93121
jpeg: {
94122
background: "#fdf4ff",
95123
border: "#fae8ff",
96124
text: "#a800b7",
125+
darkBackground: "#4a044e",
126+
darkBorder: "#86198f",
127+
darkText: "#f0abfc",
97128
},
98129
pptx: {
99130
background: "#fff7ed",
100131
border: "#ffedd4",
101132
text: "#ca3500",
133+
darkBackground: "#431407",
134+
darkBorder: "#9a3412",
135+
darkText: "#fdba74",
102136
},
103137
xlsx: {
104138
background: "#ecfdf5",
105139
border: "#d0fae5",
106140
text: "#007a55",
141+
darkBackground: "#052e16",
142+
darkBorder: "#047857",
143+
darkText: "#86efac",
107144
},
108145
csv: {
109146
background: "#ecfeff",
110147
border: "#cefafe",
111148
text: "#007595",
149+
darkBackground: "#083344",
150+
darkBorder: "#0e7490",
151+
darkText: "#67e8f9",
112152
},
113153
png: {
114154
background: "#f5f3ff",
115155
border: "#ede9fe",
116156
text: "#7008e7",
157+
darkBackground: "#2e1065",
158+
darkBorder: "#6d28d9",
159+
darkText: "#c4b5fd",
117160
},
118161
md: {
119162
background: "#f7fee7",
120163
border: "#ecfcca",
121164
text: "#497d00",
165+
darkBackground: "#1a2e05",
166+
darkBorder: "#4d7c0f",
167+
darkText: "#bef264",
122168
},
123169
json: {
124170
background: "#fefce8",
125171
border: "#fef9c2",
126172
text: "#a65f00",
173+
darkBackground: "#422006",
174+
darkBorder: "#a16207",
175+
darkText: "#fde68a",
127176
},
128177
txt: {
129178
background: "#eef2ff",
130179
border: "#e0e7ff",
131180
text: "#432dd7",
181+
darkBackground: "#1e1b4b",
182+
darkBorder: "#4338ca",
183+
darkText: "#a5b4fc",
132184
},
133-
} as const;
185+
} satisfies Readonly<Record<string, FileTypeTheme>>;
186+
187+
const buildFileTypeBadgeStyle = (theme: FileTypeTheme): FileTypeBadgeStyle => ({
188+
"--file-type-background": theme.background,
189+
"--file-type-border": theme.border,
190+
"--file-type-text": theme.text,
191+
"--file-type-dark-background": theme.darkBackground,
192+
"--file-type-dark-border": theme.darkBorder,
193+
"--file-type-dark-text": theme.darkText,
194+
});
134195

135196
const buildPaginationItems = (page: number, pageCount: number): PaginationItem[] => {
136197
if (pageCount <= 6) {
@@ -310,6 +371,10 @@ export function UsageTable({
310371
{header.key === "date" ? <ArrowUp className="h-3 w-3 text-[#9f9fa9]" /> : null}
311372
</div>
312373
))}
374+
<div
375+
aria-hidden="true"
376+
className="sticky right-0 z-10 h-full border-l border-[#e4e4e7] bg-[#f4f4f5] dark:border-[#3f3f46] dark:bg-[#27272a]"
377+
/>
313378
</div>
314379

315380
{data.length > 0 ? (
@@ -350,12 +415,8 @@ export function UsageTable({
350415
</TableCell>
351416
<TableCell>
352417
<span
353-
className="inline-flex items-center border py-1 pl-[6px] pr-2 font-mono-display text-[18px] leading-6"
354-
style={{
355-
backgroundColor: fileTypeTheme.background,
356-
borderColor: fileTypeTheme.border,
357-
color: fileTypeTheme.text,
358-
}}
418+
className="inline-flex items-center border border-[var(--file-type-border)] bg-[var(--file-type-background)] px-[6px] font-mono-display text-[12px] leading-4 text-[var(--file-type-text)] dark:border-[var(--file-type-dark-border)] dark:bg-[var(--file-type-dark-background)] dark:text-[var(--file-type-dark-text)]"
419+
style={buildFileTypeBadgeStyle(fileTypeTheme)}
359420
>
360421
{formatFileTypeLabel(row.fileType)}
361422
</span>
@@ -366,9 +427,6 @@ export function UsageTable({
366427
<TableCell className="font-mono-display leading-4 text-[#3f3f46] dark:text-[#e4e4e7]">
367428
{row.pages}
368429
</TableCell>
369-
<TableCell className={row.ocr ? "text-[#00a63e]" : "text-[#e7000b]"}>
370-
{row.ocr ? t("yes") : t("no")}
371-
</TableCell>
372430
<TableCell className="gap-1.5">
373431
<StatusIcon statusKind={row.statusKind} />
374432
<span
@@ -392,19 +450,21 @@ export function UsageTable({
392450

393451
<button
394452
type="button"
395-
className="sticky right-0 z-10 col-[1/-1] row-start-1 flex h-10 w-12 items-center justify-center self-center justify-self-end border-l border-[#f4f4f5] bg-[#fafafa] text-[#ff8904] transition-colors hover:bg-[#fff7ed] disabled:cursor-not-allowed disabled:text-[#d4d4d8] dark:border-[#3f3f46] dark:bg-[#27272a] dark:hover:bg-[#3f3f46]"
453+
className="sticky right-0 z-10 flex h-10 w-12 items-center justify-center border-l border-[#f4f4f5] bg-[#fafafa] text-[#ff8904] transition-colors hover:bg-[#fff7ed] disabled:cursor-not-allowed disabled:text-[#d4d4d8] dark:border-[#3f3f46] dark:bg-[#27272a] dark:hover:bg-[#3f3f46] sm:h-[22px] lg:h-8"
396454
onClick={() => onDownloadResult?.(row.jobId, row.resultUrl)}
397455
disabled={!row.resultUrl}
398456
aria-label={row.resultUrl ? t("download") : row.status}
399457
>
400-
<Image
401-
src="/icons/usage/row-action.svg"
402-
alt=""
403-
aria-hidden
404-
width={14.33}
405-
height={14.33}
406-
className="h-[14.33px] w-[14.33px]"
407-
/>
458+
<span className="flex size-6 items-center justify-center overflow-hidden rounded-full">
459+
<Image
460+
src="/icons/usage/row-action.svg"
461+
alt=""
462+
aria-hidden
463+
width={16}
464+
height={16}
465+
className="h-4 w-4"
466+
/>
467+
</span>
408468
</button>
409469
</div>
410470
);

app/(dashboard)/usage/_hooks/use-jobs.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,6 @@ function mapJobToUsageRecord(job: JobResponse): UsageRecord {
134134
fileType: fileType,
135135
model: job.model || (job.result_metadata?.model as string | undefined) || "-",
136136
pages: (job.result_metadata?.pages as number | undefined) || 0,
137-
ocr: job.ocr_enabled ?? (job.result_metadata?.ocr as boolean | undefined) ?? false,
138137
status: statusInfo.label,
139138
statusKind: statusInfo.kind,
140139
duration: job.duration_seconds

i18n/locales/en.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,6 @@
106106
"cost": "Cost",
107107
"status": "Status",
108108
"type": "Type",
109-
"ocr": "OCR",
110-
"yes": "Yes",
111-
"no": "No",
112109
"noResults": "No results.",
113110
"previous": "Previous",
114111
"next": "Next",

i18n/locales/zh.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,6 @@
106106
"cost": "花费",
107107
"status": "状态",
108108
"type": "类型",
109-
"ocr": "OCR",
110-
"yes": "",
111-
"no": "",
112109
"noResults": "暂无数据",
113110
"previous": "上一页",
114111
"next": "下一页",

0 commit comments

Comments
 (0)