Skip to content

Commit

Permalink
feat: add custom website title and logo feature; support update root …
Browse files Browse the repository at this point in the history
…password in admin system settings
  • Loading branch information
zmh-program committed Jan 3, 2024
1 parent 89e5e13 commit 4efd0e8
Show file tree
Hide file tree
Showing 31 changed files with 443 additions and 30 deletions.
30 changes: 30 additions & 0 deletions admin/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ type SubscriptionOperationForm struct {
Month int64 `json:"month"`
}

type UpdateRootPasswordForm struct {
Password string `json:"password"`
}

func InfoAPI(c *gin.Context) {
db := utils.GetDBFromContext(c)
cache := utils.GetCacheFromContext(c)
Expand Down Expand Up @@ -161,3 +165,29 @@ func UserSubscriptionAPI(c *gin.Context) {
"status": true,
})
}

func UpdateRootPasswordAPI(c *gin.Context) {
var form UpdateRootPasswordForm
if err := c.ShouldBindJSON(&form); err != nil {
c.JSON(http.StatusOK, gin.H{
"status": false,
"error": err.Error(),
})
return
}

db := utils.GetDBFromContext(c)
cache := utils.GetCacheFromContext(c)
err := UpdateRootPassword(db, cache, form.Password)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"status": false,
"error": err.Error(),
})
return
}

c.JSON(http.StatusOK, gin.H{
"status": true,
})
}
1 change: 1 addition & 0 deletions admin/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ func Register(app *gin.RouterGroup) {
app.GET("/admin/user/list", UserPaginationAPI)
app.POST("/admin/user/quota", UserQuotaAPI)
app.POST("/admin/user/subscription", UserSubscriptionAPI)
app.POST("/admin/user/root", UpdateRootPasswordAPI)
}
20 changes: 20 additions & 0 deletions admin/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ package admin

import (
"chat/utils"
"context"
"database/sql"
"fmt"
"github.com/go-redis/redis/v8"
"math"
"strings"
"time"
)

Expand Down Expand Up @@ -109,3 +113,19 @@ func SubscriptionOperation(db *sql.DB, id int64, month int64) error {

return err
}

func UpdateRootPassword(db *sql.DB, cache *redis.Client, password string) error {
password = strings.TrimSpace(password)
if len(password) < 6 || len(password) > 36 {
return fmt.Errorf("password length must be between 6 and 36")
}

if _, err := db.Exec(`
UPDATE auth SET password = ? WHERE username = 'root'
`, utils.Sha2Encrypt(password)); err != nil {
return err
}

cache.Del(context.Background(), fmt.Sprint("nio:user:root"))
return nil
}
24 changes: 24 additions & 0 deletions app/src/admin/api/info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import axios from "axios";
import { setAppLogo, setAppName } from "@/utils/env.ts";

export type SiteInfo = {
title: string;
logo: string;
};

export async function getSiteInfo(): Promise<SiteInfo> {
try {
const response = await axios.get("/info");
return response.data as SiteInfo;
} catch (e) {
console.warn(e);
return { title: "", logo: "" };
}
}

export function syncSiteInfo() {
getSiteInfo().then((info) => {
setAppName(info.title);
setAppLogo(info.logo);
});
}
15 changes: 15 additions & 0 deletions app/src/admin/api/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { getErrorMessage } from "@/utils/base.ts";
import axios from "axios";

export type GeneralState = {
title: string;
logo: string;
backend: string;
};

Expand Down Expand Up @@ -47,8 +49,21 @@ export async function setConfig(config: SystemProps): Promise<CommonResponse> {
}
}

export async function updateRootPassword(
password: string,
): Promise<CommonResponse> {
try {
const response = await axios.post(`/admin/user/root`, { password });
return response.data as CommonResponse;
} catch (e) {
return { status: false, error: getErrorMessage(e) };
}
}

export const initialSystemState: SystemProps = {
general: {
logo: "",
title: "",
backend: "",
},
mail: {
Expand Down
1 change: 1 addition & 0 deletions app/src/assets/pages/auth.less
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
.logo {
width: 4rem;
height: 4rem;
border-radius: var(--radius);
}

& > * {
Expand Down
1 change: 1 addition & 0 deletions app/src/assets/pages/navbar.less
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
margin: 2px;
width: 40px;
height: 40px;
border-radius: var(--radius);
cursor: pointer;
}

Expand Down
7 changes: 6 additions & 1 deletion app/src/assets/ui.less
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,18 @@ input[type="number"] {
}
}

.paragraph-space {
width: 100%;
height: 0.25rem;
}

.paragraph-footer {
display: flex;
flex-direction: row;
margin-top: 1rem;

& > * {
margin-right: 1rem;
margin-right: 0.5rem;

&:last-child {
margin-right: 0;
Expand Down
4 changes: 4 additions & 0 deletions app/src/components/Paragraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export function ParagraphDescription({ children }: { children: string }) {
);
}

export function ParagraphSpace() {
return <div className={`paragraph-space`} />;
}

function ParagraphFooter({ children }: { children: React.ReactNode }) {
return <div className={`paragraph-footer`}>{children}</div>;
}
Expand Down
3 changes: 2 additions & 1 deletion app/src/components/app/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import MenuBar from "./MenuBar.tsx";
import { getMemory } from "@/utils/memory.ts";
import { goAuth } from "@/utils/app.ts";
import Avatar from "@/components/Avatar.tsx";
import { appLogo } from "@/utils/env.ts";

function NavMenu() {
const username = useSelector(selectUsername);
Expand Down Expand Up @@ -53,7 +54,7 @@ function NavBar() {
</Button>
<img
className={`logo`}
src="/favicon.ico"
src={appLogo}
alt=""
onClick={() => router.navigate("/")}
/>
Expand Down
3 changes: 3 additions & 0 deletions app/src/conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { getMemory } from "@/utils/memory.ts";
import { Compass, Image, Newspaper } from "lucide-react";
import React from "react";
import { syncSiteInfo } from "@/admin/api/info.ts";

export const version = "3.8.0";
export const dev: boolean = getDev();
Expand Down Expand Up @@ -524,3 +525,5 @@ export function login() {
axios.defaults.baseURL = rest_api;
axios.defaults.headers.post["Content-Type"] = "application/json";
axios.defaults.headers.common["Authorization"] = getMemory(tokenField);

syncSiteInfo();
11 changes: 10 additions & 1 deletion app/src/resources/i18n/cn.json
Original file line number Diff line number Diff line change
Expand Up @@ -464,17 +464,26 @@
"search": "联网搜索",
"mail": "SMTP 发件设置",
"save": "保存",
"updateRoot": "修改 Root 密码",
"updateRootTip": "请谨慎操作,修改 Root 密码后,您需要重新登录。",
"updateRootPlaceholder": "请输入新的 Root 密码",
"updateRootRepeatPlaceholder": "请再次输入新的 Root 密码",
"test": "测试发件",
"title": "网站名称",
"titleTip": "网站名称,用于显示在网站标题,留空默认",
"logo": "网站 Logo",
"logoTip": "网站 Logo 的链接,用于显示在网站标题,留空默认 (如 {{logo}})",
"backend": "后端域名",
"backendTip": "后端回调域名(docker 安装默认路径为 /api),接收回调参数。",
"backendPlaceholder": "后端回调域名,默认为空,接受回调必填(如 {{backend}})",
"mailHost": "发件域名",
"mailPort": "SMTP 端口",
"mailUser": "用户名",
"mailPass": "密码",
"mailFrom": "发件人",
"searchEndpoint": "搜索接入点",
"searchQuery": "最大搜索结果数",
"searchTip": "DuckDuckGo 搜索接入点,如不填写自动使用 WebPilot 和 New Bing 逆向进行搜索功能。\nDuckDuckGo API 项目搭建:[duckduckgo-api](https://github.com/binjie09/duckduckgo-api)。"
"searchTip": "DuckDuckGo 搜索接入点,如不填写自动使用 WebPilot 和 New Bing 逆向进行搜索功能(速度较慢)\nDuckDuckGo API 项目搭建:[duckduckgo-api](https://github.com/binjie09/duckduckgo-api)。"
}
},
"mask": {
Expand Down
11 changes: 10 additions & 1 deletion app/src/resources/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,16 @@
"searchQuery": "Max Search Results",
"searchTip": "DuckDuckGo search endpoint, if not filled in, use WebPilot and New Bing reverse search function by default.\nDuckDuckGo API project build: [duckduckgo-api](https://github.com/binjie09/duckduckgo-api).",
"mailFrom": "Sender",
"test": "Test outgoing"
"test": "Test outgoing",
"updateRoot": "Change Root Password",
"updateRootTip": "Please proceed with caution, after changing the root password, you will need to log in again.",
"updateRootPlaceholder": "Please enter a new root password",
"updateRootRepeatPlaceholder": "Please enter the new root password again",
"title": "Site name",
"titleTip": "Site name to display in the site title, leave blank for default",
"logo": "Site Logo",
"logoTip": "Link to the site logo to display in the site title, leave blank for default (e.g. {{logo}})",
"backendPlaceholder": "Backend callback domain name, empty by default, required for accepting callbacks (e.g. {{backend}})"
},
"user": "User Management",
"invitation-code": "Invitation Code",
Expand Down
11 changes: 10 additions & 1 deletion app/src/resources/i18n/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,16 @@
"searchQuery": "検索結果の最大数",
"searchTip": "DuckDuckGoは、入力せずにWebPilotやNew Bing Reverse Searchなどのアクセスポイントを自動的に検索します。\\ nDuckDuckGo APIプロジェクトビルド:[ duckduckgo - api ]( https://github.com/binjie09/duckduckgo-api )。",
"mailFrom": "発信元",
"test": "テスト送信"
"test": "テスト送信",
"updateRoot": "ルートパスワードの変更",
"updateRootTip": "ルートパスワードを変更した後、再度ログインする必要がありますので、慎重に進んでください。",
"updateRootPlaceholder": "新しいrootパスワードを入力してください",
"updateRootRepeatPlaceholder": "新しいrootパスワードをもう一度入力してください",
"title": "サイト名",
"titleTip": "サイトタイトルに表示するサイト名、デフォルトの場合は空白のままにします",
"logo": "サイトロゴ",
"logoTip": "サイトタイトルに表示するサイトロゴへのリンク、デフォルトの場合は空白のままにします(例:{{ logo }})",
"backendPlaceholder": "バックエンドコールバックドメイン名、デフォルトで空、コールバックを受け入れるために必要(例:{{ backend }})"
},
"user": "ユーザー管理",
"invitation-code": "招待コード",
Expand Down
11 changes: 10 additions & 1 deletion app/src/resources/i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,16 @@
"searchQuery": "Максимальное количество результатов поиска",
"searchTip": "Конечная точка поиска DuckDuckGo, если она не заполнена, по умолчанию используется функция обратного поиска WebPilot и New Bing.\nСборка проекта DuckDuckGo API: [duckduckgo-api](https://github.com/binjie09/duckduckgo-api).",
"mailFrom": "От",
"test": "Тест исходящий"
"test": "Тест исходящий",
"updateRoot": "Изменить корневой пароль",
"updateRootTip": "Пожалуйста, соблюдайте осторожность, после смены пароля root вам нужно будет снова войти в систему.",
"updateRootPlaceholder": "Введите новый пароль root",
"updateRootRepeatPlaceholder": "Введите новый пароль root еще раз",
"title": "Название сайта",
"titleTip": "Название сайта для отображения в заголовке сайта, оставьте пустым по умолчанию",
"logo": "Логотип сайта",
"logoTip": "Ссылка на логотип сайта для отображения в заголовке сайта, оставьте поле пустым по умолчанию (например, {{logo}})",
"backendPlaceholder": "Имя домена обратного вызова Backend, пустое по умолчанию, требуется для приема обратных вызовов (например, {{backend}})"
},
"user": "Управление пользователями",
"invitation-code": "Код приглашения",
Expand Down
8 changes: 5 additions & 3 deletions app/src/routes/Auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import router from "@/router.tsx";
import { useTranslation } from "react-i18next";
import { getQueryParam } from "@/utils/path.ts";
import { setMemory } from "@/utils/memory.ts";
import { useDeeptrain } from "@/utils/env.ts";
import { appLogo, appName, useDeeptrain } from "@/utils/env.ts";
import { Card, CardContent } from "@/components/ui/card.tsx";
import { goAuth } from "@/utils/app.ts";
import { Label } from "@/components/ui/label.tsx";
Expand Down Expand Up @@ -132,8 +132,10 @@ function Login() {

return (
<div className={`auth-container`}>
<img className={`logo`} src="/favicon.ico" alt="" />
<div className={`title`}>{t("login")} Chat Nio</div>
<img className={`logo`} src={appLogo} alt="" />
<div className={`title`}>
{t("login")} {appName}
</div>
<Card className={`auth-card`}>
<CardContent className={`pb-0`}>
<div className={`auth-wrapper`}>
Expand Down
3 changes: 2 additions & 1 deletion app/src/routes/Forgot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Require, {
import { Input } from "@/components/ui/input.tsx";
import { Button } from "@/components/ui/button.tsx";
import TickButton from "@/components/TickButton.tsx";
import { appLogo } from "@/utils/env.ts";

function Forgot() {
const { t } = useTranslation();
Expand Down Expand Up @@ -57,7 +58,7 @@ function Forgot() {

return (
<div className={`auth-container`}>
<img className={`logo`} src="/favicon.ico" alt="" />
<img className={`logo`} src={appLogo} alt="" />
<div className={`title`}>{t("auth.reset-password")}</div>
<Card className={`auth-card`}>
<CardContent className={`pb-0`}>
Expand Down
3 changes: 2 additions & 1 deletion app/src/routes/Generation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useToast } from "@/components/ui/use-toast.ts";
import { handleGenerationData } from "@/utils/processor.ts";
import { selectModel } from "@/store/chat.ts";
import ModelFinder from "@/components/home/ModelFinder.tsx";
import { appLogo } from "@/utils/env.ts";

type WrapperProps = {
onSend?: (value: string, model: string) => boolean;
Expand Down Expand Up @@ -127,7 +128,7 @@ function Wrapper({ onSend }: WrapperProps) {
</div>
) : (
<div className={`product`}>
<img src={`/favicon.ico`} alt={""} />
<img src={appLogo} alt={""} />
AI Code Generator
</div>
)}
Expand Down
7 changes: 5 additions & 2 deletions app/src/routes/Register.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useToast } from "@/components/ui/use-toast.ts";
import TickButton from "@/components/TickButton.tsx";
import { validateToken } from "@/store/auth.ts";
import { useDispatch } from "react-redux";
import { appLogo, appName } from "@/utils/env.ts";

type CompProps = {
form: RegisterForm;
Expand Down Expand Up @@ -225,8 +226,10 @@ function Register() {

return (
<div className={`auth-container`}>
<img className={`logo`} src="/favicon.ico" alt="" />
<div className={`title`}>{t("register")} Chat Nio</div>
<img className={`logo`} src={appLogo} alt="" />
<div className={`title`}>
{t("register")} {appName}
</div>
<Card className={`auth-card`}>
<CardContent className={`pb-0`}>
{!next ? (
Expand Down
Loading

0 comments on commit 4efd0e8

Please sign in to comment.