Merge pull request #337 from yokowu/feat-edition

feat: 进一步开放开源版功能
This commit is contained in:
Yoko
2025-09-04 17:06:52 +08:00
committed by GitHub
12 changed files with 126 additions and 49 deletions

View File

@@ -23,7 +23,10 @@ var (
ErrDingtalkNotEnabled = web.NewBadRequestErr("err-dingtalk-not-enabled")
ErrCustomNotEnabled = web.NewBadRequestErr("err-custom-not-enabled")
ErrUserLimit = web.NewBadRequestErr("err-user-limit")
ErrModelLimit = web.NewBadRequestErr("err-model-limit")
ErrSecurityLimit = web.NewBadRequestErr("err-security-limit")
ErrOnlyAdmin = web.NewBadRequestErr("err-only-admin")
ErrOnlyEnterprise = web.NewBadRequestErr("err-only-enterprise")
ErrInvalidSecret = web.NewBadRequestErr("err-invalid-secret")
ErrAIEmployeeLimit = web.NewBadRequestErr("err-ai-employee-limit")
)

View File

@@ -40,6 +40,15 @@ other = "OAuth is not enabled"
[err-user-limit]
other = "User limit reached"
[err-model-limit]
other = "Model limit reached"
[err-security-limit]
other = "Security limit reached"
[err-only-enterprise]
other = "Only enterprise user can perform this operation"
[err-invalid-secret]
other = "Invalid secret"

View File

@@ -40,6 +40,15 @@ other = "OAuth未启用"
[err-user-limit]
other = "用户数量已达上限"
[err-model-limit]
other = "模型数量已达上限"
[err-security-limit]
other = "已有扫描中的任务"
[err-only-enterprise]
other = "仅企业版可用"
[err-invalid-secret]
other = "无效的密钥"

View File

@@ -62,6 +62,9 @@ func (r *ModelRepo) Create(ctx context.Context, m *domain.CreateModelReq) (*db.M
return nil, err
}
status := consts.ModelStatusActive
if m.ModelType == consts.ModelTypeCoder {
status = consts.ModelStatusInactive
}
if n == 0 {
status = consts.ModelStatusDefault
}

View File

@@ -109,12 +109,22 @@ func (m *ModelUsecase) Update(ctx context.Context, req *domain.UpdateModelReq) (
}
if req.Status != nil {
if *req.Status == consts.ModelStatusDefault {
if err := tx.Model.Update().
Where(model.Status(consts.ModelStatusDefault)).
Where(model.ModelType(old.ModelType)).
SetStatus(consts.ModelStatusActive).
Exec(ctx); err != nil {
return err
if old.ModelType == consts.ModelTypeCoder {
if err := tx.Model.Update().
Where(model.StatusIn(consts.ModelStatusDefault, consts.ModelStatusActive)).
Where(model.ModelType(old.ModelType)).
SetStatus(consts.ModelStatusInactive).
Exec(ctx); err != nil {
return err
}
} else {
if err := tx.Model.Update().
Where(model.Status(consts.ModelStatusDefault)).
Where(model.ModelType(old.ModelType)).
SetStatus(consts.ModelStatusActive).
Exec(ctx); err != nil {
return err
}
}
}
if *req.Status == consts.ModelStatusActive {

View File

@@ -442,23 +442,6 @@ func (h *UserHandler) LoginHistory(c *web.Context) error {
// @Router /api/v1/user/invite [get]
func (h *UserHandler) Invite(c *web.Context) error {
admin := middleware.GetAdmin(c)
edition := c.Get("edition")
if edition == nil {
return errcode.ErrPermission
}
// 如果是 Free 版本 user 表不允许超过 100 人
if edition.(int) == 0 {
count, err := h.usecase.GetUserCount(c.Request().Context())
if err != nil {
return err
}
if count >= 100 {
return errcode.ErrUserLimit
}
}
resp, err := h.usecase.Invite(c.Request().Context(), admin.ID.String())
if err != nil {
return err

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +1,13 @@
import dayjs from 'dayjs';
import { useState } from 'react';
import { Ellipsis, Modal } from '@c-x/ui';
import { Box, Button, Link, Stack } from '@mui/material';
import { Modal } from '@c-x/ui';
import HelpCenter from '@/assets/json/help-center.json';
import Takeoff from '@/assets/json/takeoff.json';
import IconUpgrade from '@/assets/json/upgrade.json';
import { Box, Button, Stack } from '@mui/material';
import { DomainLicenseResp } from '@/api/types';
import ChangeLicense from './changeLicense';
import LottieIcon from '../lottieIcon';
interface LicenseModalProps {
open: boolean;
@@ -36,7 +40,7 @@ const AboutModal = ({
}
return (
<Modal
<Modal
title='关于 MonkeyCode'
width={600}
open={open}
@@ -45,7 +49,7 @@ const AboutModal = ({
<Stack direction={'column'} gap={2} sx={{
fontSize: '14px'
}}>
<Stack direction={'row'}>
<Stack direction={'row'} gap={2} alignItems={'center'}>
<Box sx={{
width: '120px'
}}></Box>
@@ -53,25 +57,75 @@ const AboutModal = ({
width: '120px',
fontWeight: 700
}}>{curVersion}</Box>
{latestVersion === `v${curVersion}` ? (
<Box sx={{ color: 'text.auxiliary', fontSize: 12 }}>
</Box>
) : (
<Button
size='small'
startIcon={
<Box>
<LottieIcon
id='version'
src={latestVersion === '' ? HelpCenter : IconUpgrade}
style={{ width: 16, height: 16, display: 'flex' }}
/>
</Box>
}
onClick={() => {
window.open(
'https://monkeycode.docs.baizhi.cloud/node/01980d22-db84-73b4-ae13-6a188e318048',
);
}}
>
</Button>
)}
</Stack>
<Stack direction={'row'}>
<Stack direction={'row'} gap={2} alignItems={'center'}>
<Box sx={{
width: '120px',
}}></Box>
<Box sx={{
mr: '20px'
}}>{editionText(license?.edition)}</Box>
<Link href="#" sx={{
color: 'info.main',
'&:hover': {
fontWeight: 700
<Box>{editionText(license?.edition)}</Box>
<Button
size='small'
startIcon={
<Box>
<LottieIcon
id='version'
src={Takeoff}
style={{ width: 16, height: 16, display: 'flex' }}
/>
</Box>
}
}}
onClick={() => {
setOpenChangeLicense(true);
}}></Link>
onClick={() => setOpenChangeLicense(true)}
>
</Button>
<Button
size='small'
startIcon={
<Box>
<LottieIcon
id='consult'
src={HelpCenter}
style={{ width: 16, display: 'flex' }}
/>
</Box>
}
onClick={() => {
window.open('https://baizhi.cloud/consult');
}}
>
</Button>
</Stack>
{license && license?.edition !== 0 && <Stack direction={'row'}>
{license && license?.edition !== 0 && <Stack direction={'row'} gap={2}>
<Box sx={{
width: '120px'
}}></Box>
@@ -79,9 +133,9 @@ const AboutModal = ({
}}>{dayjs.unix(license.started_at!).format('YYYY-MM-DD')} ~ {dayjs.unix(license.expired_at!).format('YYYY-MM-DD')}</Box>
</Stack>}
</Stack>
<ChangeLicense
<ChangeLicense
open={openChangeLicense}
onClose={() => {setOpenChangeLicense(false)}} />
onClose={() => { setOpenChangeLicense(false) }} />
</Modal>
);
};

View File

@@ -61,7 +61,6 @@ const presets = {
end: new Date(),
},
};
export type TimeRange = '90d' | '24h';
const Dashboard = () => {
const navigate = useNavigate();
@@ -69,7 +68,7 @@ const Dashboard = () => {
const [tabValue, setTabValue] = useState(tab || 'global');
const [memberData, setMemberData] = useState<DomainUser | null>(null);
const [timeRange, setTimeRange] = useState<RangeValue>(
presets['last-1-days']
presets['last-7-days']
);
const license = useRequest(() => {
@@ -167,7 +166,7 @@ const Dashboard = () => {
disabled={license?.edition !== 2}
onChange={handleTimeRangeChange}
presets={presets}
presetIndex={0}
presetIndex={2}
value={timeRange}
/>
</Box>

View File

@@ -19,8 +19,8 @@ import {
} from '@mui/material';
import { Table, Modal, message } from '@c-x/ui';
import { ColumnsType } from '@c-x/ui/dist/Table';
import { DomainAdminUser, DomainUser, DomainUserGroup } from '@/api/types';
import { deleteDeleteGroup, getListAdminUser, getListUserGroup } from '@/api';
import { DomainAdminUser, DomainLicenseEdition, DomainUser, DomainUserGroup } from '@/api/types';
import { deleteDeleteGroup, getListAdminUser, getListUserGroup, v1LicenseList } from '@/api';
import { deleteRemoveAdminFromGroup, postGrantGroup, postAddUserToGroup, deleteRemoveUserFromGroup, putUpdateUserGroup } from '@/api/UserGroup';
import CreateGroupModal from './createGroupModal';
import UpdateGroupModal from './updateGroupModal';
@@ -40,6 +40,9 @@ const GroupList = () => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const memberInputRef = useRef<HTMLInputElement>(null);
const adminInputRef = useRef<HTMLInputElement>(null);
const license = useRequest(() => {
return v1LicenseList({})
}).data
const filteredData = useMemo(() => {
if (!groupData?.data?.groups) return [];
@@ -353,6 +356,7 @@ const GroupList = () => {
variant='contained'
color='primary'
onClick={() => setOpenCreateGroupModal(true)}
disabled={license?.edition !== DomainLicenseEdition.LicenseEditionEnterprise}
></Button>
</Stack>
</Stack>

View File

@@ -344,7 +344,9 @@ const ModelItem = ({
</ButtonBase>
)}
{data.status === GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusInactive && (
{data.status === GithubComChaitinMonkeyCodeBackendConstsModelStatus.ModelStatusInactive &&
data.model_type !== GithubComChaitinMonkeyCodeBackendConstsModelType.ModelTypeCoder &&
(
<ButtonBase
disableRipple
sx={{