mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-13 16:36:41 +00:00
Merge branch 'dev' into feat/login-page-customization
This commit is contained in:
@@ -51,6 +51,21 @@ export const internal = axios.create({
|
||||
}
|
||||
});
|
||||
|
||||
const remoteAPIURL =
|
||||
process.env.NODE_ENV === "development"
|
||||
? (process.env.NEXT_PUBLIC_FOSSORIAL_REMOTE_API_URL ??
|
||||
"https://api.fossorial.io")
|
||||
: "https://api.fossorial.io";
|
||||
|
||||
export const remote = axios.create({
|
||||
baseURL: `${remoteAPIURL}/api/v1`,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-CSRF-Token": "x-csrf-protection"
|
||||
}
|
||||
});
|
||||
|
||||
export const priv = axios.create({
|
||||
baseURL: `http://localhost:${process.env.SERVER_INTERNAL_PORT}/api/v1`,
|
||||
timeout: 10000,
|
||||
|
||||
13
src/lib/durationToMs.ts
Normal file
13
src/lib/durationToMs.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export function durationToMs(
|
||||
value: number,
|
||||
unit: "seconds" | "minutes" | "hours" | "days" | "weeks"
|
||||
): number {
|
||||
const multipliers = {
|
||||
seconds: 1000,
|
||||
minutes: 60 * 1000,
|
||||
hours: 60 * 60 * 1000,
|
||||
days: 24 * 60 * 60 * 1000,
|
||||
weeks: 7 * 24 * 60 * 60 * 1000
|
||||
};
|
||||
return value * multipliers[unit];
|
||||
}
|
||||
@@ -21,7 +21,17 @@ export function pullEnv(): Env {
|
||||
environment: process.env.ENVIRONMENT as string,
|
||||
sandbox_mode: process.env.SANDBOX_MODE === "true" ? true : false,
|
||||
version: process.env.APP_VERSION as string,
|
||||
dashboardUrl: process.env.DASHBOARD_URL as string
|
||||
dashboardUrl: process.env.DASHBOARD_URL as string,
|
||||
notifications: {
|
||||
product_updates:
|
||||
process.env.PRODUCT_UPDATES_NOTIFICATION_ENABLED === "true"
|
||||
? true
|
||||
: false,
|
||||
new_releases:
|
||||
process.env.NEW_RELEASES_NOTIFICATION_ENABLED === "true"
|
||||
? true
|
||||
: false
|
||||
}
|
||||
},
|
||||
email: {
|
||||
emailEnabled: process.env.EMAIL_ENABLED === "true" ? true : false
|
||||
@@ -50,9 +60,7 @@ export function pullEnv(): Env {
|
||||
hideSupporterKey:
|
||||
process.env.HIDE_SUPPORTER_KEY === "true" ? true : false,
|
||||
usePangolinDns:
|
||||
process.env.USE_PANGOLIN_DNS === "true"
|
||||
? true
|
||||
: false
|
||||
process.env.USE_PANGOLIN_DNS === "true" ? true : false
|
||||
},
|
||||
|
||||
branding: {
|
||||
|
||||
67
src/lib/queries.ts
Normal file
67
src/lib/queries.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
|
||||
import { durationToMs } from "./durationToMs";
|
||||
import { build } from "@server/build";
|
||||
import { remote } from "./api";
|
||||
import type ResponseT from "@server/types/Response";
|
||||
|
||||
export type ProductUpdate = {
|
||||
link: string | null;
|
||||
build: "enterprise" | "oss" | "saas" | null;
|
||||
id: number;
|
||||
type: "Update" | "Important" | "New" | "Warning";
|
||||
title: string;
|
||||
contents: string;
|
||||
publishedAt: Date;
|
||||
showUntil: Date;
|
||||
};
|
||||
|
||||
export type LatestVersionResponse = {
|
||||
pangolin: {
|
||||
latestVersion: string;
|
||||
releaseNotes: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const productUpdatesQueries = {
|
||||
list: (enabled: boolean) =>
|
||||
queryOptions({
|
||||
queryKey: ["PRODUCT_UPDATES"] as const,
|
||||
queryFn: async ({ signal }) => {
|
||||
const sp = new URLSearchParams({
|
||||
build
|
||||
});
|
||||
const data = await remote.get<ResponseT<ProductUpdate[]>>(
|
||||
`/product-updates?${sp.toString()}`,
|
||||
{ signal }
|
||||
);
|
||||
return data.data;
|
||||
},
|
||||
refetchInterval: (query) => {
|
||||
if (query.state.data) {
|
||||
return durationToMs(5, "minutes");
|
||||
}
|
||||
return false;
|
||||
},
|
||||
enabled
|
||||
}),
|
||||
latestVersion: (enabled: boolean) =>
|
||||
queryOptions({
|
||||
queryKey: ["LATEST_VERSION"] as const,
|
||||
queryFn: async ({ signal }) => {
|
||||
const data = await remote.get<ResponseT<LatestVersionResponse>>(
|
||||
"/versions",
|
||||
{ signal }
|
||||
);
|
||||
return data.data;
|
||||
},
|
||||
placeholderData: keepPreviousData,
|
||||
refetchInterval: (query) => {
|
||||
if (query.state.data) {
|
||||
return durationToMs(30, "minutes");
|
||||
}
|
||||
return false;
|
||||
},
|
||||
enabled: enabled && (build === "oss" || build === "enterprise") // disabled in cloud version
|
||||
// because we don't need to listen for new versions there
|
||||
})
|
||||
};
|
||||
47
src/lib/timeAgoFormatter.ts
Normal file
47
src/lib/timeAgoFormatter.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
export function timeAgoFormatter(
|
||||
dateInput: string | Date,
|
||||
short: boolean = false
|
||||
): string {
|
||||
const date = new Date(dateInput);
|
||||
const now = new Date();
|
||||
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
||||
|
||||
const secondsInMinute = 60;
|
||||
const secondsInHour = 60 * secondsInMinute;
|
||||
const secondsInDay = 24 * secondsInHour;
|
||||
const secondsInWeek = 7 * secondsInDay;
|
||||
const secondsInMonth = 30 * secondsInDay;
|
||||
const secondsInYear = 365 * secondsInDay;
|
||||
|
||||
let value: number;
|
||||
let unit: Intl.RelativeTimeFormatUnit;
|
||||
|
||||
if (diffInSeconds < secondsInMinute) {
|
||||
value = diffInSeconds;
|
||||
unit = "second";
|
||||
} else if (diffInSeconds < secondsInHour) {
|
||||
value = Math.floor(diffInSeconds / secondsInMinute);
|
||||
unit = "minute";
|
||||
} else if (diffInSeconds < secondsInDay) {
|
||||
value = Math.floor(diffInSeconds / secondsInHour);
|
||||
unit = "hour";
|
||||
} else if (diffInSeconds < secondsInWeek) {
|
||||
value = Math.floor(diffInSeconds / secondsInDay);
|
||||
unit = "day";
|
||||
} else if (diffInSeconds < secondsInMonth) {
|
||||
value = Math.floor(diffInSeconds / secondsInWeek);
|
||||
unit = "week";
|
||||
} else if (diffInSeconds < secondsInYear) {
|
||||
value = Math.floor(diffInSeconds / secondsInMonth);
|
||||
unit = "month";
|
||||
} else {
|
||||
value = Math.floor(diffInSeconds / secondsInYear);
|
||||
unit = "year";
|
||||
}
|
||||
|
||||
const rtf = new Intl.RelativeTimeFormat(navigator.languages[0] ?? "en", {
|
||||
numeric: "auto",
|
||||
style: short ? "narrow" : "long"
|
||||
});
|
||||
return rtf.format(-value, unit);
|
||||
}
|
||||
@@ -4,6 +4,10 @@ export type Env = {
|
||||
sandbox_mode: boolean;
|
||||
version: string;
|
||||
dashboardUrl: string;
|
||||
notifications: {
|
||||
product_updates: boolean;
|
||||
new_releases: boolean;
|
||||
};
|
||||
};
|
||||
server: {
|
||||
externalPort: string;
|
||||
@@ -29,11 +33,11 @@ export type Env = {
|
||||
enableClients: boolean;
|
||||
hideSupporterKey: boolean;
|
||||
usePangolinDns: boolean;
|
||||
},
|
||||
};
|
||||
branding: {
|
||||
appName?: string;
|
||||
background_image_path?: string;
|
||||
logo?: {
|
||||
logo: {
|
||||
lightPath?: string;
|
||||
darkPath?: string;
|
||||
authPage?: {
|
||||
@@ -43,22 +47,22 @@ export type Env = {
|
||||
navbar?: {
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
},
|
||||
loginPage?: {
|
||||
};
|
||||
};
|
||||
loginPage: {
|
||||
titleText?: string;
|
||||
subtitleText?: string;
|
||||
},
|
||||
signupPage?: {
|
||||
};
|
||||
signupPage: {
|
||||
titleText?: string;
|
||||
subtitleText?: string;
|
||||
},
|
||||
resourceAuthPage?: {
|
||||
};
|
||||
resourceAuthPage: {
|
||||
showLogo?: boolean;
|
||||
hidePoweredBy?: boolean;
|
||||
titleText?: string;
|
||||
subtitleText?: string;
|
||||
},
|
||||
};
|
||||
footer?: string;
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user