Skip to content

Commit

Permalink
Add system all users
Browse files Browse the repository at this point in the history
  • Loading branch information
Yat3s committed Oct 15, 2023
1 parent 5d72eff commit 24a8178
Show file tree
Hide file tree
Showing 12 changed files with 359 additions and 18 deletions.
2 changes: 1 addition & 1 deletion install/helm-single/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ hydrooj cli user setSuperAdmin 2

Helm Chart示例中尚未完全适配多节点以及HA需求。主要体现在
- Mongo的单节点部署
- 为了理解和调试便利,后端容器`/Users/zhiye/data/file``/root/.hydro`,Mongo容器`/data/db`,评测机容器`/root/.config/hydro`使用了HostPath。
- 为了理解和调试便利,后端容器`/data/file``/root/.hydro`,Mongo容器`/data/db`,评测机容器`/root/.config/hydro`使用了HostPath。


由于Judge需要以特权容器运行(cgroup所需),建议将Backend和Judge调度到不同的节点上。
Expand Down
2 changes: 1 addition & 1 deletion install/helm-single/templates/backend.deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ spec:
image: {{.Values.Backend.Image}}
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /Users/zhiye/data/file
- mountPath: /data/file
name: file-volume
- mountPath: /root/.hydro
name: backend-volume
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"lint:ui": "yarn workspace @hydrooj/ui-default lint --ext .js,.ts,.jsx,.tsx . --fix",
"lint:ui:ci": "yarn workspace @hydrooj/ui-default lint --ext .js,.ts,.jsx,.tsx .",
"server": "node --trace-warnings --async-stack-traces --trace-deprecation packages/hydrooj/bin/hydrooj --debug --template --port=2333 --watch",
"server-local": "cross-env DEFAULT_STORE_PATH=/Users/zhiye/data/file/hydro node --trace-warnings --async-stack-traces --trace-deprecation packages/hydrooj/bin/hydrooj --debug --template --port=2333 --watch",
"start": "node packages/hydrooj/bin/hydrooj --port=8888",
"postinstall": "node build/prepare.js",
"gen-patch": "node -r @hydrooj/utils/lib/register build/gen-patch.ts"
Expand Down
1 change: 1 addition & 0 deletions packages/hydrooj/locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ domain_main: Main
domain_permission: System Permission
domain_role: System Role
domain_user: System User
system_all_users: All Users
fs_upload: File Upload
home_account: Account Settings
home_domain_account: Profile @ Domain
Expand Down
15 changes: 8 additions & 7 deletions packages/hydrooj/locales/zh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -260,17 +260,18 @@ Domain {0} is bulit-in and cannot be modified.: 网站 {0} 为内置,不可修
Domain {0} not found.: 网站 {0} 不存在。
Domain ID cannot be changed once the domain is created.: 在创建后不能更改 ID。
Domain ID: 网站 ID
domain_dashboard: 管理网站
domain_dashboard: 管理站点
domain_discussion: 讨论节点
domain_edit: 编辑网站资料
domain_edit: 编辑站点资料
domain_file: 我的文件
domain_group: 小组管理
domain_join_applications: 添加子网站
domain_join: 加入网站
domain_join_applications: 添加子站点
domain_join: 加入站点
domain_main: 首页
domain_permission: 权限管理
domain_role: 角色管理
domain_user: 子管理员管理
domain_user: 系统用户管理
system_all_users: 全站用户管理
domain: 网站
Domain: 网站
Don't have an account?: 还没有账户?
Expand Down Expand Up @@ -357,7 +358,7 @@ Hint: 提示
home_account: 账户设置
home_domain_account: 当前网站的设置
home_domain_create: 创建网站
home_domain: 我的网站
home_domain: 我的站点
home_files: 我的文件
home_messages: 站内消息
home_preference: 偏好设置
Expand Down Expand Up @@ -478,7 +479,7 @@ monacoTheme: 编辑器主题
Month:
Monthly Popular: 月度最受欢迎
Most Upvoted Solutions: 最被赞同的题解
My Domains: 我的网站
My Domains: 我的站点
My Files: 我的文件
My Profile: 我的资料
My Recent Submissions: 我的最近递交记录
Expand Down
52 changes: 51 additions & 1 deletion packages/hydrooj/src/handler/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class DomainRankHandler extends Handler {
@query('page', Types.PositiveInt, true)
async get(domainId: string, page = 1) {
const [dudocs, upcount, ucount] = await paginate(
domain.getMultiUserInDomain(domainId, { uid: { $gt: 1 }, rp: { $gt: 0 } }).sort({ rp: -1 }),
domain.getMultiUserInDomain(domainId, { uid: { $gt: 1 } }).sort({ rp: -1 }),
page,
100,
);
Expand Down Expand Up @@ -185,6 +185,55 @@ class DomainUserHandler extends ManageHandler {
}
}

class SystemAllUserHandler extends ManageHandler {
@requireSudo
async get({ domainId }) {
if (domainId !== 'system') {
throw new ForbiddenError(
'Only system domain can view all users',
);
}

const [roles] = await Promise.all([
domain.getRoles(domainId),
]);
const allUsers = (await user.fetchAllUsers()).filter((u) => u._id > 1);
// Set gender string for all users
allUsers.forEach((u) => {
u.genderStr = u.gender === 0 ? "男" : u.gender === 1 ? "女" : "未填写";

Check warning on line 203 in packages/hydrooj/src/handler/domain.ts

View workflow job for this annotation

GitHub Actions / build

Strings must use singlequote

Check warning on line 203 in packages/hydrooj/src/handler/domain.ts

View workflow job for this annotation

GitHub Actions / build

Strings must use singlequote

Check warning on line 203 in packages/hydrooj/src/handler/domain.ts

View workflow job for this annotation

GitHub Actions / build

Strings must use singlequote
});

this.response.template = 'system_all_users.html';
this.response.body = {
roles, allUsers, domain: this.domain,
};
}

@requireSudo
@post('uid', Types.Int)
@post('role', Types.Role)
async postSetUser(domainId: string, uid: number, role: string) {
if (uid === this.domain.owner) throw new ForbiddenError();
await Promise.all([
domain.setUserRole(domainId, uid, role),
oplog.log(this, 'domain.setRole', { uid, role }),
]);
this.back();
}

@requireSudo
@param('uid', Types.NumericArray)
@param('role', Types.Role)
async postSetUsers(domainId: string, uid: number[], role: string) {
if (uid.includes(this.domain.owner)) throw new ForbiddenError();
await Promise.all([
domain.setUserRole(domainId, uid, role),
oplog.log(this, 'domain.setRole', { uid, role }),
]);
this.back();
}
}

class DomainPermissionHandler extends ManageHandler {
@requireSudo
async get({ domainId }) {
Expand Down Expand Up @@ -367,6 +416,7 @@ export async function apply(ctx: Context) {
ctx.Route('domain_dashboard', '/domain/dashboard', DomainDashboardHandler);
ctx.Route('domain_edit', '/domain/edit', DomainEditHandler);
ctx.Route('domain_user', '/domain/user', DomainUserHandler);
ctx.Route('system_all_users', '/domain/allusers', SystemAllUserHandler);
ctx.Route('domain_permission', '/domain/permission', DomainPermissionHandler);
ctx.Route('domain_role', '/domain/role', DomainRoleHandler);
ctx.Route('domain_group', '/domain/group', DomainUserGroupHandler);
Expand Down
1 change: 1 addition & 0 deletions packages/hydrooj/src/lib/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ inject('DomainManage', 'domain_edit', { family: 'Properties', icon: 'info' });
inject('DomainManage', 'domain_join_applications', { family: 'Properties', icon: 'info' });
inject('DomainManage', 'domain_role', { family: 'Access Control', icon: 'user' });
inject('DomainManage', 'domain_user', { family: 'Access Control', icon: 'user' });
inject('DomainManage', 'system_all_users', { family: 'Access Control', icon: 'user' });
inject('DomainManage', 'domain_permission', { family: 'Access Control', icon: 'user' });
inject('DomainManage', 'domain_group', { family: 'Access Control', icon: 'user' });

Expand Down
9 changes: 9 additions & 0 deletions packages/hydrooj/src/model/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,15 @@ class UserModel {
return r;
}

static async fetchAllUsers(): Promise<User[]> {
const udocs = await coll.find({}).toArray();
const r: User[] = [];
for (const udoc of udocs) {
r.push(new User(udoc, {}));
}
return r;
}

@ArgMethod
static async getByUname(domainId: string, uname: string): Promise<User | null> {
const unameLower = uname.trim().toLowerCase();
Expand Down
15 changes: 8 additions & 7 deletions packages/ui-default/locales/zh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -298,12 +298,13 @@ Domain ID: 网站 ID
Domain Settings: 网站设置
domain_dashboard: 管理网站
domain_discussion: 讨论节点
domain_edit: 编辑网站资料
domain_join: 加入网站
domain_edit: 编辑站点资料
domain_join: 加入站点
domain_main: 首页
domain_permission: 管理权限
domain_role: 管理角色
domain_user: 管理用户
domain_permission: 权限管理
domain_role: 角色管理
domain_user: 系统用户管理
system_all_users: 全站用户管理
domain: 网站
Domain: 网站
Don't have an account?: 还没有账户?
Expand Down Expand Up @@ -413,7 +414,7 @@ Hitokoto: 一言
home_account: 账户设置
home_domain_account: 当前网站的设置
home_domain_create: 创建网站
home_domain: 我的网站
home_domain: 我的站点
home_messages: 站内消息
home_preference: 偏好设置
home_security: 安全设置
Expand Down Expand Up @@ -537,7 +538,7 @@ More: 更多
Most Upvoted Solutions: 最被赞同的题解
Move to subtask: 移动到子任务
Multi Platform Authenticator: 跨平台认证器
My Domains: 我的网站
My Domains: 我的站点
My Files: 我的文件
My Profile: 我的资料
My Recent Submissions: 我的最近递交记录
Expand Down
155 changes: 155 additions & 0 deletions packages/ui-default/pages/system_all_users.page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import $ from 'jquery';
import _ from 'lodash';
import UserSelectAutoComplete from 'vj/components/autocomplete/UserSelectAutoComplete';
import { ActionDialog, ConfirmDialog } from 'vj/components/dialog';
import Notification from 'vj/components/notification';
import { NamedPage } from 'vj/misc/Page';
import {
delay, i18n, request, tpl,
} from 'vj/utils';

const page = new NamedPage('system_all_users', () => {
const addUserSelector = UserSelectAutoComplete.getOrConstruct($('.dialog__body--add-user [name="user"]'));
const addUserDialog = new ActionDialog({
$body: $('.dialog__body--add-user > div'),
onDispatch(action) {
const $role = addUserDialog.$dom.find('[name="role"]');
if (action === 'ok') {
if (addUserSelector.value() === null) {
addUserSelector.focus();
return false;
}
if ($role.val() === '') {
$role.focus();
return false;
}
}
return true;
},
});
addUserDialog.clear = function () {
addUserSelector.clear();
this.$dom.find('[name="role"]').val('');
return this;
};

const setRolesDialog = new ActionDialog({
$body: $('.dialog__body--set-role > div'),
onDispatch(action) {
const $role = setRolesDialog.$dom.find('[name="role"]');
if (action === 'ok' && $role.val() === '') {
$role.focus();
return false;
}
return true;
},
});
setRolesDialog.clear = function () {
this.$dom.find('[name="role"]').val('');
return this;
};

async function handleClickAddUser() {
const action = await addUserDialog.clear().open();
if (action !== 'ok') {
return;
}
const user = addUserSelector.value();
const role = addUserDialog.$dom.find('[name="role"]').val();
try {
await request.post('', {
operation: 'set_user',
uid: user._id,
role,
});
window.location.reload();
} catch (error) {
Notification.error(error.message);
}
}

function ensureAndGetSelectedUsers() {
const users = _.map(
$('.domain-users tbody [type="checkbox"]:checked'),
(ch) => $(ch).closest('tr').attr('data-uid'),
);
if (users.length === 0) {
Notification.error(i18n('Please select at least one user to perform this operation.'));
return null;
}
return users;
}

async function handleClickRemoveSelected() {
const selectedUsers = ensureAndGetSelectedUsers();
if (selectedUsers === null) {
return;
}
const action = await new ConfirmDialog({
$body: tpl`
<div class="typo">
<p>${i18n('Confirm removing the selected users?')}</p>
<p>${i18n('Their account will not be deleted and they will be with the default role.')}</p>
</div>`,
}).open();
if (action !== 'yes') return;
try {
await request.post('', {
operation: 'set_users',
uid: selectedUsers,
role: 'default',
});
Notification.success(i18n('Selected users have been removed from the domain.'));
await delay(2000);
window.location.reload();
} catch (error) {
Notification.error(error.message);
}
}

async function handleClickSetSelected() {
const selectedUsers = ensureAndGetSelectedUsers();
if (selectedUsers === null) {
return;
}
const action = await setRolesDialog.clear().open();
if (action !== 'ok') {
return;
}
const role = setRolesDialog.$dom.find('[name="role"]').val();
try {
await request.post('', {
operation: 'set_users',
uid: selectedUsers,
role,
});
Notification.success(i18n('Role has been updated to {0} for selected users.', role));
await delay(2000);
window.location.reload();
} catch (error) {
Notification.error(error.message);
}
}

async function handleChangeUserRole(ev) {
const row = $(ev.currentTarget).closest('tr');
const role = $(ev.currentTarget).val();
try {
await request.post('', {
operation: 'set_user',
uid: row.attr('data-uid'),
role,
});
Notification.success(i18n('Role has been updated to {0}.', role));
} catch (error) {
Notification.error(error.message);
}
}

$('[name="add_user"]').click(() => handleClickAddUser());
$('[name="remove_selected"]').click(() => handleClickRemoveSelected());
$('[name="set_roles"]').click(() => handleClickSetSelected());
$('.domain-users [name="role"]').change((ev) => handleChangeUserRole(ev));
});

export default page;
2 changes: 1 addition & 1 deletion packages/ui-default/templates/domain_user.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ <h1>{{ _('Set Role') }}</h1>
</div>
<div class="section">
<div class="section__header mb-4">
<h1 class="section__title font-medium">{{ _('Kathy: 用户').format(domain.name) }}</h1>
<h1 class="section__title font-medium">{{ _('Kathy: 系统用户管理').format(domain.name) }}</h1>
<div class="section__tools">
<button class="primary rounded button" name="add_user">{{ _('Add User') }}</button>
</div>
Expand Down
Loading

0 comments on commit 24a8178

Please sign in to comment.