feat: able to delete all manually disabled channels (close #539)

This commit is contained in:
JustSong 2023-10-02 13:06:27 +08:00
parent cbd62011b8
commit 8d34b7a77e
6 changed files with 101 additions and 28 deletions

View File

@ -156,9 +156,10 @@ const (
) )
const ( const (
ChannelStatusUnknown = 0 ChannelStatusUnknown = 0
ChannelStatusEnabled = 1 // don't use 0, 0 is the default value! ChannelStatusEnabled = 1 // don't use 0, 0 is the default value!
ChannelStatusDisabled = 2 // also don't use 0 ChannelStatusManuallyDisabled = 2 // also don't use 0
ChannelStatusAutoDisabled = 3
) )
const ( const (

View File

@ -141,7 +141,7 @@ func disableChannel(channelId int, channelName string, reason string) {
if common.RootUserEmail == "" { if common.RootUserEmail == "" {
common.RootUserEmail = model.GetRootUserEmail() common.RootUserEmail = model.GetRootUserEmail()
} }
model.UpdateChannelStatusById(channelId, common.ChannelStatusDisabled) model.UpdateChannelStatusById(channelId, common.ChannelStatusAutoDisabled)
subject := fmt.Sprintf("通道「%s」#%d已被禁用", channelName, channelId) subject := fmt.Sprintf("通道「%s」#%d已被禁用", channelName, channelId)
content := fmt.Sprintf("通道「%s」#%d已被禁用原因%s", channelName, channelId, reason) content := fmt.Sprintf("通道「%s」#%d已被禁用原因%s", channelName, channelId, reason)
err := common.SendEmail(subject, common.RootUserEmail, content) err := common.SendEmail(subject, common.RootUserEmail, content)

View File

@ -127,6 +127,23 @@ func DeleteChannel(c *gin.Context) {
return return
} }
func DeleteManuallyDisabledChannel(c *gin.Context) {
rows, err := model.DeleteChannelByStatus(common.ChannelStatusManuallyDisabled)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": rows,
})
return
}
func UpdateChannel(c *gin.Context) { func UpdateChannel(c *gin.Context) {
channel := model.Channel{} channel := model.Channel{}
err := c.ShouldBindJSON(&channel) err := c.ShouldBindJSON(&channel)

View File

@ -176,3 +176,8 @@ func updateChannelUsedQuota(id int, quota int) {
common.SysError("failed to update channel used quota: " + err.Error()) common.SysError("failed to update channel used quota: " + err.Error())
} }
} }
func DeleteChannelByStatus(status int64) (int64, error) {
result := DB.Where("status = ?", status).Delete(&Channel{})
return result.RowsAffected, result.Error
}

View File

@ -74,6 +74,7 @@ func SetApiRouter(router *gin.Engine) {
channelRoute.GET("/update_balance/:id", controller.UpdateChannelBalance) channelRoute.GET("/update_balance/:id", controller.UpdateChannelBalance)
channelRoute.POST("/", controller.AddChannel) channelRoute.POST("/", controller.AddChannel)
channelRoute.PUT("/", controller.UpdateChannel) channelRoute.PUT("/", controller.UpdateChannel)
channelRoute.DELETE("/manually_disabled", controller.DeleteManuallyDisabledChannel)
channelRoute.DELETE("/:id", controller.DeleteChannel) channelRoute.DELETE("/:id", controller.DeleteChannel)
} }
tokenRoute := apiRouter.Group("/token") tokenRoute := apiRouter.Group("/token")

View File

@ -1,5 +1,5 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import {Button, Form, Input, Label, Pagination, Popup, Table} from 'semantic-ui-react'; import { Button, Form, Input, Label, Pagination, Popup, Table } from 'semantic-ui-react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { API, showError, showInfo, showNotice, showSuccess, timestamp2string } from '../helpers'; import { API, showError, showInfo, showNotice, showSuccess, timestamp2string } from '../helpers';
@ -96,7 +96,7 @@ const ChannelsTable = () => {
}); });
}, []); }, []);
const manageChannel = async (id, action, idx, priority) => { const manageChannel = async (id, action, idx, value) => {
let data = { id }; let data = { id };
let res; let res;
switch (action) { switch (action) {
@ -112,10 +112,20 @@ const ChannelsTable = () => {
res = await API.put('/api/channel/', data); res = await API.put('/api/channel/', data);
break; break;
case 'priority': case 'priority':
if (priority === '') { if (value === '') {
return; return;
} }
data.priority = parseInt(priority); data.priority = parseInt(value);
res = await API.put('/api/channel/', data);
break;
case 'weight':
if (value === '') {
return;
}
data.weight = parseInt(value);
if (data.weight < 0) {
data.weight = 0;
}
res = await API.put('/api/channel/', data); res = await API.put('/api/channel/', data);
break; break;
} }
@ -142,9 +152,23 @@ const ChannelsTable = () => {
return <Label basic color='green'>已启用</Label>; return <Label basic color='green'>已启用</Label>;
case 2: case 2:
return ( return (
<Label basic color='red'> <Popup
已禁用 trigger={<Label basic color='red'>
</Label> 已禁用
</Label>}
content='本渠道被手动禁用'
basic
/>
);
case 3:
return (
<Popup
trigger={<Label basic color='yellow'>
已禁用
</Label>}
content='本渠道被程序自动禁用'
basic
/>
); );
default: default:
return ( return (
@ -202,7 +226,7 @@ const ChannelsTable = () => {
showInfo(`通道 ${name} 测试成功,耗时 ${time.toFixed(2)} 秒。`); showInfo(`通道 ${name} 测试成功,耗时 ${time.toFixed(2)} 秒。`);
} else { } else {
showError(message); showError(message);
showNotice("当前版本测试是通过按照 OpenAI API 格式使用 gpt-3.5-turbo 模型进行非流式请求实现的,因此测试报错并不一定代表通道不可用,该功能后续会修复。") showNotice('当前版本测试是通过按照 OpenAI API 格式使用 gpt-3.5-turbo 模型进行非流式请求实现的,因此测试报错并不一定代表通道不可用,该功能后续会修复。');
} }
}; };
@ -216,6 +240,17 @@ const ChannelsTable = () => {
} }
}; };
const deleteAllManuallyDisabledChannels = async () => {
const res = await API.delete(`/api/channel/manually_disabled`);
const { success, message, data } = res.data;
if (success) {
showSuccess(`已删除所有手动禁用渠道,共计 ${data}`);
await refresh();
} else {
showError(message);
}
};
const updateChannelBalance = async (id, name, idx) => { const updateChannelBalance = async (id, name, idx) => {
const res = await API.get(`/api/channel/update_balance/${id}/`); const res = await API.get(`/api/channel/update_balance/${id}/`);
const { success, message, balance } = res.data; const { success, message, balance } = res.data;
@ -343,10 +378,10 @@ const ChannelsTable = () => {
余额 余额
</Table.HeaderCell> </Table.HeaderCell>
<Table.HeaderCell <Table.HeaderCell
style={{ cursor: 'pointer' }} style={{ cursor: 'pointer' }}
onClick={() => { onClick={() => {
sortChannel('priority'); sortChannel('priority');
}} }}
> >
优先级 优先级
</Table.HeaderCell> </Table.HeaderCell>
@ -390,18 +425,18 @@ const ChannelsTable = () => {
</Table.Cell> </Table.Cell>
<Table.Cell> <Table.Cell>
<Popup <Popup
trigger={<Input type="number" defaultValue={channel.priority} onBlur={(event) => { trigger={<Input type='number' defaultValue={channel.priority} onBlur={(event) => {
manageChannel( manageChannel(
channel.id, channel.id,
'priority', 'priority',
idx, idx,
event.target.value, event.target.value
); );
}}> }}>
<input style={{maxWidth:'60px'}} /> <input style={{ maxWidth: '60px' }} />
</Input>} </Input>}
content='渠道选择优先级,越高越优先' content='渠道选择优先级,越高越优先'
basic basic
/> />
</Table.Cell> </Table.Cell>
<Table.Cell> <Table.Cell>
@ -481,6 +516,20 @@ const ChannelsTable = () => {
</Button> </Button>
<Button size='small' onClick={updateAllChannelsBalance} <Button size='small' onClick={updateAllChannelsBalance}
loading={loading || updatingBalance}>更新所有已启用通道余额</Button> loading={loading || updatingBalance}>更新所有已启用通道余额</Button>
<Popup
trigger={
<Button size='small' loading={loading}>
删除所有手动禁用渠道
</Button>
}
on='click'
flowing
hoverable
>
<Button size='small' loading={loading} negative onClick={deleteAllManuallyDisabledChannels}>
确认删除
</Button>
</Popup>
<Pagination <Pagination
floated='right' floated='right'
activePage={activePage} activePage={activePage}