mirror of
https://github.com/coaidev/coai.git
synced 2025-05-19 21:10:18 +09:00
feat: add custom website title and logo feature; support update root password in admin system settings
This commit is contained in:
parent
89e5e13f97
commit
4efd0e8928
@ -29,6 +29,10 @@ type SubscriptionOperationForm struct {
|
|||||||
Month int64 `json:"month"`
|
Month int64 `json:"month"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateRootPasswordForm struct {
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
func InfoAPI(c *gin.Context) {
|
func InfoAPI(c *gin.Context) {
|
||||||
db := utils.GetDBFromContext(c)
|
db := utils.GetDBFromContext(c)
|
||||||
cache := utils.GetCacheFromContext(c)
|
cache := utils.GetCacheFromContext(c)
|
||||||
@ -161,3 +165,29 @@ func UserSubscriptionAPI(c *gin.Context) {
|
|||||||
"status": true,
|
"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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -23,4 +23,5 @@ func Register(app *gin.RouterGroup) {
|
|||||||
app.GET("/admin/user/list", UserPaginationAPI)
|
app.GET("/admin/user/list", UserPaginationAPI)
|
||||||
app.POST("/admin/user/quota", UserQuotaAPI)
|
app.POST("/admin/user/quota", UserQuotaAPI)
|
||||||
app.POST("/admin/user/subscription", UserSubscriptionAPI)
|
app.POST("/admin/user/subscription", UserSubscriptionAPI)
|
||||||
|
app.POST("/admin/user/root", UpdateRootPasswordAPI)
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,12 @@ package admin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"chat/utils"
|
"chat/utils"
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
"math"
|
"math"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -109,3 +113,19 @@ func SubscriptionOperation(db *sql.DB, id int64, month int64) error {
|
|||||||
|
|
||||||
return err
|
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
app/src/admin/api/info.ts
Normal file
24
app/src/admin/api/info.ts
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
}
|
@ -3,6 +3,8 @@ import { getErrorMessage } from "@/utils/base.ts";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
export type GeneralState = {
|
export type GeneralState = {
|
||||||
|
title: string;
|
||||||
|
logo: string;
|
||||||
backend: string;
|
backend: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -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 = {
|
export const initialSystemState: SystemProps = {
|
||||||
general: {
|
general: {
|
||||||
|
logo: "",
|
||||||
|
title: "",
|
||||||
backend: "",
|
backend: "",
|
||||||
},
|
},
|
||||||
mail: {
|
mail: {
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
.logo {
|
.logo {
|
||||||
width: 4rem;
|
width: 4rem;
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
margin: 2px;
|
margin: 2px;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
border-radius: var(--radius);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,13 +234,18 @@ input[type="number"] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.paragraph-space {
|
||||||
|
width: 100%;
|
||||||
|
height: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.paragraph-footer {
|
.paragraph-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
margin-right: 1rem;
|
margin-right: 0.5rem;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
@ -78,6 +78,10 @@ export function ParagraphDescription({ children }: { children: string }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ParagraphSpace() {
|
||||||
|
return <div className={`paragraph-space`} />;
|
||||||
|
}
|
||||||
|
|
||||||
function ParagraphFooter({ children }: { children: React.ReactNode }) {
|
function ParagraphFooter({ children }: { children: React.ReactNode }) {
|
||||||
return <div className={`paragraph-footer`}>{children}</div>;
|
return <div className={`paragraph-footer`}>{children}</div>;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import MenuBar from "./MenuBar.tsx";
|
|||||||
import { getMemory } from "@/utils/memory.ts";
|
import { getMemory } from "@/utils/memory.ts";
|
||||||
import { goAuth } from "@/utils/app.ts";
|
import { goAuth } from "@/utils/app.ts";
|
||||||
import Avatar from "@/components/Avatar.tsx";
|
import Avatar from "@/components/Avatar.tsx";
|
||||||
|
import { appLogo } from "@/utils/env.ts";
|
||||||
|
|
||||||
function NavMenu() {
|
function NavMenu() {
|
||||||
const username = useSelector(selectUsername);
|
const username = useSelector(selectUsername);
|
||||||
@ -53,7 +54,7 @@ function NavBar() {
|
|||||||
</Button>
|
</Button>
|
||||||
<img
|
<img
|
||||||
className={`logo`}
|
className={`logo`}
|
||||||
src="/favicon.ico"
|
src={appLogo}
|
||||||
alt=""
|
alt=""
|
||||||
onClick={() => router.navigate("/")}
|
onClick={() => router.navigate("/")}
|
||||||
/>
|
/>
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
import { getMemory } from "@/utils/memory.ts";
|
import { getMemory } from "@/utils/memory.ts";
|
||||||
import { Compass, Image, Newspaper } from "lucide-react";
|
import { Compass, Image, Newspaper } from "lucide-react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import { syncSiteInfo } from "@/admin/api/info.ts";
|
||||||
|
|
||||||
export const version = "3.8.0";
|
export const version = "3.8.0";
|
||||||
export const dev: boolean = getDev();
|
export const dev: boolean = getDev();
|
||||||
@ -524,3 +525,5 @@ export function login() {
|
|||||||
axios.defaults.baseURL = rest_api;
|
axios.defaults.baseURL = rest_api;
|
||||||
axios.defaults.headers.post["Content-Type"] = "application/json";
|
axios.defaults.headers.post["Content-Type"] = "application/json";
|
||||||
axios.defaults.headers.common["Authorization"] = getMemory(tokenField);
|
axios.defaults.headers.common["Authorization"] = getMemory(tokenField);
|
||||||
|
|
||||||
|
syncSiteInfo();
|
||||||
|
@ -464,9 +464,18 @@
|
|||||||
"search": "联网搜索",
|
"search": "联网搜索",
|
||||||
"mail": "SMTP 发件设置",
|
"mail": "SMTP 发件设置",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
|
"updateRoot": "修改 Root 密码",
|
||||||
|
"updateRootTip": "请谨慎操作,修改 Root 密码后,您需要重新登录。",
|
||||||
|
"updateRootPlaceholder": "请输入新的 Root 密码",
|
||||||
|
"updateRootRepeatPlaceholder": "请再次输入新的 Root 密码",
|
||||||
"test": "测试发件",
|
"test": "测试发件",
|
||||||
|
"title": "网站名称",
|
||||||
|
"titleTip": "网站名称,用于显示在网站标题,留空默认",
|
||||||
|
"logo": "网站 Logo",
|
||||||
|
"logoTip": "网站 Logo 的链接,用于显示在网站标题,留空默认 (如 {{logo}})",
|
||||||
"backend": "后端域名",
|
"backend": "后端域名",
|
||||||
"backendTip": "后端回调域名(docker 安装默认路径为 /api),接收回调参数。",
|
"backendTip": "后端回调域名(docker 安装默认路径为 /api),接收回调参数。",
|
||||||
|
"backendPlaceholder": "后端回调域名,默认为空,接受回调必填(如 {{backend}})",
|
||||||
"mailHost": "发件域名",
|
"mailHost": "发件域名",
|
||||||
"mailPort": "SMTP 端口",
|
"mailPort": "SMTP 端口",
|
||||||
"mailUser": "用户名",
|
"mailUser": "用户名",
|
||||||
@ -474,7 +483,7 @@
|
|||||||
"mailFrom": "发件人",
|
"mailFrom": "发件人",
|
||||||
"searchEndpoint": "搜索接入点",
|
"searchEndpoint": "搜索接入点",
|
||||||
"searchQuery": "最大搜索结果数",
|
"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": {
|
"mask": {
|
||||||
|
@ -425,7 +425,16 @@
|
|||||||
"searchQuery": "Max Search Results",
|
"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).",
|
"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",
|
"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",
|
"user": "User Management",
|
||||||
"invitation-code": "Invitation Code",
|
"invitation-code": "Invitation Code",
|
||||||
|
@ -425,7 +425,16 @@
|
|||||||
"searchQuery": "検索結果の最大数",
|
"searchQuery": "検索結果の最大数",
|
||||||
"searchTip": "DuckDuckGoは、入力せずにWebPilotやNew Bing Reverse Searchなどのアクセスポイントを自動的に検索します。\\ nDuckDuckGo APIプロジェクトビルド:[ duckduckgo - api ]( https://github.com/binjie09/duckduckgo-api )。",
|
"searchTip": "DuckDuckGoは、入力せずにWebPilotやNew Bing Reverse Searchなどのアクセスポイントを自動的に検索します。\\ nDuckDuckGo APIプロジェクトビルド:[ duckduckgo - api ]( https://github.com/binjie09/duckduckgo-api )。",
|
||||||
"mailFrom": "発信元",
|
"mailFrom": "発信元",
|
||||||
"test": "テスト送信"
|
"test": "テスト送信",
|
||||||
|
"updateRoot": "ルートパスワードの変更",
|
||||||
|
"updateRootTip": "ルートパスワードを変更した後、再度ログインする必要がありますので、慎重に進んでください。",
|
||||||
|
"updateRootPlaceholder": "新しいrootパスワードを入力してください",
|
||||||
|
"updateRootRepeatPlaceholder": "新しいrootパスワードをもう一度入力してください",
|
||||||
|
"title": "サイト名",
|
||||||
|
"titleTip": "サイトタイトルに表示するサイト名、デフォルトの場合は空白のままにします",
|
||||||
|
"logo": "サイトロゴ",
|
||||||
|
"logoTip": "サイトタイトルに表示するサイトロゴへのリンク、デフォルトの場合は空白のままにします(例:{{ logo }})",
|
||||||
|
"backendPlaceholder": "バックエンドコールバックドメイン名、デフォルトで空、コールバックを受け入れるために必要(例:{{ backend }})"
|
||||||
},
|
},
|
||||||
"user": "ユーザー管理",
|
"user": "ユーザー管理",
|
||||||
"invitation-code": "招待コード",
|
"invitation-code": "招待コード",
|
||||||
|
@ -425,7 +425,16 @@
|
|||||||
"searchQuery": "Максимальное количество результатов поиска",
|
"searchQuery": "Максимальное количество результатов поиска",
|
||||||
"searchTip": "Конечная точка поиска DuckDuckGo, если она не заполнена, по умолчанию используется функция обратного поиска WebPilot и New Bing.\nСборка проекта DuckDuckGo API: [duckduckgo-api](https://github.com/binjie09/duckduckgo-api).",
|
"searchTip": "Конечная точка поиска DuckDuckGo, если она не заполнена, по умолчанию используется функция обратного поиска WebPilot и New Bing.\nСборка проекта DuckDuckGo API: [duckduckgo-api](https://github.com/binjie09/duckduckgo-api).",
|
||||||
"mailFrom": "От",
|
"mailFrom": "От",
|
||||||
"test": "Тест исходящий"
|
"test": "Тест исходящий",
|
||||||
|
"updateRoot": "Изменить корневой пароль",
|
||||||
|
"updateRootTip": "Пожалуйста, соблюдайте осторожность, после смены пароля root вам нужно будет снова войти в систему.",
|
||||||
|
"updateRootPlaceholder": "Введите новый пароль root",
|
||||||
|
"updateRootRepeatPlaceholder": "Введите новый пароль root еще раз",
|
||||||
|
"title": "Название сайта",
|
||||||
|
"titleTip": "Название сайта для отображения в заголовке сайта, оставьте пустым по умолчанию",
|
||||||
|
"logo": "Логотип сайта",
|
||||||
|
"logoTip": "Ссылка на логотип сайта для отображения в заголовке сайта, оставьте поле пустым по умолчанию (например, {{logo}})",
|
||||||
|
"backendPlaceholder": "Имя домена обратного вызова Backend, пустое по умолчанию, требуется для приема обратных вызовов (например, {{backend}})"
|
||||||
},
|
},
|
||||||
"user": "Управление пользователями",
|
"user": "Управление пользователями",
|
||||||
"invitation-code": "Код приглашения",
|
"invitation-code": "Код приглашения",
|
||||||
|
@ -10,7 +10,7 @@ import router from "@/router.tsx";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { getQueryParam } from "@/utils/path.ts";
|
import { getQueryParam } from "@/utils/path.ts";
|
||||||
import { setMemory } from "@/utils/memory.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 { Card, CardContent } from "@/components/ui/card.tsx";
|
||||||
import { goAuth } from "@/utils/app.ts";
|
import { goAuth } from "@/utils/app.ts";
|
||||||
import { Label } from "@/components/ui/label.tsx";
|
import { Label } from "@/components/ui/label.tsx";
|
||||||
@ -132,8 +132,10 @@ function Login() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`auth-container`}>
|
<div className={`auth-container`}>
|
||||||
<img className={`logo`} src="/favicon.ico" alt="" />
|
<img className={`logo`} src={appLogo} alt="" />
|
||||||
<div className={`title`}>{t("login")} Chat Nio</div>
|
<div className={`title`}>
|
||||||
|
{t("login")} {appName}
|
||||||
|
</div>
|
||||||
<Card className={`auth-card`}>
|
<Card className={`auth-card`}>
|
||||||
<CardContent className={`pb-0`}>
|
<CardContent className={`pb-0`}>
|
||||||
<div className={`auth-wrapper`}>
|
<div className={`auth-wrapper`}>
|
||||||
|
@ -14,6 +14,7 @@ import Require, {
|
|||||||
import { Input } from "@/components/ui/input.tsx";
|
import { Input } from "@/components/ui/input.tsx";
|
||||||
import { Button } from "@/components/ui/button.tsx";
|
import { Button } from "@/components/ui/button.tsx";
|
||||||
import TickButton from "@/components/TickButton.tsx";
|
import TickButton from "@/components/TickButton.tsx";
|
||||||
|
import { appLogo } from "@/utils/env.ts";
|
||||||
|
|
||||||
function Forgot() {
|
function Forgot() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -57,7 +58,7 @@ function Forgot() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`auth-container`}>
|
<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>
|
<div className={`title`}>{t("auth.reset-password")}</div>
|
||||||
<Card className={`auth-card`}>
|
<Card className={`auth-card`}>
|
||||||
<CardContent className={`pb-0`}>
|
<CardContent className={`pb-0`}>
|
||||||
|
@ -12,6 +12,7 @@ import { useToast } from "@/components/ui/use-toast.ts";
|
|||||||
import { handleGenerationData } from "@/utils/processor.ts";
|
import { handleGenerationData } from "@/utils/processor.ts";
|
||||||
import { selectModel } from "@/store/chat.ts";
|
import { selectModel } from "@/store/chat.ts";
|
||||||
import ModelFinder from "@/components/home/ModelFinder.tsx";
|
import ModelFinder from "@/components/home/ModelFinder.tsx";
|
||||||
|
import { appLogo } from "@/utils/env.ts";
|
||||||
|
|
||||||
type WrapperProps = {
|
type WrapperProps = {
|
||||||
onSend?: (value: string, model: string) => boolean;
|
onSend?: (value: string, model: string) => boolean;
|
||||||
@ -127,7 +128,7 @@ function Wrapper({ onSend }: WrapperProps) {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className={`product`}>
|
<div className={`product`}>
|
||||||
<img src={`/favicon.ico`} alt={""} />
|
<img src={appLogo} alt={""} />
|
||||||
AI Code Generator
|
AI Code Generator
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -16,6 +16,7 @@ import { useToast } from "@/components/ui/use-toast.ts";
|
|||||||
import TickButton from "@/components/TickButton.tsx";
|
import TickButton from "@/components/TickButton.tsx";
|
||||||
import { validateToken } from "@/store/auth.ts";
|
import { validateToken } from "@/store/auth.ts";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
|
import { appLogo, appName } from "@/utils/env.ts";
|
||||||
|
|
||||||
type CompProps = {
|
type CompProps = {
|
||||||
form: RegisterForm;
|
form: RegisterForm;
|
||||||
@ -225,8 +226,10 @@ function Register() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`auth-container`}>
|
<div className={`auth-container`}>
|
||||||
<img className={`logo`} src="/favicon.ico" alt="" />
|
<img className={`logo`} src={appLogo} alt="" />
|
||||||
<div className={`title`}>{t("register")} Chat Nio</div>
|
<div className={`title`}>
|
||||||
|
{t("register")} {appName}
|
||||||
|
</div>
|
||||||
<Card className={`auth-card`}>
|
<Card className={`auth-card`}>
|
||||||
<CardContent className={`pb-0`}>
|
<CardContent className={`pb-0`}>
|
||||||
{!next ? (
|
{!next ? (
|
||||||
|
@ -24,6 +24,7 @@ import {
|
|||||||
SearchState,
|
SearchState,
|
||||||
setConfig,
|
setConfig,
|
||||||
SystemProps,
|
SystemProps,
|
||||||
|
updateRootPassword,
|
||||||
} from "@/admin/api/system.ts";
|
} from "@/admin/api/system.ts";
|
||||||
import { useEffectAsync } from "@/utils/hook.ts";
|
import { useEffectAsync } from "@/utils/hook.ts";
|
||||||
import { toastState } from "@/admin/utils.ts";
|
import { toastState } from "@/admin/utils.ts";
|
||||||
@ -38,6 +39,7 @@ import {
|
|||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} 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";
|
||||||
|
|
||||||
type CompProps<T> = {
|
type CompProps<T> = {
|
||||||
data: T;
|
data: T;
|
||||||
@ -45,6 +47,83 @@ type CompProps<T> = {
|
|||||||
onChange: (doToast?: boolean) => Promise<void>;
|
onChange: (doToast?: boolean) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function RootDialog() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
const [password, setPassword] = useState<string>("");
|
||||||
|
const [repeat, setRepeat] = useState<string>("");
|
||||||
|
|
||||||
|
const onPost = async () => {
|
||||||
|
const res = await updateRootPassword(password);
|
||||||
|
toastState(toast, t, res, true);
|
||||||
|
if (res.status) {
|
||||||
|
setPassword("");
|
||||||
|
setRepeat("");
|
||||||
|
setOpen(false);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button variant={`outline`} size={`sm`}>
|
||||||
|
{t("admin.system.updateRoot")}
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{t("admin.system.updateRoot")}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
<div className={`mb-4 select-none`}>
|
||||||
|
{t("admin.system.updateRootTip")}
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
className={`mb-2`}
|
||||||
|
type={`password`}
|
||||||
|
placeholder={t("admin.system.updateRootPlaceholder")}
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
type={`password`}
|
||||||
|
placeholder={t("admin.system.updateRootRepeatPlaceholder")}
|
||||||
|
value={repeat}
|
||||||
|
onChange={(e) => setRepeat(e.target.value)}
|
||||||
|
/>
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant={`outline`}
|
||||||
|
onClick={() => {
|
||||||
|
setPassword("");
|
||||||
|
setRepeat("");
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("admin.cancel")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={`default`}
|
||||||
|
loading={true}
|
||||||
|
onClick={onPost}
|
||||||
|
disabled={
|
||||||
|
password.trim().length === 0 || password.trim() !== repeat.trim()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("admin.confirm")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function General({ data, dispatch, onChange }: CompProps<GeneralState>) {
|
function General({ data, dispatch, onChange }: CompProps<GeneralState>) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -55,7 +134,37 @@ function General({ data, dispatch, onChange }: CompProps<GeneralState>) {
|
|||||||
isCollapsed={true}
|
isCollapsed={true}
|
||||||
>
|
>
|
||||||
<ParagraphItem>
|
<ParagraphItem>
|
||||||
<Label>{t("admin.system.backend")}</Label>
|
<Label>{t("admin.system.title")}</Label>
|
||||||
|
<Input
|
||||||
|
value={data.title}
|
||||||
|
onChange={(e) =>
|
||||||
|
dispatch({
|
||||||
|
type: "update:general.title",
|
||||||
|
value: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
placeholder={t("admin.system.titleTip")}
|
||||||
|
/>
|
||||||
|
</ParagraphItem>
|
||||||
|
<ParagraphItem>
|
||||||
|
<Label>{t("admin.system.logo")}</Label>
|
||||||
|
<Input
|
||||||
|
value={data.logo}
|
||||||
|
onChange={(e) =>
|
||||||
|
dispatch({
|
||||||
|
type: "update:general.logo",
|
||||||
|
value: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
placeholder={t("admin.system.logoTip", {
|
||||||
|
logo: `${window.location.protocol}//${window.location.host}/favicon.ico`,
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</ParagraphItem>
|
||||||
|
<ParagraphItem>
|
||||||
|
<Label>
|
||||||
|
<Require /> {t("admin.system.backend")}
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
value={data.backend}
|
value={data.backend}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@ -64,7 +173,9 @@ function General({ data, dispatch, onChange }: CompProps<GeneralState>) {
|
|||||||
value: e.target.value,
|
value: e.target.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
placeholder={`${window.location.protocol}//${window.location.host}/api`}
|
placeholder={t("admin.system.backendPlaceholder", {
|
||||||
|
backend: `${window.location.protocol}//${window.location.host}/api`,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
</ParagraphItem>
|
</ParagraphItem>
|
||||||
<ParagraphDescription>
|
<ParagraphDescription>
|
||||||
@ -72,7 +183,12 @@ function General({ data, dispatch, onChange }: CompProps<GeneralState>) {
|
|||||||
</ParagraphDescription>
|
</ParagraphDescription>
|
||||||
<ParagraphFooter>
|
<ParagraphFooter>
|
||||||
<div className={`grow`} />
|
<div className={`grow`} />
|
||||||
<Button size={`sm`} loading={true} onClick={async () => await onChange()}>
|
<RootDialog />
|
||||||
|
<Button
|
||||||
|
size={`sm`}
|
||||||
|
loading={true}
|
||||||
|
onClick={async () => await onChange()}
|
||||||
|
>
|
||||||
{t("admin.system.save")}
|
{t("admin.system.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</ParagraphFooter>
|
</ParagraphFooter>
|
||||||
@ -102,7 +218,9 @@ function Mail({ data, dispatch, onChange }: CompProps<MailState>) {
|
|||||||
isCollapsed={true}
|
isCollapsed={true}
|
||||||
>
|
>
|
||||||
<ParagraphItem>
|
<ParagraphItem>
|
||||||
<Label>{t("admin.system.mailHost")}</Label>
|
<Label>
|
||||||
|
<Require /> {t("admin.system.mailHost")}
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
value={data.host}
|
value={data.host}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@ -115,7 +233,9 @@ function Mail({ data, dispatch, onChange }: CompProps<MailState>) {
|
|||||||
/>
|
/>
|
||||||
</ParagraphItem>
|
</ParagraphItem>
|
||||||
<ParagraphItem>
|
<ParagraphItem>
|
||||||
<Label>{t("admin.system.mailPort")}</Label>
|
<Label>
|
||||||
|
<Require /> {t("admin.system.mailPort")}
|
||||||
|
</Label>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={data.port}
|
value={data.port}
|
||||||
onValueChange={(value) =>
|
onValueChange={(value) =>
|
||||||
@ -127,7 +247,9 @@ function Mail({ data, dispatch, onChange }: CompProps<MailState>) {
|
|||||||
/>
|
/>
|
||||||
</ParagraphItem>
|
</ParagraphItem>
|
||||||
<ParagraphItem>
|
<ParagraphItem>
|
||||||
<Label>{t("admin.system.mailUser")}</Label>
|
<Label>
|
||||||
|
<Require /> {t("admin.system.mailUser")}
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
value={data.username}
|
value={data.username}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@ -140,7 +262,9 @@ function Mail({ data, dispatch, onChange }: CompProps<MailState>) {
|
|||||||
/>
|
/>
|
||||||
</ParagraphItem>
|
</ParagraphItem>
|
||||||
<ParagraphItem>
|
<ParagraphItem>
|
||||||
<Label>{t("admin.system.mailPass")}</Label>
|
<Label>
|
||||||
|
<Require /> {t("admin.system.mailPass")}
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
value={data.password}
|
value={data.password}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@ -153,7 +277,9 @@ function Mail({ data, dispatch, onChange }: CompProps<MailState>) {
|
|||||||
/>
|
/>
|
||||||
</ParagraphItem>
|
</ParagraphItem>
|
||||||
<ParagraphItem>
|
<ParagraphItem>
|
||||||
<Label>{t("admin.system.mailFrom")}</Label>
|
<Label>
|
||||||
|
<Require /> {t("admin.system.mailFrom")}
|
||||||
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
value={data.from}
|
value={data.from}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
@ -200,7 +326,11 @@ function Mail({ data, dispatch, onChange }: CompProps<MailState>) {
|
|||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<Button size={`sm`} loading={true} onClick={async () => await onChange()}>
|
<Button
|
||||||
|
size={`sm`}
|
||||||
|
loading={true}
|
||||||
|
onClick={async () => await onChange()}
|
||||||
|
>
|
||||||
{t("admin.system.save")}
|
{t("admin.system.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</ParagraphFooter>
|
</ParagraphFooter>
|
||||||
@ -245,7 +375,11 @@ function Search({ data, dispatch, onChange }: CompProps<SearchState>) {
|
|||||||
<ParagraphDescription>{t("admin.system.searchTip")}</ParagraphDescription>
|
<ParagraphDescription>{t("admin.system.searchTip")}</ParagraphDescription>
|
||||||
<ParagraphFooter>
|
<ParagraphFooter>
|
||||||
<div className={`grow`} />
|
<div className={`grow`} />
|
||||||
<Button size={`sm`} loading={true} onClick={async () => await onChange()}>
|
<Button
|
||||||
|
size={`sm`}
|
||||||
|
loading={true}
|
||||||
|
onClick={async () => await onChange()}
|
||||||
|
>
|
||||||
{t("admin.system.save")}
|
{t("admin.system.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</ParagraphFooter>
|
</ParagraphFooter>
|
||||||
|
@ -220,3 +220,15 @@ export function scrollDown(el: HTMLElement | null) {
|
|||||||
behavior: "smooth",
|
behavior: "smooth",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateFavicon(url: string) {
|
||||||
|
/**
|
||||||
|
* Update favicon in the link element from head
|
||||||
|
* @param url Favicon url
|
||||||
|
* @example
|
||||||
|
* updateFavicon("https://example.com/favicon.ico");
|
||||||
|
*/
|
||||||
|
|
||||||
|
const link = document.querySelector("link[rel*='icon']");
|
||||||
|
return link && link.setAttribute("href", url);
|
||||||
|
}
|
||||||
|
@ -1,3 +1,13 @@
|
|||||||
|
import { updateFavicon } from "@/utils/dom.ts";
|
||||||
|
|
||||||
|
export let appName =
|
||||||
|
localStorage.getItem("app_name") ||
|
||||||
|
import.meta.env.VITE_APP_NAME ||
|
||||||
|
"Chat Nio";
|
||||||
|
export let appLogo =
|
||||||
|
localStorage.getItem("app_logo") ||
|
||||||
|
import.meta.env.VITE_APP_LOGO ||
|
||||||
|
"/favicon.ico";
|
||||||
export const useDeeptrain = !!import.meta.env.VITE_USE_DEEPTRAIN;
|
export const useDeeptrain = !!import.meta.env.VITE_USE_DEEPTRAIN;
|
||||||
export const backendEndpoint = import.meta.env.VITE_BACKEND_ENDPOINT || "/api";
|
export const backendEndpoint = import.meta.env.VITE_BACKEND_ENDPOINT || "/api";
|
||||||
export const blobEndpoint =
|
export const blobEndpoint =
|
||||||
@ -10,6 +20,9 @@ export const deeptrainAppName = import.meta.env.VITE_DEEPTRAIN_APP || "chatnio";
|
|||||||
export const deeptrainApiEndpoint =
|
export const deeptrainApiEndpoint =
|
||||||
import.meta.env.VITE_DEEPTRAIN_API_ENDPOINT || "https://api.deeptrain.net";
|
import.meta.env.VITE_DEEPTRAIN_API_ENDPOINT || "https://api.deeptrain.net";
|
||||||
|
|
||||||
|
document.title = appName;
|
||||||
|
updateFavicon(appLogo);
|
||||||
|
|
||||||
export function getDev(): boolean {
|
export function getDev(): boolean {
|
||||||
/**
|
/**
|
||||||
* return if the current environment is development
|
* return if the current environment is development
|
||||||
@ -47,3 +60,29 @@ export function getTokenField(deploy: boolean): string {
|
|||||||
*/
|
*/
|
||||||
return deploy ? "token" : "token-dev";
|
return deploy ? "token" : "token-dev";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setAppName(name: string): void {
|
||||||
|
/**
|
||||||
|
* set the app name in localStorage
|
||||||
|
*/
|
||||||
|
name = name.trim();
|
||||||
|
if (name.length === 0) return;
|
||||||
|
|
||||||
|
localStorage.setItem("app_name", name);
|
||||||
|
appName = name;
|
||||||
|
|
||||||
|
document.title = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setAppLogo(logo: string): void {
|
||||||
|
/**
|
||||||
|
* set the app logo in localStorage
|
||||||
|
*/
|
||||||
|
logo = logo.trim();
|
||||||
|
if (logo.length === 0) return;
|
||||||
|
|
||||||
|
localStorage.setItem("app_logo", logo);
|
||||||
|
appLogo = logo;
|
||||||
|
|
||||||
|
updateFavicon(logo);
|
||||||
|
}
|
||||||
|
22
auth/auth.go
22
auth/auth.go
@ -4,6 +4,7 @@ import (
|
|||||||
"chat/channel"
|
"chat/channel"
|
||||||
"chat/globals"
|
"chat/globals"
|
||||||
"chat/utils"
|
"chat/utils"
|
||||||
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -244,14 +245,31 @@ func Reset(c *gin.Context, form ResetForm) error {
|
|||||||
return errors.New("invalid email verification code")
|
return errors.New("invalid email verification code")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user := GetUserByEmail(db, email)
|
||||||
|
if user == nil {
|
||||||
|
return errors.New("cannot find user by email")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := user.UpdatePassword(db, cache, password); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.Del(c, fmt.Sprintf("nio:otp:%s", email))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) UpdatePassword(db *sql.DB, cache *redis.Client, password string) error {
|
||||||
hash := utils.Sha2Encrypt(password)
|
hash := utils.Sha2Encrypt(password)
|
||||||
|
|
||||||
if _, err := db.Exec(`
|
if _, err := db.Exec(`
|
||||||
UPDATE auth SET password = ? WHERE email = ?
|
UPDATE auth SET password = ? WHERE id = ?
|
||||||
`, hash, email); err != nil {
|
`, hash, u.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cache.Del(context.Background(), fmt.Sprintf("nio:user:%s", u.Username))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,22 @@ func GetUserById(db *sql.DB, id int64) *User {
|
|||||||
return &user
|
return &user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUserByName(db *sql.DB, username string) *User {
|
||||||
|
var user User
|
||||||
|
if err := db.QueryRow("SELECT id, username FROM auth WHERE username = ?", username).Scan(&user.ID, &user.Username); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &user
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserByEmail(db *sql.DB, email string) *User {
|
||||||
|
var user User
|
||||||
|
if err := db.QueryRow("SELECT id, username FROM auth WHERE email = ?", email).Scan(&user.ID, &user.Username); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &user
|
||||||
|
}
|
||||||
|
|
||||||
func GetId(db *sql.DB, user *User) int64 {
|
func GetId(db *sql.DB, user *User) int64 {
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return -1
|
return -1
|
||||||
|
@ -6,6 +6,10 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func GetInfo(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, SystemInstance.AsInfo())
|
||||||
|
}
|
||||||
|
|
||||||
func DeleteChannel(c *gin.Context) {
|
func DeleteChannel(c *gin.Context) {
|
||||||
id := c.Param("id")
|
id := c.Param("id")
|
||||||
state := ConduitInstance.DeleteChannel(utils.ParseInt(id))
|
state := ConduitInstance.DeleteChannel(utils.ParseInt(id))
|
||||||
|
@ -3,6 +3,8 @@ package channel
|
|||||||
import "github.com/gin-gonic/gin"
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
func Register(app *gin.RouterGroup) {
|
func Register(app *gin.RouterGroup) {
|
||||||
|
app.GET("/info", GetInfo)
|
||||||
|
|
||||||
app.GET("/admin/channel/list", GetChannelList)
|
app.GET("/admin/channel/list", GetChannelList)
|
||||||
app.POST("/admin/channel/create", CreateChannel)
|
app.POST("/admin/channel/create", CreateChannel)
|
||||||
app.GET("/admin/channel/get/:id", GetChannel)
|
app.GET("/admin/channel/get/:id", GetChannel)
|
||||||
|
@ -5,7 +5,14 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ApiInfo struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Logo string `json:"logo"`
|
||||||
|
}
|
||||||
|
|
||||||
type generalState struct {
|
type generalState struct {
|
||||||
|
Title string `json:"title" mapstructure:"title"`
|
||||||
|
Logo string `json:"logo" mapstructure:"logo"`
|
||||||
Backend string `json:"backend" mapstructure:"backend"`
|
Backend string `json:"backend" mapstructure:"backend"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +54,13 @@ func (c *SystemConfig) SaveConfig() error {
|
|||||||
return viper.WriteConfig()
|
return viper.WriteConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *SystemConfig) AsInfo() ApiInfo {
|
||||||
|
return ApiInfo{
|
||||||
|
Title: c.General.Title,
|
||||||
|
Logo: c.General.Logo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *SystemConfig) UpdateConfig(data *SystemConfig) error {
|
func (c *SystemConfig) UpdateConfig(data *SystemConfig) error {
|
||||||
c.General = data.General
|
c.General = data.General
|
||||||
c.Mail = data.Mail
|
c.Mail = data.Mail
|
||||||
|
25
cli/admin.go
Normal file
25
cli/admin.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"chat/admin"
|
||||||
|
"chat/connection"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UpdateRootCommand(args []string) {
|
||||||
|
db := connection.ConnectMySQL()
|
||||||
|
cache := connection.ConnectRedis()
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
outputError(errors.New("invalid arguments, please provide a new root password"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
password := args[0]
|
||||||
|
if err := admin.UpdateRootPassword(db, cache, password); err != nil {
|
||||||
|
outputError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outputInfo("root", "root password updated")
|
||||||
|
}
|
@ -10,17 +10,17 @@ func Run() bool {
|
|||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "help":
|
case "help":
|
||||||
Help()
|
Help()
|
||||||
return true
|
|
||||||
case "invite":
|
case "invite":
|
||||||
CreateInvitationCommand(param)
|
CreateInvitationCommand(param)
|
||||||
return true
|
|
||||||
case "filter":
|
case "filter":
|
||||||
FilterApiKeyCommand(param)
|
FilterApiKeyCommand(param)
|
||||||
return true
|
|
||||||
case "token":
|
case "token":
|
||||||
CreateTokenCommand(param)
|
CreateTokenCommand(param)
|
||||||
return true
|
case "root":
|
||||||
|
UpdateRootCommand(param)
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ Commands:
|
|||||||
- help
|
- help
|
||||||
- invite <type> <num> <quota>
|
- invite <type> <num> <quota>
|
||||||
- token <user-id>
|
- token <user-id>
|
||||||
|
- root <password>
|
||||||
`
|
`
|
||||||
|
|
||||||
func Help() {
|
func Help() {
|
||||||
|
@ -63,7 +63,7 @@ func GetArgString(args []string, idx int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func outputError(err error) {
|
func outputError(err error) {
|
||||||
fmt.Println(fmt.Sprintf("[cli] error: %s", err.Error()))
|
fmt.Println(fmt.Sprintf("\033[31m[cli] error: %s\033[0m", err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func outputInfo(t, msg string) {
|
func outputInfo(t, msg string) {
|
||||||
|
Loading…
Reference in New Issue
Block a user