adds STATIC_SERVICE_INFO config
This commit is contained in:
parent
3aca9e90f0
commit
3de79873e9
|
@ -4,6 +4,7 @@ import { HttpError } from "../shared/errors";
|
||||||
import { injectLocals } from "../shared/inject-locals";
|
import { injectLocals } from "../shared/inject-locals";
|
||||||
import { withSession } from "../shared/with-session";
|
import { withSession } from "../shared/with-session";
|
||||||
import { injectCsrfToken, checkCsrfToken } from "../shared/inject-csrf";
|
import { injectCsrfToken, checkCsrfToken } from "../shared/inject-csrf";
|
||||||
|
import { buildInfoPageHtml } from "../info-page";
|
||||||
import { loginRouter } from "./login";
|
import { loginRouter } from "./login";
|
||||||
import { usersApiRouter as apiRouter } from "./api/users";
|
import { usersApiRouter as apiRouter } from "./api/users";
|
||||||
import { usersWebRouter as webRouter } from "./web/manage";
|
import { usersWebRouter as webRouter } from "./web/manage";
|
||||||
|
@ -23,6 +24,11 @@ adminRouter.use(checkCsrfToken);
|
||||||
adminRouter.use(injectLocals);
|
adminRouter.use(injectLocals);
|
||||||
adminRouter.use("/", loginRouter);
|
adminRouter.use("/", loginRouter);
|
||||||
adminRouter.use("/manage", authorize({ via: "cookie" }), webRouter);
|
adminRouter.use("/manage", authorize({ via: "cookie" }), webRouter);
|
||||||
|
adminRouter.use("/service-info", authorize({ via: "cookie" }), (req, res) => {
|
||||||
|
return res.send(
|
||||||
|
buildInfoPageHtml(req.protocol + "://" + req.get("host"), true)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
adminRouter.use(
|
adminRouter.use(
|
||||||
(
|
(
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
<%- include("partials/shared_header", { title: "OAI Reverse Proxy Admin" }) %>
|
<%- include("partials/shared_header", { title: "OAI Reverse Proxy Admin" }) %>
|
||||||
<h1>OAI Reverse Proxy Admin</h1>
|
<h1>OAI Reverse Proxy Admin</h1>
|
||||||
|
<% if (!usersEnabled) { %>
|
||||||
|
<p style="color: red; background-color: #eedddd; padding: 1em">
|
||||||
|
<strong>🚨 <code>user_token</code> gatekeeper is not enabled.</strong><br />
|
||||||
|
<br />None of the user management features will do anything.
|
||||||
|
</p>
|
||||||
|
<% } %>
|
||||||
<% if (!persistenceEnabled) { %>
|
<% if (!persistenceEnabled) { %>
|
||||||
<p style="color: red; background-color: #eedddd; padding: 1em">
|
<p style="color: red; background-color: #eedddd; padding: 1em">
|
||||||
<strong>⚠️ Users will be lost when the server restarts because persistence is not configured.</strong><br />
|
<strong>⚠️ Users will be lost when the server restarts because persistence is not configured.</strong><br />
|
||||||
|
@ -19,6 +25,7 @@
|
||||||
<li><a href="/admin/manage/import-users">Import Users</a></li>
|
<li><a href="/admin/manage/import-users">Import Users</a></li>
|
||||||
<li><a href="/admin/manage/export-users">Export Users</a></li>
|
<li><a href="/admin/manage/export-users">Export Users</a></li>
|
||||||
<li><a href="/admin/manage/download-stats">Download Rentry Stats</a>
|
<li><a href="/admin/manage/download-stats">Download Rentry Stats</a>
|
||||||
|
<li><a href="/admin/service-info">Service Info</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<h3>Maintenance</h3>
|
<h3>Maintenance</h3>
|
||||||
<form id="maintenanceForm" action="/admin/manage/maintenance" method="post">
|
<form id="maintenanceForm" action="/admin/manage/maintenance" method="post">
|
||||||
|
|
|
@ -171,6 +171,13 @@ type Config = {
|
||||||
* the admin UI to used over HTTP.
|
* the admin UI to used over HTTP.
|
||||||
*/
|
*/
|
||||||
useInsecureCookies: boolean;
|
useInsecureCookies: boolean;
|
||||||
|
/**
|
||||||
|
* Whether to use a more minimal public Service Info page with static content.
|
||||||
|
* This disables all stats, including traffic, keys, and queue info.
|
||||||
|
* The full info page will appear if you have signed in as an admin using the
|
||||||
|
* configured ADMIN_KEY and go to /admin/service-info.
|
||||||
|
**/
|
||||||
|
staticServiceInfo?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// To change configs, create a file called .env in the root directory.
|
// To change configs, create a file called .env in the root directory.
|
||||||
|
@ -249,6 +256,7 @@ export const config: Config = {
|
||||||
allowNicknameChanges: getEnvWithDefault("ALLOW_NICKNAME_CHANGES", true),
|
allowNicknameChanges: getEnvWithDefault("ALLOW_NICKNAME_CHANGES", true),
|
||||||
showRecentImages: getEnvWithDefault("SHOW_RECENT_IMAGES", true),
|
showRecentImages: getEnvWithDefault("SHOW_RECENT_IMAGES", true),
|
||||||
useInsecureCookies: getEnvWithDefault("USE_INSECURE_COOKIES", isDev),
|
useInsecureCookies: getEnvWithDefault("USE_INSECURE_COOKIES", isDev),
|
||||||
|
staticServiceInfo: getEnvWithDefault("STATIC_SERVICE_INFO", false),
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
function generateCookieSecret() {
|
function generateCookieSecret() {
|
||||||
|
@ -314,7 +322,8 @@ export async function assertConfigIsValid() {
|
||||||
// them to users.
|
// them to users.
|
||||||
for (const key of getKeys(config)) {
|
for (const key of getKeys(config)) {
|
||||||
const maybeSensitive = ["key", "credentials", "secret", "password"].some(
|
const maybeSensitive = ["key", "credentials", "secret", "password"].some(
|
||||||
(sensitive) => key.toLowerCase().includes(sensitive)
|
(sensitive) =>
|
||||||
|
key.toLowerCase().includes(sensitive) && !["checkKeys"].includes(key)
|
||||||
);
|
);
|
||||||
const secured = new Set([...SENSITIVE_KEYS, ...OMITTED_KEYS]);
|
const secured = new Set([...SENSITIVE_KEYS, ...OMITTED_KEYS]);
|
||||||
if (maybeSensitive && !secured.has(key))
|
if (maybeSensitive && !secured.has(key))
|
||||||
|
@ -345,7 +354,6 @@ export const OMITTED_KEYS: (keyof Config)[] = [
|
||||||
"awsCredentials",
|
"awsCredentials",
|
||||||
"proxyKey",
|
"proxyKey",
|
||||||
"adminKey",
|
"adminKey",
|
||||||
"checkKeys",
|
|
||||||
"rejectPhrases",
|
"rejectPhrases",
|
||||||
"showTokenCosts",
|
"showTokenCosts",
|
||||||
"googleSheetsKey",
|
"googleSheetsKey",
|
||||||
|
@ -359,6 +367,7 @@ export const OMITTED_KEYS: (keyof Config)[] = [
|
||||||
"allowNicknameChanges",
|
"allowNicknameChanges",
|
||||||
"showRecentImages",
|
"showRecentImages",
|
||||||
"useInsecureCookies",
|
"useInsecureCookies",
|
||||||
|
"staticServiceInfo",
|
||||||
];
|
];
|
||||||
|
|
||||||
const getKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>;
|
const getKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>;
|
||||||
|
|
|
@ -4,10 +4,10 @@ import showdown from "showdown";
|
||||||
import { config, listConfig } from "./config";
|
import { config, listConfig } from "./config";
|
||||||
import {
|
import {
|
||||||
AnthropicKey,
|
AnthropicKey,
|
||||||
GooglePalmKey,
|
|
||||||
OpenAIKey,
|
|
||||||
AwsBedrockKey,
|
AwsBedrockKey,
|
||||||
|
GooglePalmKey,
|
||||||
keyPool,
|
keyPool,
|
||||||
|
OpenAIKey,
|
||||||
} from "./shared/key-management";
|
} from "./shared/key-management";
|
||||||
import { ModelFamily, OpenAIModelFamily } from "./shared/models";
|
import { ModelFamily, OpenAIModelFamily } from "./shared/models";
|
||||||
import { getUniqueIps } from "./proxy/rate-limit";
|
import { getUniqueIps } from "./proxy/rate-limit";
|
||||||
|
@ -72,7 +72,10 @@ export const handleInfoPage = (req: Request, res: Response) => {
|
||||||
? getExternalUrlForHuggingfaceSpaceId(process.env.SPACE_ID)
|
? getExternalUrlForHuggingfaceSpaceId(process.env.SPACE_ID)
|
||||||
: req.protocol + "://" + req.get("host");
|
: req.protocol + "://" + req.get("host");
|
||||||
|
|
||||||
res.send(cacheInfoPageHtml(baseUrl));
|
infoPageHtml = buildInfoPageHtml(baseUrl);
|
||||||
|
infoPageLastUpdated = Date.now();
|
||||||
|
|
||||||
|
res.send(infoPageHtml);
|
||||||
};
|
};
|
||||||
|
|
||||||
function getCostString(cost: number) {
|
function getCostString(cost: number) {
|
||||||
|
@ -80,8 +83,9 @@ function getCostString(cost: number) {
|
||||||
return ` ($${cost.toFixed(2)})`;
|
return ` ($${cost.toFixed(2)})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cacheInfoPageHtml(baseUrl: string) {
|
export function buildInfoPageHtml(baseUrl: string, asAdmin = false) {
|
||||||
const keys = keyPool.list();
|
const keys = keyPool.list();
|
||||||
|
const hideFullInfo = config.staticServiceInfo && !asAdmin;
|
||||||
|
|
||||||
modelStats.clear();
|
modelStats.clear();
|
||||||
serviceStats.clear();
|
serviceStats.clear();
|
||||||
|
@ -97,31 +101,45 @@ function cacheInfoPageHtml(baseUrl: string) {
|
||||||
|
|
||||||
const allowDalle = config.allowedModelFamilies.includes("dall-e");
|
const allowDalle = config.allowedModelFamilies.includes("dall-e");
|
||||||
|
|
||||||
const info = {
|
const endpoints = {
|
||||||
uptime: Math.floor(process.uptime()),
|
...(openaiKeys ? { openai: baseUrl + "/proxy/openai" } : {}),
|
||||||
endpoints: {
|
...(openaiKeys
|
||||||
...(openaiKeys ? { openai: baseUrl + "/proxy/openai" } : {}),
|
? { ["openai2"]: baseUrl + "/proxy/openai/turbo-instruct" }
|
||||||
...(openaiKeys
|
: {}),
|
||||||
? { ["openai2"]: baseUrl + "/proxy/openai/turbo-instruct" }
|
...(openaiKeys && allowDalle
|
||||||
: {}),
|
? { ["openai-image"]: baseUrl + "/proxy/openai-image" }
|
||||||
...(openaiKeys && allowDalle
|
: {}),
|
||||||
? { ["openai-image"]: baseUrl + "/proxy/openai-image" }
|
...(anthropicKeys ? { anthropic: baseUrl + "/proxy/anthropic" } : {}),
|
||||||
: {}),
|
...(palmKeys ? { "google-palm": baseUrl + "/proxy/google-palm" } : {}),
|
||||||
...(anthropicKeys ? { anthropic: baseUrl + "/proxy/anthropic" } : {}),
|
...(awsKeys ? { aws: baseUrl + "/proxy/aws/claude" } : {}),
|
||||||
...(palmKeys ? { "google-palm": baseUrl + "/proxy/google-palm" } : {}),
|
};
|
||||||
...(awsKeys ? { aws: baseUrl + "/proxy/aws/claude" } : {}),
|
|
||||||
},
|
const stats = {
|
||||||
proompts,
|
proompts,
|
||||||
tookens: `${prettyTokens(tokens)}${getCostString(tokenCost)}`,
|
tookens: `${prettyTokens(tokens)}${getCostString(tokenCost)}`,
|
||||||
...(config.textModelRateLimit ? { proomptersNow: getUniqueIps() } : {}),
|
...(config.textModelRateLimit ? { proomptersNow: getUniqueIps() } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const keyInfo = {
|
||||||
openaiKeys,
|
openaiKeys,
|
||||||
anthropicKeys,
|
anthropicKeys,
|
||||||
palmKeys,
|
palmKeys,
|
||||||
awsKeys,
|
awsKeys,
|
||||||
|
};
|
||||||
|
|
||||||
|
const providerInfo = {
|
||||||
...(openaiKeys ? getOpenAIInfo() : {}),
|
...(openaiKeys ? getOpenAIInfo() : {}),
|
||||||
...(anthropicKeys ? getAnthropicInfo() : {}),
|
...(anthropicKeys ? getAnthropicInfo() : {}),
|
||||||
...(palmKeys ? { "palm-bison": getPalmInfo() } : {}),
|
...(palmKeys ? { "palm-bison": getPalmInfo() } : {}),
|
||||||
...(awsKeys ? { "aws-claude": getAwsInfo() } : {}),
|
...(awsKeys ? { "aws-claude": getAwsInfo() } : {}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = {
|
||||||
|
uptime: Math.floor(process.uptime()),
|
||||||
|
endpoints,
|
||||||
|
...(hideFullInfo ? {} : stats),
|
||||||
|
...keyInfo,
|
||||||
|
...(hideFullInfo ? {} : providerInfo),
|
||||||
config: listConfig(),
|
config: listConfig(),
|
||||||
build: process.env.BUILD_INFO || "dev",
|
build: process.env.BUILD_INFO || "dev",
|
||||||
};
|
};
|
||||||
|
@ -129,7 +147,7 @@ function cacheInfoPageHtml(baseUrl: string) {
|
||||||
const title = getServerTitle();
|
const title = getServerTitle();
|
||||||
const headerHtml = buildInfoPageHeader(new showdown.Converter(), title);
|
const headerHtml = buildInfoPageHeader(new showdown.Converter(), title);
|
||||||
|
|
||||||
const pageBody = `<!DOCTYPE html>
|
return `<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
|
@ -144,11 +162,6 @@ function cacheInfoPageHtml(baseUrl: string) {
|
||||||
${getSelfServiceLinks()}
|
${getSelfServiceLinks()}
|
||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
|
|
||||||
infoPageHtml = pageBody;
|
|
||||||
infoPageLastUpdated = Date.now();
|
|
||||||
|
|
||||||
return pageBody;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUniqueOpenAIOrgs(keys: KeyPoolKey[]) {
|
function getUniqueOpenAIOrgs(keys: KeyPoolKey[]) {
|
||||||
|
@ -402,8 +415,8 @@ function getAwsInfo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const customGreeting = fs.existsSync("greeting.md")
|
const customGreeting = fs.existsSync("greeting.md")
|
||||||
? fs.readFileSync("greeting.md", "utf8")
|
? `\n## Server Greeting\n${fs.readFileSync("greeting.md", "utf8")}`
|
||||||
: null;
|
: "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the server operator provides a `greeting.md` file, it will be included in
|
* If the server operator provides a `greeting.md` file, it will be included in
|
||||||
|
@ -422,8 +435,12 @@ Logs are anonymous and do not contain IP addresses or timestamps. [You can see t
|
||||||
**If you are uncomfortable with this, don't send prompts to this proxy!**`;
|
**If you are uncomfortable with this, don't send prompts to this proxy!**`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.staticServiceInfo) {
|
||||||
|
return converter.makeHtml(infoBody + customGreeting);
|
||||||
|
}
|
||||||
|
|
||||||
const waits: string[] = [];
|
const waits: string[] = [];
|
||||||
infoBody += `\n## Estimated Wait Times\nIf the AI is busy, your prompt will processed when a slot frees up.`;
|
infoBody += `\n## Estimated Wait Times`;
|
||||||
|
|
||||||
if (config.openaiKey) {
|
if (config.openaiKey) {
|
||||||
// TODO: un-fuck this
|
// TODO: un-fuck this
|
||||||
|
@ -466,9 +483,7 @@ Logs are anonymous and do not contain IP addresses or timestamps. [You can see t
|
||||||
|
|
||||||
infoBody += "\n\n" + waits.join(" / ");
|
infoBody += "\n\n" + waits.join(" / ");
|
||||||
|
|
||||||
if (customGreeting) {
|
infoBody += customGreeting;
|
||||||
infoBody += `\n## Server Greeting\n${customGreeting}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
infoBody += buildRecentImageSection();
|
infoBody += buildRecentImageSection();
|
||||||
|
|
||||||
|
@ -544,11 +559,11 @@ function buildRecentImageSection() {
|
||||||
|
|
||||||
function escapeHtml(unsafe: string) {
|
function escapeHtml(unsafe: string) {
|
||||||
return unsafe
|
return unsafe
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, "&")
|
||||||
.replace(/</g, '<')
|
.replace(/</g, "<")
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, ">")
|
||||||
.replace(/"/g, '"')
|
.replace(/"/g, """)
|
||||||
.replace(/'/g, ''');
|
.replace(/'/g, "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExternalUrlForHuggingfaceSpaceId(spaceId: string) {
|
function getExternalUrlForHuggingfaceSpaceId(spaceId: string) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ export const injectLocals: RequestHandler = (req, res, next) => {
|
||||||
res.locals.quota = quota;
|
res.locals.quota = quota;
|
||||||
res.locals.nextQuotaRefresh = userStore.getNextQuotaRefresh();
|
res.locals.nextQuotaRefresh = userStore.getNextQuotaRefresh();
|
||||||
res.locals.persistenceEnabled = config.gatekeeperStore !== "memory";
|
res.locals.persistenceEnabled = config.gatekeeperStore !== "memory";
|
||||||
|
res.locals.usersEnabled = config.gatekeeper === "user_token";
|
||||||
res.locals.showTokenCosts = config.showTokenCosts;
|
res.locals.showTokenCosts = config.showTokenCosts;
|
||||||
res.locals.maxIps = config.maxIpsPerUser;
|
res.locals.maxIps = config.maxIpsPerUser;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue