diff --git a/.changeset/chatty-sloths-wait.md b/.changeset/chatty-sloths-wait.md new file mode 100644 index 0000000000..e31a742e2f --- /dev/null +++ b/.changeset/chatty-sloths-wait.md @@ -0,0 +1,6 @@ +--- +"@scow/mis-web": patch +"@scow/mis-server": patch +--- + +getWhitelistedAccounts 新增返回字段 expirationDate,whitelistAccount 新增字段 expirationDate,在 getWhitelistedAccounts 新增每次查询会检测 中是否有账户过期,有的话会自动删除 diff --git a/.changeset/flat-students-walk.md b/.changeset/flat-students-walk.md new file mode 100644 index 0000000000..0867e6fa6a --- /dev/null +++ b/.changeset/flat-students-walk.md @@ -0,0 +1,7 @@ +--- +"@scow/demo-vagrant": patch +"@scow/mis-web": patch +"@scow/auth": patch +--- + +mis 系统下,管理员添加白名单新增白名单账户过期字段 diff --git a/.changeset/plenty-avocados-tap.md b/.changeset/plenty-avocados-tap.md new file mode 100644 index 0000000000..093eff2911 --- /dev/null +++ b/.changeset/plenty-avocados-tap.md @@ -0,0 +1,5 @@ +--- +"@scow/grpc-api": minor +--- + +getWhitelistedAccounts 新增返回字段 expirationDate,whitelistAccount 新增字段 expirationDate,在 getWhitelistedAccounts 新增每次查询会检测 中是否有账户过期,有的话会自动删除 diff --git a/apps/mis-server/src/entities/AccountWhitelist.ts b/apps/mis-server/src/entities/AccountWhitelist.ts index bc57ae089a..84213b1090 100644 --- a/apps/mis-server/src/entities/AccountWhitelist.ts +++ b/apps/mis-server/src/entities/AccountWhitelist.ts @@ -31,15 +31,22 @@ export class AccountWhitelist { @Property() operatorId: string; + // 当expirationTime为undefined时,即为永久有效 + @Property({ nullable: true }) + expirationTime?: Date; + constructor(init: { account: EntityOrRef, time?: Date, comment: string operatorId: string; + expirationTime?: Date }) { this.account = toRef(init.account); this.time = init.time ?? new Date(); this.comment = init.comment; this.operatorId = init.operatorId; + // undefined为永久有效 + this.expirationTime = init.expirationTime ?? undefined; } } diff --git a/apps/mis-server/src/migrations/.snapshot-scow_server.json b/apps/mis-server/src/migrations/.snapshot-scow_server.json index 510a6230ee..876ae3736a 100644 --- a/apps/mis-server/src/migrations/.snapshot-scow_server.json +++ b/apps/mis-server/src/migrations/.snapshot-scow_server.json @@ -39,6 +39,16 @@ "primary": false, "nullable": false, "mappedType": "string" + }, + "expiration_time": { + "name": "expiration_time", + "type": "datetime", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 0, + "mappedType": "datetime" } }, "name": "account_whitelist", diff --git a/apps/mis-server/src/migrations/Migration20240515090037.ts b/apps/mis-server/src/migrations/Migration20240515090037.ts new file mode 100644 index 0000000000..d324fb505f --- /dev/null +++ b/apps/mis-server/src/migrations/Migration20240515090037.ts @@ -0,0 +1,13 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20240515090037 extends Migration { + + async up(): Promise { + this.addSql('alter table `account_whitelist` add `expiration_time` datetime null;'); + } + + async down(): Promise { + this.addSql('alter table `account_whitelist` drop column `expiration_time`;'); + } + +} diff --git a/apps/mis-server/src/services/account.ts b/apps/mis-server/src/services/account.ts index e6c9916b45..280ac91a60 100644 --- a/apps/mis-server/src/services/account.ts +++ b/apps/mis-server/src/services/account.ts @@ -289,20 +289,36 @@ export const accountServiceServer = plugin((server) => { const { tenantName } = request; - const results = await em.find(AccountWhitelist, { account: { tenant: { name: tenantName } } }, { + // 删除过期的白名单账户 + const today = new Date(); + + // 查询所有相关信息,并删除过期的白名单账户 + const results = await em.find(AccountWhitelist, { + $and: [ + { account: { tenant: { name: tenantName } } }, + ], + }, { populate: ["account"], }); + // 删除过期的白名单账户 + const expiredWhitelists = results.filter((x) => x.expirationTime && x.expirationTime < today); + if (expiredWhitelists.length > 0) { + await em.removeAndFlush(expiredWhitelists); + } + const owners = await em.find(UserAccount, { account: { accountName: results.map((x) => x.account.$.accountName), tenant: { name: tenantName } }, role: EntityUserRole.OWNER, }, { populate: ["user"]}); + // 过滤结果,排除已删除的白名单账户 + const validResults = results.filter((x) => !expiredWhitelists.includes(x)); + return [{ - accounts: results.map((x) => { + accounts: validResults.map((x) => { const accountOwner = owners.find((o) => o.account.id === x.account.id)!.user.$; - return { accountName: x.account.$.accountName, comment: x.comment, @@ -311,6 +327,8 @@ export const accountServiceServer = plugin((server) => { ownerId: accountOwner.userId + "", ownerName: accountOwner.name, balance: decimalToMoney(x.account.$.balance), + expirationTime:x.expirationTime?.toISOString().includes("2099") ? undefined + : x.expirationTime?.toISOString(), }; }), @@ -318,7 +336,7 @@ export const accountServiceServer = plugin((server) => { }, whitelistAccount: async ({ request, em, logger }) => { - const { accountName, comment, operatorId, tenantName } = request; + const { accountName, comment, operatorId, tenantName, expirationTime } = request; const account = await em.findOne(Account, { accountName, tenant: { name: tenantName } }, { populate: [ "tenant"]}); @@ -338,6 +356,8 @@ export const accountServiceServer = plugin((server) => { time: new Date(), comment, operatorId, + // expirationTime为undefined时为永久有效 + expirationTime:expirationTime ? new Date(expirationTime) : undefined, }); account.whitelist = toRef(whitelist); diff --git a/apps/mis-server/tests/admin/updateBlockStatus.test.ts b/apps/mis-server/tests/admin/updateBlockStatus.test.ts index 9c6b8331fa..5bfd828db4 100644 --- a/apps/mis-server/tests/admin/updateBlockStatus.test.ts +++ b/apps/mis-server/tests/admin/updateBlockStatus.test.ts @@ -64,6 +64,7 @@ it("update block status with whitelist accounts", async () => { accountName: data.blockedAccountB.accountName, comment: "test", operatorId: "123", + expirationTime:new Date("2025-01-01T00:00:00.000Z").toISOString(), }); const blockedData = await updateBlockStatusInSlurm( diff --git a/apps/mis-server/tests/admin/whitelist.test.ts b/apps/mis-server/tests/admin/whitelist.test.ts index 3371645d4b..9d528797d2 100644 --- a/apps/mis-server/tests/admin/whitelist.test.ts +++ b/apps/mis-server/tests/admin/whitelist.test.ts @@ -59,6 +59,7 @@ it("unblocks account when added to whitelist", async () => { accountName: a.accountName, comment: "test", operatorId: "123", + expirationTime:new Date("2025-01-01T00:00:00.000Z").toISOString(), }); await reloadEntity(em, a); @@ -72,6 +73,7 @@ it("blocks account when it is dewhitelisted and balance is < 0", async () => { account: a, comment: "", operatorId: "123", + expirationTime:new Date("2025-01-01T00:00:00.000Z"), }); await em.persistAndFlush(whitelist); @@ -102,6 +104,7 @@ it("blocks account when it is dewhitelisted and balance is = 0", async () => { account: a, comment: "", operatorId: "123", + expirationTime:new Date("2025-01-01T00:00:00.000Z"), }); await em.persistAndFlush(whitelist); @@ -134,6 +137,7 @@ it("charges user but don't block account if account is whitelist", async () => { account : a, comment: "123", operatorId: "123", + expirationTime:new Date("2025-01-01T00:00:00.000Z"), })); await em.flush(); @@ -161,6 +165,7 @@ it("get whitelisted accounts", async () => { comment: "", operatorId: "123", time: new Date("2023-01-01T00:00:00.000Z"), + expirationTime:new Date("2025-01-01T00:00:00.000Z"), }); await em.persistAndFlush(whitelist); @@ -183,6 +188,7 @@ it("get whitelisted accounts", async () => { "comment": "", "addTime": "2023-01-01T00:00:00.000Z", balance: decimalToMoney(data.accountA.balance), + "expirationTime":"2025-01-01T00:00:00.000Z", }, ]); diff --git a/apps/mis-web/src/i18n/en.ts b/apps/mis-web/src/i18n/en.ts index 86dc1a8a85..54148cff5d 100644 --- a/apps/mis-web/src/i18n/en.ts +++ b/apps/mis-web/src/i18n/en.ts @@ -41,6 +41,8 @@ export default { amount: "Amount", unit: "CNY", comment: "Comment", + expirationTime:"expirationTime", + submit: "Submit", time: "Time", type: "Type", @@ -232,6 +234,7 @@ export default { blockThresholdAmountTooltip: "The account will be blocked " + "when the balance is less than the block threshold.", comment: "Comment", + expirationTime:"expirationTime", status: "Status", statusTooltip: "Status:", statusFrozenTooltip: "Frozen: The account has been frozen by the account administrator " @@ -609,11 +612,19 @@ export default { confirmRemoveWhiteText2: " from the whitelist?", removeWhiteSuccess: "Successfully removed from the whitelist!", removeWhite: "Remove from Whitelist", + expirationTime:"expirationTime", }, addWhitelistedAccountButton: { notExist: "Account does not exist!", addSuccess: "Added successfully!", addWhiteList: "Add Whitelisted Account", + expirationTime:"expirationTime", + custom:"Custom", + oneWeek:"One Week", + oneMonth:"One Month", + oneYear:"One Year", + permanent:"Permanent", + }, adminJobTable: { batch: "Batch Search", diff --git a/apps/mis-web/src/i18n/zh_cn.ts b/apps/mis-web/src/i18n/zh_cn.ts index ccba590294..358a18df9f 100644 --- a/apps/mis-web/src/i18n/zh_cn.ts +++ b/apps/mis-web/src/i18n/zh_cn.ts @@ -41,6 +41,7 @@ export default { amount:"金额", unit:"元", comment:"备注", + expirationTime:"有效期", submit:"提交", time:"时间", type:"类型", @@ -232,6 +233,7 @@ export default { blockThresholdAmountTooltip: "当账户余额低于此值时,账户将被封锁", comment:"备注", + expirationTime:"有效期", status:"状态", statusTooltip: "账户状态说明", statusFrozenTooltip: "冻结:账户已被账户管理员冻结,无法通过此账户提交作业", @@ -609,11 +611,18 @@ export default { confirmRemoveWhiteText2:"从白名单移除?", removeWhiteSuccess:"移出白名单成功!", removeWhite:"从白名单中去除", + expirationTime:"有效期", }, addWhitelistedAccountButton:{ notExist:"账户不存在!", addSuccess:"添加成功!", addWhiteList:"添加白名单账户", + expirationTime:"有效期", + custom:"自定义", + oneWeek:"一周", + oneMonth:"一个月", + oneYear:"一年", + permanent:"永久有效", }, adminJobTable:{ batch:"批量搜索", diff --git a/apps/mis-web/src/models/UserSchemaModel.ts b/apps/mis-web/src/models/UserSchemaModel.ts index 645ff238d0..9077dcd232 100644 --- a/apps/mis-web/src/models/UserSchemaModel.ts +++ b/apps/mis-web/src/models/UserSchemaModel.ts @@ -131,6 +131,7 @@ export const WhitelistedAccount = Type.Object({ operatorId: Type.String(), comment: Type.String(), balance: Type.Optional(Money), + expirationTime:Type.Optional(Type.String({ format: "date-time" })), }); export type WhitelistedAccount = Static; diff --git a/apps/mis-web/src/pageComponents/tenant/AccountWhitelistTable.tsx b/apps/mis-web/src/pageComponents/tenant/AccountWhitelistTable.tsx index cf319d6e10..ff8557a7bd 100644 --- a/apps/mis-web/src/pageComponents/tenant/AccountWhitelistTable.tsx +++ b/apps/mis-web/src/pageComponents/tenant/AccountWhitelistTable.tsx @@ -179,6 +179,11 @@ export const AccountWhitelistTable: React.FC = ({ title={t(p("joinTime"))} render={(time: string) => formatDateTime(time) } /> + + dataIndex="expirationTime" + title={t(p("expirationTime"))} + render={(time: string | undefined) => time ? formatDateTime(time) : "永久有效"} + /> dataIndex="comment" title={t(pCommon("comment"))} /> dataIndex="operatorId" title={t(p("operatorId"))} /> diff --git a/apps/mis-web/src/pageComponents/tenant/AddWhitelistedAccountButton.tsx b/apps/mis-web/src/pageComponents/tenant/AddWhitelistedAccountButton.tsx index 244699c2af..6742975710 100644 --- a/apps/mis-web/src/pageComponents/tenant/AddWhitelistedAccountButton.tsx +++ b/apps/mis-web/src/pageComponents/tenant/AddWhitelistedAccountButton.tsx @@ -11,7 +11,8 @@ */ import { PlusOutlined } from "@ant-design/icons"; -import { App, Button, Form, Input, Modal } from "antd"; +import { App, Button, DatePicker, Form, Input, Modal, Select } from "antd"; +import dayjs from "dayjs"; import React, { useState } from "react"; import { api } from "src/apis"; import { prefix, useI18nTranslateToString } from "src/i18n"; @@ -19,6 +20,7 @@ import { prefix, useI18nTranslateToString } from "src/i18n"; interface FormProps { accountName: string; comment: string; + expirationTime: dayjs.Dayjs } interface ModalProps { @@ -30,6 +32,105 @@ interface ModalProps { const p = prefix("pageComp.tenant.addWhitelistedAccountButton."); const pCommon = prefix("common."); +// 过期时间选择组件的interface +interface PickExpDateProps { + id?: string; + value?: dayjs.Dayjs; + onChange?: (value: dayjs.Dayjs) => void; +} + +// 过期时间选择组件 +const PickExpDate: React.FC = (props) => { + const { id, value = dayjs(), onChange } = props; + + + const t = useI18nTranslateToString(); + + // 添加状态来跟踪选择的选项 + const [selectedOption, setSelectedOption] = useState("custom"); + + + // 定义 Select 选项数组,使用国际化函数 t() 翻译每个选项的 label + const options = [ + { value: "custom", label: t(p("custom")) }, + { value: "oneWeek", label: t(p("oneWeek")) }, + { value: "oneMonth", label: t(p("oneMonth")) }, + { value: "oneYear", label: t(p("oneYear")) }, + { value: "permanent", label: t(p("permanent")) }, + ]; + + // 定义规范时间 + const dateFormat = "YYYY-MM-DD"; + + // dateRange的时间 + const [expirationTime, setExpirationTime] = useState(dayjs()); + + // 对dateRange时间根据options选项进行处理 + React.useEffect(() => { + let newDate: dayjs.Dayjs | string; + switch (selectedOption) { + + // 一周 + case "oneWeek": + newDate = dayjs().add(1, "week"); + break; + + // 一个礼拜 + case "oneMonth": + newDate = dayjs().add(1, "month"); + break; + + // 一年 + case "oneYear": + newDate = dayjs().add(1, "year"); + break; + + // 永久生效 + case "permanent": + newDate = dayjs("2099-12-31"); + break; + + // 自定义时间 + case "custom": + newDate = expirationTime ?? dayjs(); + break; + + default: + newDate = dayjs(); + break; + } + // 传递值 + onChange?.(newDate); + // 更新组件状态 + setExpirationTime(newDate); + }, [selectedOption]); + + return ( +
+ + {/* 日期选择 */} + + form.setFieldsValue({ expirationTime: date })} + /> + @@ -82,12 +191,16 @@ interface Props { refresh: () => void; } + + export const AddWhitelistedAccountButton: React.FC = ({ refresh }) => { const [modalShow, setModalShow] = useState(false); const t = useI18nTranslateToString(); + + return ( <> setModalShow(false)} open={modalShow} refresh={refresh} /> diff --git a/apps/mis-web/src/pages/api/tenant/accountWhitelist/whitelistAccount.ts b/apps/mis-web/src/pages/api/tenant/accountWhitelist/whitelistAccount.ts index 2f6295c01b..c60ae6727d 100644 --- a/apps/mis-web/src/pages/api/tenant/accountWhitelist/whitelistAccount.ts +++ b/apps/mis-web/src/pages/api/tenant/accountWhitelist/whitelistAccount.ts @@ -29,6 +29,7 @@ export const WhitelistAccountSchema = typeboxRouteSchema({ body: Type.Object({ accountName: Type.String(), comment: Type.String(), + expirationTime:Type.String({ format: "date-time" }), }), responses: { @@ -47,7 +48,7 @@ export default route(WhitelistAccountSchema, return; } - const { accountName, comment } = req.body; + const { accountName, comment, expirationTime } = req.body; const logInfo = { operatorUserId: info.identityId, @@ -65,6 +66,7 @@ export default route(WhitelistAccountSchema, accountName, operatorId: info.identityId, comment, + expirationTime, }) .then(async () => { await callLog(logInfo, OperationResult.SUCCESS); diff --git a/protos/server/account.proto b/protos/server/account.proto index 2c3e8a8e08..a7341dd1bc 100644 --- a/protos/server/account.proto +++ b/protos/server/account.proto @@ -54,6 +54,7 @@ message WhitelistedAccount { string operator_id = 5; string comment = 6; common.Money balance = 7; + optional google.protobuf.Timestamp expiration_time = 8; } message GetWhitelistedAccountsResponse { @@ -65,6 +66,7 @@ message WhitelistAccountRequest { string account_name = 2; string operator_id = 3; string comment = 4; + optional google.protobuf.Timestamp expiration_time = 5; } // NOT_FOUND: account is not found.