mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-22 12:56:37 +00:00
Compare commits
4 Commits
1.15.4-s.4
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58ac499f30 | ||
|
|
f07f0092ad | ||
|
|
218a4893b6 | ||
|
|
266bf261aa |
BIN
public/fonts/ember/Amazon-Ember-Medium.ttf
Normal file
BIN
public/fonts/ember/Amazon-Ember-Medium.ttf
Normal file
Binary file not shown.
BIN
public/fonts/ember/Amazon-Ember-MediumItalic.ttf
Normal file
BIN
public/fonts/ember/Amazon-Ember-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/ember/AmazonEmber_Bd.ttf
Normal file
BIN
public/fonts/ember/AmazonEmber_Bd.ttf
Normal file
Binary file not shown.
BIN
public/fonts/ember/AmazonEmber_BdIt.ttf
Normal file
BIN
public/fonts/ember/AmazonEmber_BdIt.ttf
Normal file
Binary file not shown.
BIN
public/fonts/ember/AmazonEmber_He.ttf
Normal file
BIN
public/fonts/ember/AmazonEmber_He.ttf
Normal file
Binary file not shown.
BIN
public/fonts/ember/AmazonEmber_HeIt.ttf
Normal file
BIN
public/fonts/ember/AmazonEmber_HeIt.ttf
Normal file
Binary file not shown.
BIN
public/fonts/ember/AmazonEmber_Lt.ttf
Normal file
BIN
public/fonts/ember/AmazonEmber_Lt.ttf
Normal file
Binary file not shown.
BIN
public/fonts/ember/AmazonEmber_LtIt.ttf
Normal file
BIN
public/fonts/ember/AmazonEmber_LtIt.ttf
Normal file
Binary file not shown.
BIN
public/fonts/ember/AmazonEmber_Rg.ttf
Normal file
BIN
public/fonts/ember/AmazonEmber_Rg.ttf
Normal file
Binary file not shown.
BIN
public/fonts/ember/AmazonEmber_RgIt.ttf
Normal file
BIN
public/fonts/ember/AmazonEmber_RgIt.ttf
Normal file
Binary file not shown.
BIN
public/fonts/ember/AmazonEmber_Th.ttf
Normal file
BIN
public/fonts/ember/AmazonEmber_Th.ttf
Normal file
Binary file not shown.
BIN
public/fonts/ember/AmazonEmber_ThIt.ttf
Normal file
BIN
public/fonts/ember/AmazonEmber_ThIt.ttf
Normal file
Binary file not shown.
BIN
public/fonts/google-sans/GoogleSans-Bold.ttf
Normal file
BIN
public/fonts/google-sans/GoogleSans-Bold.ttf
Normal file
Binary file not shown.
BIN
public/fonts/google-sans/GoogleSans-BoldItalic.ttf
Normal file
BIN
public/fonts/google-sans/GoogleSans-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/google-sans/GoogleSans-Italic.ttf
Normal file
BIN
public/fonts/google-sans/GoogleSans-Italic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/google-sans/GoogleSans-Medium.ttf
Normal file
BIN
public/fonts/google-sans/GoogleSans-Medium.ttf
Normal file
Binary file not shown.
BIN
public/fonts/google-sans/GoogleSans-MediumItalic.ttf
Normal file
BIN
public/fonts/google-sans/GoogleSans-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
public/fonts/google-sans/GoogleSans-Regular.ttf
Normal file
BIN
public/fonts/google-sans/GoogleSans-Regular.ttf
Normal file
Binary file not shown.
BIN
public/fonts/google-sans/GoogleSans-SemiBold.ttf
Normal file
BIN
public/fonts/google-sans/GoogleSans-SemiBold.ttf
Normal file
Binary file not shown.
BIN
public/fonts/google-sans/GoogleSans-SemiBoldItalic.ttf
Normal file
BIN
public/fonts/google-sans/GoogleSans-SemiBoldItalic.ttf
Normal file
Binary file not shown.
@@ -3,7 +3,14 @@ import {
|
||||
encodeHexLowerCase
|
||||
} from "@oslojs/encoding";
|
||||
import { sha256 } from "@oslojs/crypto/sha2";
|
||||
import { resourceSessions, Session, sessions, User, users } from "@server/db";
|
||||
import {
|
||||
resourceSessions,
|
||||
safeRead,
|
||||
Session,
|
||||
sessions,
|
||||
User,
|
||||
users
|
||||
} from "@server/db";
|
||||
import { db } from "@server/db";
|
||||
import { eq, inArray } from "drizzle-orm";
|
||||
import config from "@server/lib/config";
|
||||
@@ -54,11 +61,15 @@ export async function validateSessionToken(
|
||||
const sessionId = encodeHexLowerCase(
|
||||
sha256(new TextEncoder().encode(token))
|
||||
);
|
||||
const result = await db
|
||||
.select({ user: users, session: sessions })
|
||||
.from(sessions)
|
||||
.innerJoin(users, eq(sessions.userId, users.userId))
|
||||
.where(eq(sessions.sessionId, sessionId));
|
||||
|
||||
const result = await safeRead((db) =>
|
||||
db
|
||||
.select({ user: users, session: sessions })
|
||||
.from(sessions)
|
||||
.innerJoin(users, eq(sessions.userId, users.userId))
|
||||
.where(eq(sessions.sessionId, sessionId))
|
||||
);
|
||||
|
||||
if (result.length < 1) {
|
||||
return { session: null, user: null };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { encodeHexLowerCase } from "@oslojs/encoding";
|
||||
import { sha256 } from "@oslojs/crypto/sha2";
|
||||
import { resourceSessions, ResourceSession } from "@server/db";
|
||||
import { db } from "@server/db";
|
||||
import { db, safeRead } from "@server/db";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import config from "@server/lib/config";
|
||||
|
||||
@@ -66,15 +66,17 @@ export async function validateResourceSessionToken(
|
||||
const sessionId = encodeHexLowerCase(
|
||||
sha256(new TextEncoder().encode(token))
|
||||
);
|
||||
const result = await db
|
||||
.select()
|
||||
.from(resourceSessions)
|
||||
.where(
|
||||
and(
|
||||
eq(resourceSessions.sessionId, sessionId),
|
||||
eq(resourceSessions.resourceId, resourceId)
|
||||
const result = await safeRead((db) =>
|
||||
db
|
||||
.select()
|
||||
.from(resourceSessions)
|
||||
.where(
|
||||
and(
|
||||
eq(resourceSessions.sessionId, sessionId),
|
||||
eq(resourceSessions.resourceId, resourceId)
|
||||
)
|
||||
)
|
||||
);
|
||||
);
|
||||
|
||||
if (result.length < 1) {
|
||||
return { resourceSession: null };
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./driver";
|
||||
export * from "./safeRead";
|
||||
export * from "./schema/schema";
|
||||
export * from "./schema/privateSchema";
|
||||
export * from "./migrate";
|
||||
|
||||
24
server/db/pg/safeRead.ts
Normal file
24
server/db/pg/safeRead.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { db, primaryDb } from "./driver";
|
||||
|
||||
/**
|
||||
* Runs a read query with replica fallback for Postgres.
|
||||
* Executes the query against the replica first (when replicas exist).
|
||||
* If the query throws or returns no data (null, undefined, or empty array),
|
||||
* runs the same query against the primary.
|
||||
*/
|
||||
export async function safeRead<T>(
|
||||
query: (d: typeof db | typeof primaryDb) => Promise<T>
|
||||
): Promise<T> {
|
||||
try {
|
||||
const result = await query(db);
|
||||
if (result === undefined || result === null) {
|
||||
return query(primaryDb);
|
||||
}
|
||||
if (Array.isArray(result) && result.length === 0) {
|
||||
return query(primaryDb);
|
||||
}
|
||||
return result;
|
||||
} catch {
|
||||
return query(primaryDb);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./driver";
|
||||
export * from "./safeRead";
|
||||
export * from "./schema/schema";
|
||||
export * from "./schema/privateSchema";
|
||||
export * from "./migrate";
|
||||
|
||||
11
server/db/sqlite/safeRead.ts
Normal file
11
server/db/sqlite/safeRead.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { db } from "./driver";
|
||||
|
||||
/**
|
||||
* Runs a read query. For SQLite there is no replica/primary distinction,
|
||||
* so the query is executed once against the database.
|
||||
*/
|
||||
export async function safeRead<T>(
|
||||
query: (d: typeof db) => Promise<T>
|
||||
): Promise<T> {
|
||||
return query(db);
|
||||
}
|
||||
@@ -46,6 +46,8 @@ export class UsageService {
|
||||
return null;
|
||||
}
|
||||
|
||||
let orgIdToUse = await this.getBillingOrg(orgId, transaction);
|
||||
|
||||
// Truncate value to 11 decimal places
|
||||
value = this.truncateValue(value);
|
||||
|
||||
@@ -57,7 +59,6 @@ export class UsageService {
|
||||
try {
|
||||
let usage;
|
||||
if (transaction) {
|
||||
const orgIdToUse = await this.getBillingOrg(orgId, transaction);
|
||||
usage = await this.internalAddUsage(
|
||||
orgIdToUse,
|
||||
featureId,
|
||||
@@ -66,7 +67,6 @@ export class UsageService {
|
||||
);
|
||||
} else {
|
||||
await db.transaction(async (trx) => {
|
||||
const orgIdToUse = await this.getBillingOrg(orgId, trx);
|
||||
usage = await this.internalAddUsage(
|
||||
orgIdToUse,
|
||||
featureId,
|
||||
@@ -92,7 +92,7 @@ export class UsageService {
|
||||
const delay = baseDelay + jitter;
|
||||
|
||||
logger.warn(
|
||||
`Deadlock detected for ${orgId}/${featureId}, retrying attempt ${attempt}/${maxRetries} after ${delay.toFixed(0)}ms`
|
||||
`Deadlock detected for ${orgIdToUse}/${featureId}, retrying attempt ${attempt}/${maxRetries} after ${delay.toFixed(0)}ms`
|
||||
);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, delay));
|
||||
@@ -100,7 +100,7 @@ export class UsageService {
|
||||
}
|
||||
|
||||
logger.error(
|
||||
`Failed to add usage for ${orgId}/${featureId} after ${attempt} attempts:`,
|
||||
`Failed to add usage for ${orgIdToUse}/${featureId} after ${attempt} attempts:`,
|
||||
error
|
||||
);
|
||||
break;
|
||||
@@ -169,7 +169,7 @@ export class UsageService {
|
||||
return;
|
||||
}
|
||||
|
||||
const orgIdToUse = await this.getBillingOrg(orgId);
|
||||
let orgIdToUse = await this.getBillingOrg(orgId);
|
||||
|
||||
try {
|
||||
// Truncate value to 11 decimal places if provided
|
||||
@@ -227,7 +227,7 @@ export class UsageService {
|
||||
orgId: string,
|
||||
featureId: FeatureId
|
||||
): Promise<string | null> {
|
||||
const orgIdToUse = await this.getBillingOrg(orgId);
|
||||
let orgIdToUse = await this.getBillingOrg(orgId);
|
||||
|
||||
const cacheKey = `customer_${orgIdToUse}_${featureId}`;
|
||||
const cached = cache.get<string>(cacheKey);
|
||||
@@ -274,7 +274,7 @@ export class UsageService {
|
||||
return null;
|
||||
}
|
||||
|
||||
const orgIdToUse = await this.getBillingOrg(orgId, trx);
|
||||
let orgIdToUse = await this.getBillingOrg(orgId, trx);
|
||||
|
||||
const usageId = `${orgIdToUse}-${featureId}`;
|
||||
|
||||
@@ -382,7 +382,7 @@ export class UsageService {
|
||||
return false;
|
||||
}
|
||||
|
||||
const orgIdToUse = await this.getBillingOrg(orgId, trx);
|
||||
let orgIdToUse = await this.getBillingOrg(orgId, trx);
|
||||
|
||||
// This method should check the current usage against the limits set for the organization
|
||||
// and kick out all of the sites on the org
|
||||
|
||||
@@ -78,8 +78,7 @@ export async function getOrgTierData(
|
||||
if (
|
||||
subscription.type === "tier1" ||
|
||||
subscription.type === "tier2" ||
|
||||
subscription.type === "tier3" ||
|
||||
subscription.type === "enterprise"
|
||||
subscription.type === "tier3"
|
||||
) {
|
||||
tier = subscription.type;
|
||||
active = true;
|
||||
|
||||
@@ -197,6 +197,7 @@ export async function updateSiteBandwidth(
|
||||
usageService
|
||||
.checkLimitSet(
|
||||
orgId,
|
||||
|
||||
FeatureId.EGRESS_DATA_MB,
|
||||
bandwidthUsage
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ export default async function migration() {
|
||||
// all roles set hoemdir to true
|
||||
|
||||
// generate ca certs for all orgs?
|
||||
// set authDaemonMode to "site" for all orgs
|
||||
// set authDaemonMode to "site" for all site-resources
|
||||
|
||||
try {
|
||||
db.transaction(() => {})();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
import { Geist, Inter, Manrope, Open_Sans } from "next/font/google";
|
||||
import localFont from "next/font/local";
|
||||
import { APP_FONT } from "./font-config";
|
||||
import { ThemeProvider } from "@app/providers/ThemeProvider";
|
||||
import EnvProvider from "@app/providers/EnvProvider";
|
||||
import { pullEnv } from "@app/lib/pullEnv";
|
||||
@@ -24,6 +25,7 @@ import { TanstackQueryProvider } from "@app/components/TanstackQueryProvider";
|
||||
import { TailwindIndicator } from "@app/components/TailwindIndicator";
|
||||
import { ViewportHeightFix } from "@app/components/ViewportHeightFix";
|
||||
import StoreInternalRedirect from "@app/components/StoreInternalRedirect";
|
||||
import { Inter } from "next/font/google";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: `Dashboard - ${process.env.BRANDING_APP_NAME || "Pangolin"}`,
|
||||
@@ -32,10 +34,79 @@ export const metadata: Metadata = {
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const font = Inter({
|
||||
const ember = localFont({
|
||||
src: [
|
||||
{
|
||||
path: "../../public/fonts/ember/AmazonEmber_Th.ttf",
|
||||
weight: "100",
|
||||
style: "normal"
|
||||
},
|
||||
{
|
||||
path: "../../public/fonts/ember/AmazonEmber_ThIt.ttf",
|
||||
weight: "100",
|
||||
style: "italic"
|
||||
},
|
||||
{
|
||||
path: "../../public/fonts/ember/AmazonEmber_Lt.ttf",
|
||||
weight: "300",
|
||||
style: "normal"
|
||||
},
|
||||
{
|
||||
path: "../../public/fonts/ember/AmazonEmber_LtIt.ttf",
|
||||
weight: "300",
|
||||
style: "italic"
|
||||
},
|
||||
{
|
||||
path: "../../public/fonts/ember/AmazonEmber_Rg.ttf",
|
||||
weight: "400",
|
||||
style: "normal"
|
||||
},
|
||||
{
|
||||
path: "../../public/fonts/ember/AmazonEmber_RgIt.ttf",
|
||||
weight: "400",
|
||||
style: "italic"
|
||||
},
|
||||
{
|
||||
path: "../../public/fonts/ember/Amazon-Ember-Medium.ttf",
|
||||
weight: "500",
|
||||
style: "normal"
|
||||
},
|
||||
{
|
||||
path: "../../public/fonts/ember/Amazon-Ember-MediumItalic.ttf",
|
||||
weight: "500",
|
||||
style: "italic"
|
||||
},
|
||||
{
|
||||
path: "../../public/fonts/ember/AmazonEmber_Bd.ttf",
|
||||
weight: "700",
|
||||
style: "normal"
|
||||
},
|
||||
{
|
||||
path: "../../public/fonts/ember/AmazonEmber_BdIt.ttf",
|
||||
weight: "700",
|
||||
style: "italic"
|
||||
},
|
||||
{
|
||||
path: "../../public/fonts/ember/AmazonEmber_He.ttf",
|
||||
weight: "800",
|
||||
style: "normal"
|
||||
},
|
||||
{
|
||||
path: "../../public/fonts/ember/AmazonEmber_HeIt.ttf",
|
||||
weight: "800",
|
||||
style: "italic"
|
||||
}
|
||||
],
|
||||
variable: "--font-ember",
|
||||
display: "swap"
|
||||
});
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"]
|
||||
});
|
||||
|
||||
const fontClassName = inter.className;
|
||||
|
||||
export default async function RootLayout({
|
||||
children
|
||||
}: Readonly<{
|
||||
@@ -79,16 +150,16 @@ export default async function RootLayout({
|
||||
|
||||
return (
|
||||
<html suppressHydrationWarning lang={locale}>
|
||||
<body className={`${font.className} h-screen-safe overflow-hidden`}>
|
||||
<body className={`${fontClassName} h-screen-safe overflow-hidden`}>
|
||||
<StoreInternalRedirect />
|
||||
<TopLoader />
|
||||
{/* build === "saas" && (
|
||||
{build === "saas" && (
|
||||
<Script
|
||||
src="https://rybbit.fossorial.io/api/script.js"
|
||||
data-site-id="fe1ff2a33287"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
)*/}
|
||||
)}
|
||||
<ViewportHeightFix />
|
||||
<NextIntlClientProvider>
|
||||
<ThemeProvider
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function SiteInfoCard({}: ClientInfoCardProps) {
|
||||
return (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
<InfoSections cols={4}>
|
||||
<InfoSections cols={3}>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>{t("name")}</InfoSectionTitle>
|
||||
<InfoSectionContent>{client.name}</InfoSectionContent>
|
||||
@@ -55,12 +55,6 @@ export default function SiteInfoCard({}: ClientInfoCardProps) {
|
||||
)}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>{t("address")}</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{client.subnet.split("/")[0]}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
</InfoSections>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function SiteInfoCard({}: SiteInfoCardProps) {
|
||||
return (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
<InfoSections cols={4}>
|
||||
<InfoSections cols={3}>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>{t("identifier")}</InfoSectionTitle>
|
||||
<InfoSectionContent>{site.niceId}</InfoSectionContent>
|
||||
@@ -68,15 +68,6 @@ export default function SiteInfoCard({}: SiteInfoCardProps) {
|
||||
{getConnectionTypeString(site.type)}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
|
||||
{site.type == "newt" && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>Address</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{site.address?.split("/")[0]}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
)}
|
||||
</InfoSections>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
@@ -20,7 +20,7 @@ export const isOrgSubscribed = cache(async (orgId: string) => {
|
||||
try {
|
||||
const subRes = await getCachedSubscription(orgId);
|
||||
subscribed =
|
||||
(subRes.data.data.tier == "tier1" || subRes.data.data.tier == "tier2" || subRes.data.data.tier == "tier3" || subRes.data.data.tier == "enterprise") &&
|
||||
(subRes.data.data.tier == "tier1" || subRes.data.data.tier == "tier2" || subRes.data.data.tier == "tier3") &&
|
||||
subRes.data.data.active;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,7 @@ export function SubscriptionStatusProvider({
|
||||
if (
|
||||
subscription.type == "tier1" ||
|
||||
subscription.type == "tier2" ||
|
||||
subscription.type == "tier3" ||
|
||||
subscription.type == "enterprise"
|
||||
subscription.type == "tier3"
|
||||
) {
|
||||
return {
|
||||
tier: subscription.type,
|
||||
@@ -62,7 +61,7 @@ export function SubscriptionStatusProvider({
|
||||
const isSubscribed = () => {
|
||||
const { tier, active } = getTier();
|
||||
return (
|
||||
(tier == "tier1" || tier == "tier2" || tier == "tier3" || tier == "enterprise") &&
|
||||
(tier == "tier1" || tier == "tier2" || tier == "tier3") &&
|
||||
active
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user