mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-27 22:44:59 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cb84c2954b |
@@ -77,7 +77,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
@@ -204,7 +204,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
@@ -407,7 +407,7 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Login to GitHub Container Registry (for cosign)
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
uses: docker/login-action@abd2ef45e78c5afb21d64d4ca52ee8550d9572c7 # v4.5.1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
|
||||
- uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
|
||||
with:
|
||||
days-before-stale: 14
|
||||
days-before-close: 14
|
||||
|
||||
@@ -465,6 +465,8 @@
|
||||
"apiKeysDelete": "Delete API Key",
|
||||
"apiKeysManage": "Manage API Keys",
|
||||
"apiKeysDescription": "API keys are used to authenticate with the integration API",
|
||||
"orgsManage": "Manage Organizations",
|
||||
"orgsDescription": "View and manage all organizations on this instance",
|
||||
"provisioningKeysTitle": "Provisioning Key",
|
||||
"provisioningKeysManage": "Manage Provisioning Keys",
|
||||
"provisioningKeysDescription": "Provisioning keys are used to authenticate automated site provisioning for your organization.",
|
||||
@@ -2089,6 +2091,7 @@
|
||||
"resourceBudgetSettings": "Budget",
|
||||
"resourceBudgetSettingsDescription": "Configure how this AI gateway restricts usage based on spending or token limits",
|
||||
"sidebarApiKeys": "API Keys",
|
||||
"sidebarOrgs": "Organizations",
|
||||
"sidebarProvisioning": "Provisioning",
|
||||
"sidebarSettings": "Settings",
|
||||
"sidebarAllUsers": "All Users",
|
||||
|
||||
+788
-2213
File diff suppressed because it is too large
Load Diff
Generated
+279
-322
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -94,12 +94,12 @@
|
||||
"input-otp": "1.4.2",
|
||||
"ioredis": "5.11.0",
|
||||
"jmespath": "0.16.0",
|
||||
"js-yaml": "4.3.1",
|
||||
"js-yaml": "4.3.0",
|
||||
"jsonwebtoken": "9.0.3",
|
||||
"lucide-react": "1.17.0",
|
||||
"maxmind": "5.0.6",
|
||||
"moment": "2.30.1",
|
||||
"next": "16.3.1",
|
||||
"next": "16.2.11",
|
||||
"next-intl": "4.13.0",
|
||||
"next-themes": "0.4.6",
|
||||
"nextjs-toploader": "3.9.17",
|
||||
@@ -139,7 +139,7 @@
|
||||
"devDependencies": {
|
||||
"@dotenvx/dotenvx": "1.69.1",
|
||||
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
||||
"@react-email/ui": "^6.9.2",
|
||||
"@react-email/ui": "^6.5.0",
|
||||
"@tailwindcss/postcss": "4.3.0",
|
||||
"@tanstack/react-query-devtools": "5.100.14",
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
@@ -170,7 +170,7 @@
|
||||
"esbuild-node-externals": "1.22.0",
|
||||
"eslint": "10.4.0",
|
||||
"eslint-config-next": "16.2.6",
|
||||
"postcss": "8.5.23",
|
||||
"postcss": "8.5.15",
|
||||
"prettier": "3.8.3",
|
||||
"react-email": "6.5.0",
|
||||
"tailwindcss": "4.3.0",
|
||||
@@ -182,6 +182,6 @@
|
||||
"overrides": {
|
||||
"esbuild": "0.28.0",
|
||||
"dompurify": "3.4.0",
|
||||
"postcss": "8.5.23"
|
||||
"postcss": "8.5.15"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import HttpCode from "@server/types/HttpCode";
|
||||
import { response } from "@server/lib/response";
|
||||
import { db } from "@server/db";
|
||||
import { passwordResetTokens, users } from "@server/db";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { alphabet, generateRandomString, sha256 } from "oslo/crypto";
|
||||
import { createDate } from "oslo";
|
||||
import logger from "@server/logger";
|
||||
@@ -49,12 +49,7 @@ export async function requestPasswordReset(
|
||||
const existingUser = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(
|
||||
and(
|
||||
eq(users.email, email),
|
||||
eq(users.type, UserType.Internal)
|
||||
)
|
||||
);
|
||||
.where(eq(users.email, email));
|
||||
|
||||
if (!existingUser || !existingUser.length) {
|
||||
await randomDelay(2000);
|
||||
|
||||
@@ -454,14 +454,14 @@ export default async function migration() {
|
||||
throw new Error(fromZodError(parsedConfig.error).toString());
|
||||
}
|
||||
|
||||
traefikConfig.experimental.plugins.badger.version = "v1.7.0";
|
||||
traefikConfig.experimental.plugins.badger.version = "v1.6.1";
|
||||
|
||||
const updatedTraefikYaml = yaml.dump(traefikConfig);
|
||||
|
||||
fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8");
|
||||
|
||||
console.log(
|
||||
"Updated the version of Badger in your Traefik configuration to v1.7.0"
|
||||
"Updated the version of Badger in your Traefik configuration to v1.6.1"
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(
|
||||
|
||||
@@ -459,14 +459,14 @@ export default async function migration() {
|
||||
throw new Error(fromZodError(parsedConfig.error).toString());
|
||||
}
|
||||
|
||||
traefikConfig.experimental.plugins.badger.version = "v1.7.0";
|
||||
traefikConfig.experimental.plugins.badger.version = "v1.6.1";
|
||||
|
||||
const updatedTraefikYaml = yaml.dump(traefikConfig);
|
||||
|
||||
fs.writeFileSync(traefikPath, updatedTraefikYaml, "utf8");
|
||||
|
||||
console.log(
|
||||
"Updated the version of Badger in your Traefik configuration to v1.7.0"
|
||||
"Updated the version of Badger in your Traefik configuration to v1.6.1"
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(
|
||||
|
||||
@@ -276,9 +276,7 @@ export default function AiSessionLogsPage() {
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<div className="whitespace-nowrap">
|
||||
{new Date(
|
||||
row.original.createdAt * 1000
|
||||
).toLocaleString()}
|
||||
{new Date(row.original.createdAt).toLocaleString()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -717,8 +715,8 @@ function generateSampleAiSessionLogs(): QueryAiSessionLogResponse["log"] {
|
||||
null
|
||||
];
|
||||
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const sevenDaysAgoMs = now - 7 * 24 * 60 * 60;
|
||||
const now = Date.now();
|
||||
const sevenDaysAgoMs = now - 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
return Array.from({ length: 10 }, (_, i) => {
|
||||
const provider =
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
export interface OrganizationsPageProps {}
|
||||
|
||||
export default async function OrganizationsPage(props: OrganizationsPageProps) {
|
||||
const t = await getTranslations();
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsSectionTitle
|
||||
title={t("orgsManage")}
|
||||
description={t("orgsDescription")}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Bot,
|
||||
Boxes,
|
||||
Building2,
|
||||
Building2Icon,
|
||||
Cable,
|
||||
ChartLine,
|
||||
Coins,
|
||||
@@ -377,6 +378,11 @@ export const adminNavSections = (env?: Env): SidebarNavSection[] => [
|
||||
href: "/admin/api-keys",
|
||||
icon: <KeyRound className="size-4 flex-none" />
|
||||
},
|
||||
{
|
||||
title: "sidebarOrgs",
|
||||
href: "/admin/organizations",
|
||||
icon: <Building2Icon className="size-4 flex-none" />
|
||||
},
|
||||
...(build === "oss" ||
|
||||
env?.app.identityProviderMode === "global" ||
|
||||
env?.app.identityProviderMode === undefined
|
||||
@@ -388,7 +394,7 @@ export const adminNavSections = (env?: Env): SidebarNavSection[] => [
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(build == "enterprise"
|
||||
...(build === "enterprise"
|
||||
? [
|
||||
{
|
||||
title: "sidebarLicense",
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import { assertEquals } from "@test/assert";
|
||||
import { detectLocale } from "./detectLocale";
|
||||
|
||||
function runTests() {
|
||||
assertEquals(
|
||||
detectLocale("zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6"),
|
||||
"zh-TW",
|
||||
"An exact regional match should take precedence over a language fallback"
|
||||
);
|
||||
assertEquals(
|
||||
detectLocale("ZH-tw"),
|
||||
"zh-TW",
|
||||
"Locale matching should be case-insensitive"
|
||||
);
|
||||
assertEquals(
|
||||
detectLocale(" zh-TW ; q=1 , zh-CN;q=0.8 "),
|
||||
"zh-TW",
|
||||
"Whitespace and quality parameters should not prevent an exact match"
|
||||
);
|
||||
assertEquals(
|
||||
detectLocale("zh-CN,zh-TW;q=0.9"),
|
||||
"zh-CN",
|
||||
"Simplified Chinese should still match exactly"
|
||||
);
|
||||
assertEquals(
|
||||
detectLocale("zh"),
|
||||
"zh-CN",
|
||||
"A generic Chinese preference should retain the existing fallback"
|
||||
);
|
||||
assertEquals(
|
||||
detectLocale("en-GB,en;q=0.9"),
|
||||
"en-US",
|
||||
"An unsupported region should fall back to a supported locale for the language"
|
||||
);
|
||||
assertEquals(
|
||||
detectLocale("ja-JP,zh-TW;q=0.9"),
|
||||
"zh-TW",
|
||||
"The next preference should be used when a language is unsupported"
|
||||
);
|
||||
assertEquals(
|
||||
detectLocale("zh-CN;q=0.5,zh-TW;q=0.9"),
|
||||
"zh-TW",
|
||||
"Preferences should be evaluated by quality"
|
||||
);
|
||||
assertEquals(
|
||||
detectLocale("zh-TW;q=0,fr-FR;q=0.8"),
|
||||
"fr-FR",
|
||||
"Locales with zero quality should be excluded"
|
||||
);
|
||||
assertEquals(
|
||||
detectLocale("*,de-DE;q=0.8"),
|
||||
"de-DE",
|
||||
"A wildcard should not obscure a supported preference"
|
||||
);
|
||||
assertEquals(
|
||||
detectLocale("ja-JP"),
|
||||
undefined,
|
||||
"An unsupported language should not match"
|
||||
);
|
||||
assertEquals(
|
||||
detectLocale(""),
|
||||
undefined,
|
||||
"An empty Accept-Language header should not match"
|
||||
);
|
||||
|
||||
console.log("All locale detection tests passed!");
|
||||
}
|
||||
|
||||
try {
|
||||
runTests();
|
||||
} catch (error) {
|
||||
console.error("Locale detection test failed:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { Locale, locales } from "./config";
|
||||
|
||||
export function detectLocale(acceptLanguage: string): Locale | undefined {
|
||||
const browserLocales = acceptLanguage
|
||||
.split(",")
|
||||
.map((entry, index) => {
|
||||
const [locale, ...parameters] = entry.trim().split(";");
|
||||
const qualityParameter = parameters.find((parameter) =>
|
||||
parameter.trim().toLowerCase().startsWith("q=")
|
||||
);
|
||||
const quality = qualityParameter
|
||||
? Number(qualityParameter.trim().slice(2))
|
||||
: 1;
|
||||
|
||||
return {
|
||||
locale: locale.trim().toLowerCase(),
|
||||
quality,
|
||||
index
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
({ locale, quality }) =>
|
||||
locale && locale !== "*" && quality > 0 && quality <= 1
|
||||
)
|
||||
.sort(
|
||||
(left, right) =>
|
||||
right.quality - left.quality || left.index - right.index
|
||||
);
|
||||
|
||||
for (const { locale: browserLocale } of browserLocales) {
|
||||
const exactMatch = locales.find(
|
||||
(locale) => locale.toLowerCase() === browserLocale
|
||||
);
|
||||
if (exactMatch) {
|
||||
return exactMatch;
|
||||
}
|
||||
|
||||
const browserLanguage = browserLocale.split("-")[0];
|
||||
const languageMatch = locales.find(
|
||||
(locale) => locale.split("-")[0].toLowerCase() === browserLanguage
|
||||
);
|
||||
if (languageMatch) {
|
||||
return languageMatch;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { Locale, defaultLocale, locales } from "@/i18n/config";
|
||||
import { detectLocale } from "@/i18n/detectLocale";
|
||||
import { internal } from "@app/lib/api";
|
||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||
|
||||
@@ -48,7 +47,12 @@ export async function getUserLocale(): Promise<Locale> {
|
||||
const acceptLang = headerList.get("accept-language");
|
||||
|
||||
if (acceptLang) {
|
||||
const matched = detectLocale(acceptLang);
|
||||
const browserLang = acceptLang.split(",")[0];
|
||||
const matched = locales.find((locale) =>
|
||||
browserLang
|
||||
.toLowerCase()
|
||||
.startsWith(locale.split("-")[0].toLowerCase())
|
||||
);
|
||||
if (matched) {
|
||||
return matched;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user