feat: support close relay api (#111 #112)

This commit is contained in:
Zhang Minghan 2024-03-13 14:25:02 +08:00
parent 4f4bc1561b
commit 40cb56761d
12 changed files with 73 additions and 17 deletions

View File

@ -33,6 +33,7 @@ export type SearchState = {
export type SiteState = { export type SiteState = {
close_register: boolean; close_register: boolean;
close_relay: boolean;
relay_plan: boolean; relay_plan: boolean;
quota: number; quota: number;
buy_link: string; buy_link: string;
@ -123,8 +124,9 @@ export const initialSystemState: SystemProps = {
pwa_manifest: "", pwa_manifest: "",
}, },
site: { site: {
relay_plan: false,
close_register: false, close_register: false,
close_relay: false,
relay_plan: false,
quota: 0, quota: 0,
buy_link: "", buy_link: "",
announcement: "", announcement: "",

View File

@ -706,6 +706,8 @@
"searchPlaceholder": "DuckDuckGo 接入点 (格式仅需填写 https://example.com)", "searchPlaceholder": "DuckDuckGo 接入点 (格式仅需填写 https://example.com)",
"closeRegistration": "暂停注册", "closeRegistration": "暂停注册",
"closeRegistrationTip": "暂停注册,关闭后新用户将无法注册", "closeRegistrationTip": "暂停注册,关闭后新用户将无法注册",
"closeRelay": "关闭中转 API",
"closeRelayTip": "关闭中转 API关闭后中转 API 将无法使用",
"relayPlan": "订阅配额支持中转 API", "relayPlan": "订阅配额支持中转 API",
"relayPlanTip": "订阅配额支持中转 API开启后中转 API 计费会优先考虑使用用户订阅配额\n提示订阅为次数配额对 Token 计费的模型可能会影响成本)", "relayPlanTip": "订阅配额支持中转 API开启后中转 API 计费会优先考虑使用用户订阅配额\n提示订阅为次数配额对 Token 计费的模型可能会影响成本)",
"quota": "用户初始点数", "quota": "用户初始点数",

View File

@ -564,7 +564,9 @@
"searchPlaceholder": "DuckDuckGo Access Point (Format only https://example.com)", "searchPlaceholder": "DuckDuckGo Access Point (Format only https://example.com)",
"image_store": "Picture storage", "image_store": "Picture storage",
"image_storeTip": "Images generated by the OpenAI channel DALL-E will be stored on the server to prevent invalidation of the images", "image_storeTip": "Images generated by the OpenAI channel DALL-E will be stored on the server to prevent invalidation of the images",
"image_storeNoBackend": "No backend domain configured, cannot enable image storage" "image_storeNoBackend": "No backend domain configured, cannot enable image storage",
"closeRelay": "Turn off Staging API",
"closeRelayTip": "Turn off the staging API, the staging API will not be available after turning off"
}, },
"user": "Users", "user": "Users",
"invitation-code": "Invitation Code", "invitation-code": "Invitation Code",

View File

@ -564,7 +564,9 @@
"searchPlaceholder": "DuckDuckGoアクセスポイントフォーマットのみhttps://example.com ", "searchPlaceholder": "DuckDuckGoアクセスポイントフォーマットのみhttps://example.com ",
"image_store": "画像ストレージ", "image_store": "画像ストレージ",
"image_storeTip": "OpenAIチャンネルDALL - Eによって生成された画像は、画像の無効化を防ぐためにサーバーに保存されます", "image_storeTip": "OpenAIチャンネルDALL - Eによって生成された画像は、画像の無効化を防ぐためにサーバーに保存されます",
"image_storeNoBackend": "バックエンドドメインが設定されていません。画像ストレージを有効にできません" "image_storeNoBackend": "バックエンドドメインが設定されていません。画像ストレージを有効にできません",
"closeRelay": "ステージングAPIをオフにする",
"closeRelayTip": "ステージングAPIをオフにすると、オフにするとステージングAPIは使用できなくなります"
}, },
"user": "ユーザー管理", "user": "ユーザー管理",
"invitation-code": "招待コード", "invitation-code": "招待コード",

View File

@ -564,7 +564,9 @@
"searchPlaceholder": "Точка доступа DuckDuckGo (только в формате https://example.com)", "searchPlaceholder": "Точка доступа DuckDuckGo (только в формате https://example.com)",
"image_store": "Хранение изображений", "image_store": "Хранение изображений",
"image_storeTip": "Изображения, сгенерированные каналом OpenAI DALL-E, будут храниться на сервере, чтобы предотвратить недействительность изображений", "image_storeTip": "Изображения, сгенерированные каналом OpenAI DALL-E, будут храниться на сервере, чтобы предотвратить недействительность изображений",
"image_storeNoBackend": "Нет настроенного внутреннего домена, невозможно включить хранение изображений" "image_storeNoBackend": "Нет настроенного внутреннего домена, невозможно включить хранение изображений",
"closeRelay": "Отключить Staging API",
"closeRelayTip": "Отключите промежуточный API, промежуточный API будет недоступен после отключения"
}, },
"user": "Управление пользователями", "user": "Управление пользователями",
"invitation-code": "Код приглашения", "invitation-code": "Код приглашения",

View File

@ -44,7 +44,7 @@ import {
} from "@/components/ui/dialog.tsx"; } from "@/components/ui/dialog.tsx";
import { DialogTitle } from "@radix-ui/react-dialog"; import { DialogTitle } from "@radix-ui/react-dialog";
import Require from "@/components/Require.tsx"; import Require from "@/components/Require.tsx";
import { Loader2, PencilLine, Settings2 } from "lucide-react"; import { PencilLine, RotateCw, Save, Settings2 } from "lucide-react";
import { FlexibleTextarea } from "@/components/ui/textarea.tsx"; import { FlexibleTextarea } from "@/components/ui/textarea.tsx";
import Tips from "@/components/Tips.tsx"; import Tips from "@/components/Tips.tsx";
import { cn } from "@/components/ui/lib/utils.ts"; import { cn } from "@/components/ui/lib/utils.ts";
@ -497,6 +497,21 @@ function Site({ data, dispatch, onChange }: CompProps<SiteState>) {
}} }}
/> />
</ParagraphItem> </ParagraphItem>
<ParagraphItem>
<Label>
{t("admin.system.closeRelay")}
<Tips
className={`inline-block`}
content={t("admin.system.closeRelayTip")}
/>
</Label>
<Switch
checked={data.close_relay}
onCheckedChange={(value) => {
dispatch({ type: "update:site.close_relay", value });
}}
/>
</ParagraphItem>
<ParagraphItem> <ParagraphItem>
<Label> <Label>
{t("admin.system.relayPlan")} {t("admin.system.relayPlan")}
@ -843,7 +858,7 @@ function System() {
if (doToast !== false) toastState(toast, t, res, true); if (doToast !== false) toastState(toast, t, res, true);
}; };
useEffectAsync(async () => { const doRefresh = async () => {
setLoading(true); setLoading(true);
const res = await getConfig(); const res = await getConfig();
setLoading(false); setLoading(false);
@ -851,18 +866,36 @@ function System() {
if (res.status) { if (res.status) {
setData({ type: "set", value: res.data }); setData({ type: "set", value: res.data });
} }
}, []); };
useEffectAsync(doRefresh, []);
return ( return (
<div className={`system`}> <div className={`system`}>
<Card className={`admin-card system-card`}> <Card className={`admin-card system-card`}>
<CardHeader className={`select-none`}> <CardHeader className={`select-none`}>
<CardTitle> <CardTitle>{t("admin.settings")}</CardTitle>
{t("admin.settings")}
{loading && <Loader2 className={`h-4 w-4 ml-2 inline-block`} />}
</CardTitle>
</CardHeader> </CardHeader>
<CardContent className={`flex flex-col gap-1`}> <CardContent className={`flex flex-col gap-1`}>
<div className={`system-actions flex flex-row`}>
<div className={`grow`} />
<Button
size={`icon`}
variant={`outline`}
loading={true}
className={`mr-2`}
onClick={async () => await doRefresh()}
>
<RotateCw className={cn(loading && `animate-spin`, `h-4 w-4`)} />
</Button>
<Button
size={`icon`}
loading={true}
onClick={async () => await doSaving()}
>
<Save className={`h-4 w-4`} />
</Button>
</div>
<General <General
form={data} form={data}
data={data.general} data={data.general}

View File

@ -196,7 +196,7 @@ func DeepLogin(c *gin.Context, token string) (string, error) {
db := utils.GetDBFromContext(c) db := utils.GetDBFromContext(c)
if !IsUserExist(db, user.Username) { if !IsUserExist(db, user.Username) {
if channel.SystemInstance.IsCloseRegister() { if globals.CloseRegistration {
return "", errors.New("this site is not open for registration") return "", errors.New("this site is not open for registration")
} }

View File

@ -2,6 +2,7 @@ package auth
import ( import (
"chat/channel" "chat/channel"
"chat/globals"
"chat/utils" "chat/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http" "net/http"
@ -143,7 +144,7 @@ func RegisterAPI(c *gin.Context) {
return return
} }
if channel.SystemInstance.IsCloseRegister() { if globals.CloseRegistration {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"status": false, "status": false,
"error": "this site is not open for registration", "error": "this site is not open for registration",

View File

@ -35,6 +35,7 @@ type generalState struct {
type siteState struct { type siteState struct {
CloseRegister bool `json:"close_register" mapstructure:"closeregister"` CloseRegister bool `json:"close_register" mapstructure:"closeregister"`
CloseRelay bool `json:"close_relay" mapstructure:"closerelay"`
RelayPlan bool `json:"relay_plan" mapstructure:"relayplan"` RelayPlan bool `json:"relay_plan" mapstructure:"relayplan"`
Quota float64 `json:"quota" mapstructure:"quota"` Quota float64 `json:"quota" mapstructure:"quota"`
BuyLink string `json:"buy_link" mapstructure:"buylink"` BuyLink string `json:"buy_link" mapstructure:"buylink"`
@ -94,6 +95,9 @@ func NewSystemConfig() *SystemConfig {
func (c *SystemConfig) Load() { func (c *SystemConfig) Load() {
globals.NotifyUrl = c.GetBackend() globals.NotifyUrl = c.GetBackend()
globals.CloseRegistration = c.Site.CloseRegister
globals.CloseRelay = c.Site.CloseRelay
globals.ArticlePermissionGroup = c.Common.Article globals.ArticlePermissionGroup = c.Common.Article
globals.GenerationPermissionGroup = c.Common.Generation globals.GenerationPermissionGroup = c.Common.Generation
globals.CacheAcceptedModels = c.Common.Cache globals.CacheAcceptedModels = c.Common.Cache
@ -282,10 +286,6 @@ func (c *SystemConfig) AcceptImageStore() bool {
return c.Common.ImageStore return c.Common.ImageStore
} }
func (c *SystemConfig) IsCloseRegister() bool {
return c.Site.CloseRegister
}
func (c *SystemConfig) SupportRelayPlan() bool { func (c *SystemConfig) SupportRelayPlan() bool {
return c.Site.RelayPlan return c.Site.RelayPlan
} }

View File

@ -18,6 +18,8 @@ var CacheAcceptedModels []string
var CacheAcceptedExpire int64 var CacheAcceptedExpire int64
var CacheAcceptedSize int64 var CacheAcceptedSize int64
var AcceptImageStore bool var AcceptImageStore bool
var CloseRegistration bool
var CloseRelay bool
func OriginIsAllowed(uri string) bool { func OriginIsAllowed(uri string) bool {
if len(AllowedOrigins) == 0 { if len(AllowedOrigins) == 0 {

View File

@ -26,6 +26,11 @@ func supportRelayPlan() bool {
} }
func ChatRelayAPI(c *gin.Context) { func ChatRelayAPI(c *gin.Context) {
if globals.CloseRelay {
abortWithErrorResponse(c, fmt.Errorf("relay api is denied of access"), "access_denied_error")
return
}
username := utils.GetUserFromContext(c) username := utils.GetUserFromContext(c)
if username == "" { if username == "" {
abortWithErrorResponse(c, fmt.Errorf("access denied for invalid api key"), "authentication_error") abortWithErrorResponse(c, fmt.Errorf("access denied for invalid api key"), "authentication_error")

View File

@ -15,6 +15,11 @@ import (
) )
func ImagesRelayAPI(c *gin.Context) { func ImagesRelayAPI(c *gin.Context) {
if globals.CloseRelay {
abortWithErrorResponse(c, fmt.Errorf("relay api is denied of access"), "access_denied_error")
return
}
username := utils.GetUserFromContext(c) username := utils.GetUserFromContext(c)
if username == "" { if username == "" {
abortWithErrorResponse(c, fmt.Errorf("access denied for invalid api key"), "authentication_error") abortWithErrorResponse(c, fmt.Errorf("access denied for invalid api key"), "authentication_error")