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 { withSession } from "../shared/with-session";
|
||||
import { injectCsrfToken, checkCsrfToken } from "../shared/inject-csrf";
|
||||
import { buildInfoPageHtml } from "../info-page";
|
||||
import { loginRouter } from "./login";
|
||||
import { usersApiRouter as apiRouter } from "./api/users";
|
||||
import { usersWebRouter as webRouter } from "./web/manage";
|
||||
|
@ -23,6 +24,11 @@ adminRouter.use(checkCsrfToken);
|
|||
adminRouter.use(injectLocals);
|
||||
adminRouter.use("/", loginRouter);
|
||||
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(
|
||||
(
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
<%- include("partials/shared_header", { title: "OAI Reverse Proxy Admin" }) %>
|
||||
<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) { %>
|
||||
<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 />
|
||||
|
@ -19,6 +25,7 @@
|
|||
<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/download-stats">Download Rentry Stats</a>
|
||||
<li><a href="/admin/service-info">Service Info</a></li>
|
||||
</ul>
|
||||
<h3>Maintenance</h3>
|
||||
<form id="maintenanceForm" action="/admin/manage/maintenance" method="post">
|
||||
|
|
|
@ -171,6 +171,13 @@ type Config = {
|
|||
* the admin UI to used over HTTP.
|
||||
*/
|
||||
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.
|
||||
|
@ -249,6 +256,7 @@ export const config: Config = {
|
|||
allowNicknameChanges: getEnvWithDefault("ALLOW_NICKNAME_CHANGES", true),
|
||||
showRecentImages: getEnvWithDefault("SHOW_RECENT_IMAGES", true),
|
||||
useInsecureCookies: getEnvWithDefault("USE_INSECURE_COOKIES", isDev),
|
||||
staticServiceInfo: getEnvWithDefault("STATIC_SERVICE_INFO", false),
|
||||
} as const;
|
||||
|
||||
function generateCookieSecret() {
|
||||
|
@ -314,7 +322,8 @@ export async function assertConfigIsValid() {
|
|||
// them to users.
|
||||
for (const key of getKeys(config)) {
|
||||
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]);
|
||||
if (maybeSensitive && !secured.has(key))
|
||||
|
@ -345,7 +354,6 @@ export const OMITTED_KEYS: (keyof Config)[] = [
|
|||
"awsCredentials",
|
||||
"proxyKey",
|
||||
"adminKey",
|
||||
"checkKeys",
|
||||
"rejectPhrases",
|
||||
"showTokenCosts",
|
||||
"googleSheetsKey",
|
||||
|
@ -359,6 +367,7 @@ export const OMITTED_KEYS: (keyof Config)[] = [
|
|||
"allowNicknameChanges",
|
||||
"showRecentImages",
|
||||
"useInsecureCookies",
|
||||
"staticServiceInfo",
|
||||
];
|
||||
|
||||
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 {
|
||||
AnthropicKey,
|
||||
GooglePalmKey,
|
||||
OpenAIKey,
|
||||
AwsBedrockKey,
|
||||
GooglePalmKey,
|
||||
keyPool,
|
||||
OpenAIKey,
|
||||
} from "./shared/key-management";
|
||||
import { ModelFamily, OpenAIModelFamily } from "./shared/models";
|
||||
import { getUniqueIps } from "./proxy/rate-limit";
|
||||
|
@ -72,7 +72,10 @@ export const handleInfoPage = (req: Request, res: Response) => {
|
|||
? getExternalUrlForHuggingfaceSpaceId(process.env.SPACE_ID)
|
||||
: req.protocol + "://" + req.get("host");
|
||||
|
||||
res.send(cacheInfoPageHtml(baseUrl));
|
||||
infoPageHtml = buildInfoPageHtml(baseUrl);
|
||||
infoPageLastUpdated = Date.now();
|
||||
|
||||
res.send(infoPageHtml);
|
||||
};
|
||||
|
||||
function getCostString(cost: number) {
|
||||
|
@ -80,8 +83,9 @@ function getCostString(cost: number) {
|
|||
return ` ($${cost.toFixed(2)})`;
|
||||
}
|
||||
|
||||
function cacheInfoPageHtml(baseUrl: string) {
|
||||
export function buildInfoPageHtml(baseUrl: string, asAdmin = false) {
|
||||
const keys = keyPool.list();
|
||||
const hideFullInfo = config.staticServiceInfo && !asAdmin;
|
||||
|
||||
modelStats.clear();
|
||||
serviceStats.clear();
|
||||
|
@ -97,31 +101,45 @@ function cacheInfoPageHtml(baseUrl: string) {
|
|||
|
||||
const allowDalle = config.allowedModelFamilies.includes("dall-e");
|
||||
|
||||
const info = {
|
||||
uptime: Math.floor(process.uptime()),
|
||||
endpoints: {
|
||||
...(openaiKeys ? { openai: baseUrl + "/proxy/openai" } : {}),
|
||||
...(openaiKeys
|
||||
? { ["openai2"]: baseUrl + "/proxy/openai/turbo-instruct" }
|
||||
: {}),
|
||||
...(openaiKeys && allowDalle
|
||||
? { ["openai-image"]: baseUrl + "/proxy/openai-image" }
|
||||
: {}),
|
||||
...(anthropicKeys ? { anthropic: baseUrl + "/proxy/anthropic" } : {}),
|
||||
...(palmKeys ? { "google-palm": baseUrl + "/proxy/google-palm" } : {}),
|
||||
...(awsKeys ? { aws: baseUrl + "/proxy/aws/claude" } : {}),
|
||||
},
|
||||
const endpoints = {
|
||||
...(openaiKeys ? { openai: baseUrl + "/proxy/openai" } : {}),
|
||||
...(openaiKeys
|
||||
? { ["openai2"]: baseUrl + "/proxy/openai/turbo-instruct" }
|
||||
: {}),
|
||||
...(openaiKeys && allowDalle
|
||||
? { ["openai-image"]: baseUrl + "/proxy/openai-image" }
|
||||
: {}),
|
||||
...(anthropicKeys ? { anthropic: baseUrl + "/proxy/anthropic" } : {}),
|
||||
...(palmKeys ? { "google-palm": baseUrl + "/proxy/google-palm" } : {}),
|
||||
...(awsKeys ? { aws: baseUrl + "/proxy/aws/claude" } : {}),
|
||||
};
|
||||
|
||||
const stats = {
|
||||
proompts,
|
||||
tookens: `${prettyTokens(tokens)}${getCostString(tokenCost)}`,
|
||||
...(config.textModelRateLimit ? { proomptersNow: getUniqueIps() } : {}),
|
||||
};
|
||||
|
||||
const keyInfo = {
|
||||
openaiKeys,
|
||||
anthropicKeys,
|
||||
palmKeys,
|
||||
awsKeys,
|
||||
};
|
||||
|
||||
const providerInfo = {
|
||||
...(openaiKeys ? getOpenAIInfo() : {}),
|
||||
...(anthropicKeys ? getAnthropicInfo() : {}),
|
||||
...(palmKeys ? { "palm-bison": getPalmInfo() } : {}),
|
||||
...(awsKeys ? { "aws-claude": getAwsInfo() } : {}),
|
||||
}
|
||||
|
||||
const info = {
|
||||
uptime: Math.floor(process.uptime()),
|
||||
endpoints,
|
||||
...(hideFullInfo ? {} : stats),
|
||||
...keyInfo,
|
||||
...(hideFullInfo ? {} : providerInfo),
|
||||
config: listConfig(),
|
||||
build: process.env.BUILD_INFO || "dev",
|
||||
};
|
||||
|
@ -129,7 +147,7 @@ function cacheInfoPageHtml(baseUrl: string) {
|
|||
const title = getServerTitle();
|
||||
const headerHtml = buildInfoPageHeader(new showdown.Converter(), title);
|
||||
|
||||
const pageBody = `<!DOCTYPE html>
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
@ -144,11 +162,6 @@ function cacheInfoPageHtml(baseUrl: string) {
|
|||
${getSelfServiceLinks()}
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
infoPageHtml = pageBody;
|
||||
infoPageLastUpdated = Date.now();
|
||||
|
||||
return pageBody;
|
||||
}
|
||||
|
||||
function getUniqueOpenAIOrgs(keys: KeyPoolKey[]) {
|
||||
|
@ -402,8 +415,8 @@ function getAwsInfo() {
|
|||
}
|
||||
|
||||
const customGreeting = fs.existsSync("greeting.md")
|
||||
? fs.readFileSync("greeting.md", "utf8")
|
||||
: null;
|
||||
? `\n## Server Greeting\n${fs.readFileSync("greeting.md", "utf8")}`
|
||||
: "";
|
||||
|
||||
/**
|
||||
* 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 (config.staticServiceInfo) {
|
||||
return converter.makeHtml(infoBody + customGreeting);
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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(" / ");
|
||||
|
||||
if (customGreeting) {
|
||||
infoBody += `\n## Server Greeting\n${customGreeting}`;
|
||||
}
|
||||
infoBody += customGreeting;
|
||||
|
||||
infoBody += buildRecentImageSection();
|
||||
|
||||
|
@ -544,11 +559,11 @@ function buildRecentImageSection() {
|
|||
|
||||
function escapeHtml(unsafe: string) {
|
||||
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) {
|
||||
|
|
|
@ -12,6 +12,7 @@ export const injectLocals: RequestHandler = (req, res, next) => {
|
|||
res.locals.quota = quota;
|
||||
res.locals.nextQuotaRefresh = userStore.getNextQuotaRefresh();
|
||||
res.locals.persistenceEnabled = config.gatekeeperStore !== "memory";
|
||||
res.locals.usersEnabled = config.gatekeeper === "user_token";
|
||||
res.locals.showTokenCosts = config.showTokenCosts;
|
||||
res.locals.maxIps = config.maxIpsPerUser;
|
||||
|
||||
|
|
Loading…
Reference in New Issue