mirror of
https://github.com/chaitin/MonkeyCode.git
synced 2026-02-04 07:43:28 +08:00
@@ -271,7 +271,7 @@ export const getGetSetting = (params: RequestParams = {}) =>
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 更新系统设置
|
||||
* @description 更新为增量更新,只传需要更新的字段
|
||||
*
|
||||
* @tags Admin
|
||||
* @name PutUpdateSetting
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
import request, { ContentType, RequestParams } from "./httpClient";
|
||||
import {
|
||||
DomainAcceptCompletionReq,
|
||||
DomainCreateSecurityScanningReq,
|
||||
DomainListSecurityScanningBriefResp,
|
||||
DomainListSecurityScanningReq,
|
||||
DomainModelListResp,
|
||||
DomainReportReq,
|
||||
WebResp,
|
||||
@@ -146,3 +149,56 @@ export const postReport = (
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 分页逻辑只支持用 next_token
|
||||
*
|
||||
* @tags OpenAIV1
|
||||
* @name GetListSecurityScanning
|
||||
* @summary 扫描任务列表
|
||||
* @request GET:/v1/security/scanning
|
||||
* @response `200` `(WebResp & {
|
||||
data?: DomainListSecurityScanningBriefResp,
|
||||
|
||||
})` OK
|
||||
*/
|
||||
|
||||
export const getListSecurityScanning = (
|
||||
param: DomainListSecurityScanningReq,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<
|
||||
WebResp & {
|
||||
data?: DomainListSecurityScanningBriefResp;
|
||||
}
|
||||
>({
|
||||
path: `/v1/security/scanning`,
|
||||
method: "GET",
|
||||
body: param,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 创建扫描任务
|
||||
*
|
||||
* @tags OpenAIV1
|
||||
* @name PostCreateSecurityScanning
|
||||
* @summary 创建扫描任务
|
||||
* @request POST:/v1/security/scanning
|
||||
* @response `200` `WebResp` OK
|
||||
*/
|
||||
|
||||
export const postCreateSecurityScanning = (
|
||||
param: DomainCreateSecurityScanningReq,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<WebResp>({
|
||||
path: `/v1/security/scanning`,
|
||||
method: "POST",
|
||||
body: param,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
82
ui/src/api/SecurityScanning.ts
Normal file
82
ui/src/api/SecurityScanning.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
* ## ##
|
||||
* ## AUTHOR: acacode ##
|
||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import request, { ContentType, RequestParams } from "./httpClient";
|
||||
import {
|
||||
DomainListSecurityScanningResp,
|
||||
DomainSecurityScanningRiskDetail,
|
||||
GetSecurityScanningDetailParams,
|
||||
GetSecurityScanningListParams,
|
||||
WebResp,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* @description 获取扫描结果
|
||||
*
|
||||
* @tags Security Scanning
|
||||
* @name GetSecurityScanningList
|
||||
* @summary 获取扫描结果
|
||||
* @request GET:/api/v1/security/scanning
|
||||
* @response `200` `(WebResp & {
|
||||
data?: DomainListSecurityScanningResp,
|
||||
|
||||
})` OK
|
||||
* @response `401` `string` Unauthorized
|
||||
*/
|
||||
|
||||
export const getSecurityScanningList = (
|
||||
query: GetSecurityScanningListParams,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<
|
||||
WebResp & {
|
||||
data?: DomainListSecurityScanningResp;
|
||||
}
|
||||
>({
|
||||
path: `/api/v1/security/scanning`,
|
||||
method: "GET",
|
||||
query: query,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 获取扫描风险详情
|
||||
*
|
||||
* @tags Security Scanning
|
||||
* @name GetSecurityScanningDetail
|
||||
* @summary 获取扫描风险详情
|
||||
* @request GET:/api/v1/security/scanning/detail
|
||||
* @response `200` `(WebResp & {
|
||||
data?: (DomainSecurityScanningRiskDetail)[],
|
||||
|
||||
})` OK
|
||||
* @response `401` `string` Unauthorized
|
||||
*/
|
||||
|
||||
export const getSecurityScanningDetail = (
|
||||
query: GetSecurityScanningDetailParams,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<
|
||||
WebResp & {
|
||||
data?: DomainSecurityScanningRiskDetail[];
|
||||
}
|
||||
>({
|
||||
path: `/api/v1/security/scanning/detail`,
|
||||
method: "GET",
|
||||
query: query,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
82
ui/src/api/UserSecurityScanning.ts
Normal file
82
ui/src/api/UserSecurityScanning.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/* eslint-disable */
|
||||
/* tslint:disable */
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
|
||||
* ## ##
|
||||
* ## AUTHOR: acacode ##
|
||||
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
|
||||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import request, { ContentType, RequestParams } from "./httpClient";
|
||||
import {
|
||||
DomainListSecurityScanningResp,
|
||||
DomainSecurityScanningRiskDetail,
|
||||
GetUserSecurityScanningDetailParams,
|
||||
GetUserSecurityScanningListParams,
|
||||
WebResp,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* @description 获取用户扫描结果
|
||||
*
|
||||
* @tags User Security Scanning
|
||||
* @name GetUserSecurityScanningList
|
||||
* @summary 获取用户扫描结果
|
||||
* @request GET:/api/v1/user/security/scanning
|
||||
* @response `200` `(WebResp & {
|
||||
data?: DomainListSecurityScanningResp,
|
||||
|
||||
})` OK
|
||||
* @response `401` `string` Unauthorized
|
||||
*/
|
||||
|
||||
export const getUserSecurityScanningList = (
|
||||
query: GetUserSecurityScanningListParams,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<
|
||||
WebResp & {
|
||||
data?: DomainListSecurityScanningResp;
|
||||
}
|
||||
>({
|
||||
path: `/api/v1/user/security/scanning`,
|
||||
method: "GET",
|
||||
query: query,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 获取用户扫描风险详情
|
||||
*
|
||||
* @tags User Security Scanning
|
||||
* @name GetUserSecurityScanningDetail
|
||||
* @summary 获取用户扫描风险详情
|
||||
* @request GET:/api/v1/user/security/scanning/detail
|
||||
* @response `200` `(WebResp & {
|
||||
data?: (DomainSecurityScanningRiskDetail)[],
|
||||
|
||||
})` OK
|
||||
* @response `401` `string` Unauthorized
|
||||
*/
|
||||
|
||||
export const getUserSecurityScanningDetail = (
|
||||
query: GetUserSecurityScanningDetailParams,
|
||||
params: RequestParams = {},
|
||||
) =>
|
||||
request<
|
||||
WebResp & {
|
||||
data?: DomainSecurityScanningRiskDetail[];
|
||||
}
|
||||
>({
|
||||
path: `/api/v1/user/security/scanning/detail`,
|
||||
method: "GET",
|
||||
query: query,
|
||||
type: ContentType.Json,
|
||||
format: "json",
|
||||
...params,
|
||||
});
|
||||
@@ -4,10 +4,12 @@ export * from './Cli'
|
||||
export * from './Dashboard'
|
||||
export * from './Model'
|
||||
export * from './OpenAiv1'
|
||||
export * from './SecurityScanning'
|
||||
export * from './User'
|
||||
export * from './UserDashboard'
|
||||
export * from './UserManage'
|
||||
export * from './UserRecord'
|
||||
export * from './UserSecurityScanning'
|
||||
export * from './WorkspaceFile'
|
||||
export * from './types'
|
||||
|
||||
|
||||
@@ -40,6 +40,47 @@ export enum ConstsUserPlatform {
|
||||
UserPlatformCustom = "custom",
|
||||
}
|
||||
|
||||
export enum ConstsSecurityScanningStatus {
|
||||
SecurityScanningStatusPending = "pending",
|
||||
SecurityScanningStatusRunning = "running",
|
||||
SecurityScanningStatusSuccess = "success",
|
||||
SecurityScanningStatusFailed = "failed",
|
||||
}
|
||||
|
||||
export enum ConstsSecurityScanningRiskLevel {
|
||||
SecurityScanningRiskLevelSevere = "severe",
|
||||
SecurityScanningRiskLevelCritical = "critical",
|
||||
SecurityScanningRiskLevelSuggest = "suggest",
|
||||
}
|
||||
|
||||
export enum ConstsSecurityScanningLanguage {
|
||||
SecurityScanningLanguageCpp = "C/C++",
|
||||
SecurityScanningLanguageJava = "Java",
|
||||
SecurityScanningLanguagePython = "Python",
|
||||
SecurityScanningLanguageJavaScript = "JavaScript",
|
||||
SecurityScanningLanguageGo = "Go",
|
||||
SecurityScanningLanguagePHP = "PHP",
|
||||
SecurityScanningLanguageCS = "C#",
|
||||
SecurityScanningLanguageSwift = "Swift",
|
||||
SecurityScanningLanguageRuby = "Ruby",
|
||||
SecurityScanningLanguageRust = "Rust",
|
||||
SecurityScanningLanguageHTML = "HTML",
|
||||
SecurityScanningLanguageObjectiveC = "Objective-C/C++",
|
||||
SecurityScanningLanguageOCaml = "OCaml",
|
||||
SecurityScanningLanguageKotlin = "Kotlin",
|
||||
SecurityScanningLanguageScala = "Scala",
|
||||
SecurityScanningLanguageSolidity = "Solidity",
|
||||
SecurityScanningLanguageCOBOL = "COBOL",
|
||||
SecurityScanningLanguageShell = "Shell",
|
||||
SecurityScanningLanguageSQL = "SQL",
|
||||
SecurityScanningLanguageFortran = "Fortran",
|
||||
SecurityScanningLanguageDart = "Dart",
|
||||
SecurityScanningLanguageGroovy = "Groovy",
|
||||
SecurityScanningLanguageLua = "Lua",
|
||||
SecurityScanningLanguageSecrets = "Secrets",
|
||||
SecurityScanningLanguageIaC = "IaC",
|
||||
}
|
||||
|
||||
export enum ConstsReportAction {
|
||||
ReportActionAccept = "accept",
|
||||
ReportActionSuggest = "suggest",
|
||||
@@ -313,6 +354,13 @@ export interface DomainCreateModelReq {
|
||||
show_name?: string;
|
||||
}
|
||||
|
||||
export interface DomainCreateSecurityScanningReq {
|
||||
/** 扫描语言 */
|
||||
language?: ConstsSecurityScanningLanguage;
|
||||
/** 项目目录 */
|
||||
workspace?: string;
|
||||
}
|
||||
|
||||
export interface DomainCreateWorkspaceFileReq {
|
||||
/** 文件内容 */
|
||||
content?: string;
|
||||
@@ -504,6 +552,41 @@ export interface DomainListLoginHistoryResp {
|
||||
total_count?: number;
|
||||
}
|
||||
|
||||
export interface DomainListSecurityScanningBriefResp {
|
||||
has_next_page?: boolean;
|
||||
items?: DomainSecurityScanningBrief[];
|
||||
next_token?: string;
|
||||
total_count?: number;
|
||||
}
|
||||
|
||||
export interface DomainListSecurityScanningReq {
|
||||
/** 作者 */
|
||||
author?: string;
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
page?: number;
|
||||
/** 项目名称 */
|
||||
project_name?: string;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface DomainListSecurityScanningResp {
|
||||
has_next_page?: boolean;
|
||||
items?: DomainSecurityScanningResult[];
|
||||
next_token?: string;
|
||||
total_count?: number;
|
||||
}
|
||||
|
||||
export interface DomainListUserResp {
|
||||
has_next_page?: boolean;
|
||||
next_token?: string;
|
||||
@@ -695,7 +778,57 @@ export interface DomainReportReq {
|
||||
user_input?: string;
|
||||
}
|
||||
|
||||
export interface DomainSecurityScanningBrief {
|
||||
/** 创建时间 */
|
||||
created_at?: number;
|
||||
/** 报告url */
|
||||
report_url?: string;
|
||||
/** 扫描状态 */
|
||||
status?: ConstsSecurityScanningStatus;
|
||||
/** 项目目录 */
|
||||
workspace?: string;
|
||||
}
|
||||
|
||||
export interface DomainSecurityScanningResult {
|
||||
/** 扫描开始时间 */
|
||||
created_at?: number;
|
||||
/** 扫描任务id */
|
||||
id?: string;
|
||||
/** 扫描任务 */
|
||||
name?: string;
|
||||
/** 项目名称 */
|
||||
project_name?: string;
|
||||
/** 风险结果 */
|
||||
risk?: DomainSecurityScanningRiskResult;
|
||||
/** 扫描状态 */
|
||||
status?: ConstsSecurityScanningStatus;
|
||||
/** 用户 */
|
||||
user?: DomainUser;
|
||||
}
|
||||
|
||||
export interface DomainSecurityScanningRiskDetail {
|
||||
/** 风险描述 */
|
||||
desc?: string;
|
||||
/** 风险文件名 */
|
||||
filename?: string;
|
||||
/** 风险id */
|
||||
id?: string;
|
||||
/** 风险等级 */
|
||||
level?: ConstsSecurityScanningRiskLevel;
|
||||
}
|
||||
|
||||
export interface DomainSecurityScanningRiskResult {
|
||||
/** 高危数 */
|
||||
critical_count?: number;
|
||||
/** 严重数 */
|
||||
severe_count?: number;
|
||||
/** 建议数 */
|
||||
suggest_count?: number;
|
||||
}
|
||||
|
||||
export interface DomainSetting {
|
||||
/** base url 配置,为了支持前置代理 */
|
||||
base_url?: string;
|
||||
/** 创建时间 */
|
||||
created_at?: number;
|
||||
/** 自定义OAuth接入 */
|
||||
@@ -829,6 +962,8 @@ export interface DomainUpdateModelReq {
|
||||
}
|
||||
|
||||
export interface DomainUpdateSettingReq {
|
||||
/** base url 配置,为了支持前置代理 */
|
||||
base_url?: string;
|
||||
/** 自定义OAuth配置 */
|
||||
custom_oauth?: DomainCustomOAuthReq;
|
||||
/** 钉钉OAuth配置 */
|
||||
@@ -1230,6 +1365,32 @@ export interface GetGetTokenUsageParams {
|
||||
model_type: "llm" | "coder" | "embedding" | "audio" | "reranker";
|
||||
}
|
||||
|
||||
export interface GetSecurityScanningListParams {
|
||||
/** 作者 */
|
||||
author?: string;
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
page?: number;
|
||||
/** 项目名称 */
|
||||
project_name?: string;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface GetSecurityScanningDetailParams {
|
||||
/** 扫描任务id */
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface GetUserChatInfoParams {
|
||||
/** 对话记录ID */
|
||||
id: string;
|
||||
@@ -1384,6 +1545,32 @@ export interface GetUserOauthSignupOrInParams {
|
||||
source: "plugin" | "browser";
|
||||
}
|
||||
|
||||
export interface GetUserSecurityScanningListParams {
|
||||
/** 作者 */
|
||||
author?: string;
|
||||
/** 下一页标识 */
|
||||
next_token?: string;
|
||||
/**
|
||||
* 分页
|
||||
* @min 1
|
||||
* @default 1
|
||||
*/
|
||||
page?: number;
|
||||
/** 项目名称 */
|
||||
project_name?: string;
|
||||
/**
|
||||
* 每页多少条记录
|
||||
* @min 1
|
||||
* @default 10
|
||||
*/
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface GetUserSecurityScanningDetailParams {
|
||||
/** 扫描任务id */
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface GetListWorkspaceFilesParams {
|
||||
/** 编程语言筛选 */
|
||||
language?: string;
|
||||
|
||||
@@ -58,10 +58,10 @@ const ADMIN_MENUS = [
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
label: '管理员',
|
||||
value: '/admin',
|
||||
pathname: 'admin',
|
||||
icon: 'icon-guanliyuan1',
|
||||
label: '通用设置',
|
||||
value: '/general-setting',
|
||||
pathname: 'general-setting',
|
||||
icon: 'icon-setting',
|
||||
show: true,
|
||||
disabled: false,
|
||||
},
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import React from 'react';
|
||||
import LoginHistory from './loginHistory';
|
||||
import AdminTable from './adminTable';
|
||||
import { Grid2 as Grid, Stack } from '@mui/material';
|
||||
|
||||
const Admin = () => {
|
||||
return (
|
||||
<Grid container spacing={2} sx={{ height: '100%' }}>
|
||||
<Grid size={6}>
|
||||
<AdminTable />
|
||||
</Grid>
|
||||
<Grid size={6}>
|
||||
<LoginHistory />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default Admin;
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
Divider,
|
||||
Stack,
|
||||
} from '@mui/material';
|
||||
import { Icon, message } from '@c-x/ui';
|
||||
import { CusTabs, Icon, message } from '@c-x/ui';
|
||||
|
||||
// @ts-ignore
|
||||
import { AestheticFluidBg } from '@/assets/jsm/AestheticFluidBg.module.js';
|
||||
@@ -43,7 +43,7 @@ const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||
position: 'relative',
|
||||
zIndex: 9,
|
||||
padding: theme.spacing(4),
|
||||
background: 'rgba(255, 255, 255, 0.85)',
|
||||
background: 'rgba(240, 240, 240, 0.85)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
width: 458,
|
||||
borderRadius: theme.spacing(2),
|
||||
@@ -133,6 +133,12 @@ const AuthPage = () => {
|
||||
// 处理登录表单提交
|
||||
const onSubmit = useCallback(
|
||||
async (data: LoginFormData) => {
|
||||
// 检查是否使用 admin 账户登录
|
||||
if (data.username === 'admin') {
|
||||
message.error('请使用研发成员的账户登录,本页面不支持管理员账户访问');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
@@ -344,6 +350,40 @@ const AuthPage = () => {
|
||||
Monkey Code
|
||||
</LogoTitle>
|
||||
</LogoContainer>
|
||||
|
||||
<CusTabs
|
||||
list={[
|
||||
{ label: '研发成员', value: 'user' },
|
||||
{ label: '管理员', value: 'admin', disabled: true},
|
||||
]}
|
||||
value={"user"}
|
||||
sx={{
|
||||
width: '100%',
|
||||
mb: 4,
|
||||
height: 40,
|
||||
border: 'none',
|
||||
padding: '4px',
|
||||
'.MuiTab-root': {
|
||||
width: '50%',
|
||||
height: 32,
|
||||
fontSize: 14,
|
||||
'&.Mui-selected': {
|
||||
color: 'text.primary',
|
||||
fontWeight: 500,
|
||||
},
|
||||
},
|
||||
'.MuiTabs-scroller': {
|
||||
height: 32,
|
||||
},
|
||||
'.MuiTabs-indicator': {
|
||||
borderRadius: '10px',
|
||||
height: 32,
|
||||
backgroundColor: '#fff',
|
||||
},
|
||||
bgcolor: 'rgba(255, 255, 255, 0.2)',
|
||||
borderRadius: '10px',
|
||||
}}
|
||||
/>
|
||||
{!loginSetting.disable_password_login && renderLoginForm()}
|
||||
{oauthEnable && oauthLogin()}
|
||||
</StyledPaper>
|
||||
|
||||
@@ -12,7 +12,7 @@ type LoginHistory = NonNullable<
|
||||
DomainListAdminLoginHistoryResp['login_histories']
|
||||
>[number];
|
||||
|
||||
const LoginHistory = () => {
|
||||
const AdminLoginHistory = () => {
|
||||
const { data, loading } = useRequest(() => getAdminLoginHistory({}));
|
||||
const columns: ColumnsType<LoginHistory> = [
|
||||
{
|
||||
@@ -55,9 +55,23 @@ const LoginHistory = () => {
|
||||
direction='row'
|
||||
justifyContent='space-between'
|
||||
alignItems='center'
|
||||
sx={{ mb: 2 }}
|
||||
sx={{
|
||||
mb: 2,
|
||||
height: 32,
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ fontWeight: 700, lineHeight: '36px' }}>管理员登录记录</Box>
|
||||
<Box sx={{
|
||||
'&::before': {
|
||||
content: '""',
|
||||
display: 'inline-block',
|
||||
width: 4,
|
||||
height: 12,
|
||||
bgcolor: 'common.black',
|
||||
borderRadius: '2px',
|
||||
mr: 1,
|
||||
},
|
||||
}}>管理员登录记录</Box>
|
||||
</Stack>
|
||||
<Table
|
||||
columns={columns}
|
||||
@@ -71,4 +85,4 @@ const LoginHistory = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginHistory;
|
||||
export default AdminLoginHistory;
|
||||
@@ -151,7 +151,7 @@ const AddAdminModal = ({
|
||||
);
|
||||
};
|
||||
|
||||
const AdminTable = () => {
|
||||
const AdminUser = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const { data, loading, refresh } = useRequest(() => getListAdminUser({}));
|
||||
const onDeleteAdmin = (data: DomainAdminUser) => {
|
||||
@@ -187,19 +187,27 @@ const AdminTable = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '最近活跃时间',
|
||||
title: '加入时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 140,
|
||||
render: (text) => {
|
||||
return dayjs.unix(text).fromNow();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '最近活跃',
|
||||
dataIndex: 'last_active_at',
|
||||
width: 140,
|
||||
render: (text, record) => {
|
||||
return <Stack direction='column'>
|
||||
<Box sx={{ color: 'text.secondary' }}>{record.created_at ? dayjs.unix(record.created_at).fromNow() + '加入' : '加入时间未知'}</Box>
|
||||
<Box sx={{ color: 'text.secondary' }}>{record.last_active_at ? dayjs.unix(record.last_active_at).fromNow() + '活跃' : '活跃时间未知'}</Box>
|
||||
</Stack>
|
||||
return record.last_active_at === 0
|
||||
? '从未使用'
|
||||
: dayjs.unix(text).fromNow();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'opt',
|
||||
width: 200,
|
||||
width: 100,
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<Button
|
||||
@@ -215,14 +223,28 @@ const AdminTable = () => {
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Card sx={{ height: '100%' }}>
|
||||
<Card>
|
||||
<Stack
|
||||
direction='row'
|
||||
justifyContent='space-between'
|
||||
alignItems='center'
|
||||
sx={{ mb: 2 }}
|
||||
sx={{
|
||||
mb: 2,
|
||||
height: 32,
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ fontWeight: 700, lineHeight: '36px' }}>管理员</Box>
|
||||
<Box sx={{
|
||||
'&::before': {
|
||||
content: '""',
|
||||
display: 'inline-block',
|
||||
width: 4,
|
||||
height: 12,
|
||||
bgcolor: 'common.black',
|
||||
borderRadius: '2px',
|
||||
mr: 1,
|
||||
},
|
||||
}}>管理员清单</Box>
|
||||
<Button
|
||||
variant='contained'
|
||||
color='primary'
|
||||
@@ -249,4 +271,4 @@ const AdminTable = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminTable;
|
||||
export default AdminUser;
|
||||
25
ui/src/pages/generalSetting/components/cardAdminUser.tsx
Normal file
25
ui/src/pages/generalSetting/components/cardAdminUser.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import Card from '@/components/card';
|
||||
import { Box, Stack } from "@mui/material"
|
||||
import AdminUser from './adminUser';
|
||||
import AdminLoginHistory from './adminLoginHistory';
|
||||
|
||||
|
||||
const CardAdminUser = () => {
|
||||
|
||||
return <Card sx={{ p : 0, }}>
|
||||
<Box sx={{
|
||||
fontWeight: 'bold',
|
||||
px: 2,
|
||||
py: 1.5,
|
||||
bgcolor: 'rgb(248, 249, 250)',
|
||||
borderTopLeftRadius: '10px',
|
||||
borderTopRightRadius: '10px',
|
||||
}}>管理员配置</Box>
|
||||
<Stack direction='column'>
|
||||
<AdminUser />
|
||||
<AdminLoginHistory />
|
||||
</Stack>
|
||||
</Card>
|
||||
}
|
||||
|
||||
export default CardAdminUser;
|
||||
122
ui/src/pages/generalSetting/components/cardServiceSettings.tsx
Normal file
122
ui/src/pages/generalSetting/components/cardServiceSettings.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import Card from '@/components/card';
|
||||
import { Box, Button, Stack, TextField } from "@mui/material"
|
||||
import { message } from '@c-x/ui';
|
||||
import { useEffect, useState } from "react"
|
||||
import { getGetSetting, putUpdateSetting } from '@/api/Admin';
|
||||
import { DomainUpdateSettingReq } from '@/api/types';
|
||||
|
||||
const CardServiceSettings = () => {
|
||||
const [baseURL, setBaseURL] = useState('');
|
||||
const [initialBaseURL, setInitialBaseURL] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const fetchInitialBaseURL = async () => {
|
||||
try {
|
||||
const response = await getGetSetting();
|
||||
const initialValue = response.base_url || '';
|
||||
setBaseURL(initialValue);
|
||||
setInitialBaseURL(initialValue);
|
||||
} catch (err: any) {
|
||||
message.error('Failed to fetch initial base URL:', err);
|
||||
// 如果获取失败,可以设置一个默认值或者保持空字符串
|
||||
setBaseURL('');
|
||||
setInitialBaseURL('');
|
||||
}
|
||||
};
|
||||
|
||||
fetchInitialBaseURL();
|
||||
}, []);
|
||||
|
||||
const handleBaseURLChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setBaseURL(event.target.value);
|
||||
};
|
||||
|
||||
const isValidURL = (url: string): boolean => {
|
||||
try {
|
||||
const parsedURL = new URL(url);
|
||||
// Check if the protocol is either http or https
|
||||
if (parsedURL.protocol !== 'http:' && parsedURL.protocol !== 'https:') {
|
||||
return false;
|
||||
}
|
||||
// Check if the URL is a base URL (no path or only root path)
|
||||
if (parsedURL.pathname !== '/' && parsedURL.pathname !== '') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
// Check if the baseURL is valid before saving
|
||||
if (baseURL && !isValidURL(baseURL)) {
|
||||
message.error('请输入一个有效的 URL 地址');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const setting: DomainUpdateSettingReq = {
|
||||
base_url: baseURL,
|
||||
};
|
||||
await putUpdateSetting(setting);
|
||||
message.success('保存成功');
|
||||
setInitialBaseURL(baseURL);
|
||||
} catch (err: any) {
|
||||
message.error('保存失败:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const hasValueChanged = baseURL !== initialBaseURL;
|
||||
|
||||
return <Card sx={{p : 0, borderBottom: '1px solid #e0e0e0'}}>
|
||||
<Box sx={{
|
||||
fontWeight: 'bold',
|
||||
px: 2,
|
||||
py: 1.5,
|
||||
bgcolor: 'rgb(248, 249, 250)',
|
||||
borderTopLeftRadius: '10px',
|
||||
borderTopRightRadius: '10px',
|
||||
}}>MonkeyCode 服务配置</Box>
|
||||
<Stack direction='column'>
|
||||
<Box sx={{ width: '100%' }}>
|
||||
<Stack direction='row' alignItems={'center'} justifyContent={'space-between'} sx={{
|
||||
m: 2,
|
||||
height: 32,
|
||||
fontWeight: 'bold',
|
||||
}}>
|
||||
<Box sx={{
|
||||
'&::before': {
|
||||
content: '""',
|
||||
display: 'inline-block',
|
||||
width: 4,
|
||||
height: 12,
|
||||
bgcolor: 'common.black',
|
||||
borderRadius: '2px',
|
||||
mr: 1,
|
||||
},
|
||||
}}>MonkeyCode 服务链接地址</Box>
|
||||
{hasValueChanged && <Button variant="contained" size="small" onClick={handleSave}>保存</Button>}
|
||||
</Stack>
|
||||
<Box sx={{ m: 2 }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
value={baseURL}
|
||||
onChange={handleBaseURLChange}
|
||||
placeholder={baseURL ? '' : window.location.origin}
|
||||
/>
|
||||
<Box sx={{
|
||||
mt: 1,
|
||||
fontSize: '0.75rem',
|
||||
color: '#FFA500',
|
||||
fontWeight: 'normal'
|
||||
}}>
|
||||
用于解决 VSCode 插件无法连接 MonkeyCode 服务的问题
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Card>
|
||||
}
|
||||
|
||||
export default CardServiceSettings;
|
||||
18
ui/src/pages/generalSetting/index.tsx
Normal file
18
ui/src/pages/generalSetting/index.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import CardServiceSettings from './components/cardServiceSettings';
|
||||
import { Grid2 as Grid } from '@mui/material';
|
||||
import CardAdminUser from './components/cardAdminUser';
|
||||
|
||||
const GeneralSetting = () => {
|
||||
return (
|
||||
<Grid container spacing={2} sx={{ height: '100%' }}>
|
||||
<Grid size={6}>
|
||||
<CardServiceSettings />
|
||||
</Grid>
|
||||
<Grid size={6}>
|
||||
<CardAdminUser />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default GeneralSetting;
|
||||
@@ -42,7 +42,7 @@ const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||
position: 'relative',
|
||||
zIndex: 9,
|
||||
padding: theme.spacing(4),
|
||||
background: 'rgba(255, 255, 255, 0.85)',
|
||||
background: 'rgba(240, 240, 240, 0.85)',
|
||||
backdropFilter: 'blur(10px)',
|
||||
width: 458,
|
||||
borderRadius: theme.spacing(2),
|
||||
@@ -209,8 +209,8 @@ const LoginPage = () => {
|
||||
|
||||
<CusTabs
|
||||
list={[
|
||||
{ label: '普通账号', value: 'user' },
|
||||
{ label: '管理员账号', value: 'admin' },
|
||||
{ label: '研发成员', value: 'user' },
|
||||
{ label: '管理员', value: 'admin' },
|
||||
]}
|
||||
value={tab}
|
||||
onChange={(value: TabType) => {
|
||||
|
||||
@@ -266,7 +266,7 @@ const MemberManage = () => {
|
||||
{
|
||||
title: '加入时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 120,
|
||||
width: 140,
|
||||
render: (text) => {
|
||||
return dayjs.unix(text).fromNow();
|
||||
},
|
||||
@@ -274,7 +274,7 @@ const MemberManage = () => {
|
||||
{
|
||||
title: '最近活跃',
|
||||
dataIndex: 'last_active_at',
|
||||
width: 120,
|
||||
width: 140,
|
||||
render: (text, record) => {
|
||||
return record.last_active_at === 0
|
||||
? '从未使用'
|
||||
@@ -284,7 +284,7 @@ const MemberManage = () => {
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'action',
|
||||
width: 100,
|
||||
width: 80,
|
||||
render: (_, record) => {
|
||||
return (
|
||||
<IconButton
|
||||
|
||||
@@ -33,7 +33,7 @@ const Chat = LazyLoadable(lazy(() => import('@/pages/chat')));
|
||||
const Completion = LazyLoadable(lazy(() => import('@/pages/completion')));
|
||||
const Model = LazyLoadable(lazy(() => import('@/pages/model')));
|
||||
const MemberManage = LazyLoadable(lazy(() => import('@/pages/memberManage')));
|
||||
const Admin = LazyLoadable(lazy(() => import('@/pages/admin')));
|
||||
const GeneralSetting = LazyLoadable(lazy(() => import('@/pages/generalSetting')));
|
||||
const Invite = LazyLoadable(lazy(() => import('@/pages/invite')));
|
||||
const Auth = LazyLoadable(lazy(() => import('@/pages/auth')));
|
||||
const Login = LazyLoadable(lazy(() => import('@/pages/login')));
|
||||
@@ -83,8 +83,8 @@ const routerConfig = [
|
||||
element: <MemberManage />,
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
element: <Admin />,
|
||||
path: 'general-setting',
|
||||
element: <GeneralSetting />,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user