This commit is contained in:
parent
caf20a1186
commit
6761ab2091
@ -44,6 +44,7 @@ func decodeUserRequest(w http.ResponseWriter, req *http.Request, dst any) error
|
||||
}
|
||||
|
||||
type userCreateRequest struct {
|
||||
GroupIDs *[]int `json:"group_ids"`
|
||||
Id int `json:"id"`
|
||||
Account string `json:"account"`
|
||||
Username string `json:"username"`
|
||||
@ -93,6 +94,15 @@ func CreateUser(ctx *context.Context, w http.ResponseWriter, req *http.Request)
|
||||
}
|
||||
user := models.User{Name: username, Account: account, Disabled: reqData.Disabled, SessionVersion: 1, SecurityVersion: 1}
|
||||
user.ApplyRole(targetRole)
|
||||
groupIDs := []int{0}
|
||||
if reqData.GroupIDs != nil {
|
||||
groupIDs = *reqData.GroupIDs
|
||||
}
|
||||
if len(groupIDs) > 0 && (len(groupIDs) != 1 || groupIDs[0] != 0) {
|
||||
if !requireAccountGroupManagement(ctx, w) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := auth.SetPassword(&user, reqData.Password); err != nil {
|
||||
if writePasswordBusy(w, err) {
|
||||
return
|
||||
@ -105,12 +115,46 @@ func CreateUser(ctx *context.Context, w http.ResponseWriter, req *http.Request)
|
||||
response.NewErrorResponse(response.ServerError, "密码设置失败", "").FPrint(w)
|
||||
return
|
||||
}
|
||||
if _, err := db.Instance.Insert(&user); err != nil {
|
||||
if db.Instance == nil {
|
||||
writeAccountGroupError(w, accountgroup.ErrDenied)
|
||||
return
|
||||
}
|
||||
tx := db.Instance.NewSession()
|
||||
defer tx.Close()
|
||||
if err := tx.Begin(); err != nil {
|
||||
writeAccountGroupError(w, err)
|
||||
return
|
||||
}
|
||||
defer tx.Rollback()
|
||||
actor := models.User{ID: ctx.UserID, SessionVersion: ctx.SessionVersion, SecurityVersion: ctx.SecurityVersion}
|
||||
actor.ApplyRole(ctx.EffectiveRole())
|
||||
if err := accountgroup.CheckWrite(tx, accountWriteGuard(ctx, actor)); err != nil {
|
||||
writeAccountGroupError(w, err)
|
||||
return
|
||||
}
|
||||
groupIDs, err = accountgroup.ValidateMembership(tx, user.EffectiveRole(), groupIDs)
|
||||
if err != nil {
|
||||
writeAccountGroupError(w, err)
|
||||
return
|
||||
}
|
||||
if _, err := tx.Insert(&user); err != nil {
|
||||
log.WithContext(ctx).WithError(err).Error("create user failed")
|
||||
response.NewErrorResponse(response.ServerError, "创建用户失败", "").FPrint(w)
|
||||
return
|
||||
}
|
||||
response.NewSuccessResponse(user).FPrint(w)
|
||||
if err := accountgroup.ReplaceMembership(tx, user.ID, groupIDs); err != nil {
|
||||
writeAccountGroupError(w, err)
|
||||
return
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
writeAccountGroupError(w, err)
|
||||
return
|
||||
}
|
||||
response.NewSuccessResponse(struct {
|
||||
models.User
|
||||
GroupIDs []int
|
||||
MembershipVersion int
|
||||
}{user, groupIDs, user.SecurityVersion}).FPrint(w)
|
||||
}
|
||||
|
||||
type userListRequest struct {
|
||||
|
||||
@ -4,7 +4,7 @@ import { http } from '@/utils/axios'
|
||||
import { useGlobalStatusStore } from '@/stores/useGlobalStatusStore'
|
||||
import { isRecentAuthenticationCancelled, requestWithRecentAuthentication } from '@/utils/recentAuthentication'
|
||||
|
||||
export function usePage() {
|
||||
export function usePage(props = {}) {
|
||||
const globalStatus = useGlobalStatusStore()
|
||||
const manager = computed(() => globalStatus.hasCapability('notification.manage'))
|
||||
const loading = ref(false)
|
||||
@ -14,7 +14,7 @@ export function usePage() {
|
||||
const accounts = ref([])
|
||||
const page = ref(1)
|
||||
const total = ref(0)
|
||||
const selectedID = ref(Number(globalStatus.userInfos?.id || 0))
|
||||
const selectedID = ref(Number(props.userId || globalStatus.userInfos?.id || 0))
|
||||
const form = reactive({ enabled: false, minute_limit: 10, day_limit: 100 })
|
||||
const secret = ref('')
|
||||
const secretAccount = ref('')
|
||||
@ -70,7 +70,7 @@ export function usePage() {
|
||||
}
|
||||
}
|
||||
const loadAccounts = async () => {
|
||||
if (!manager.value) return
|
||||
if (!manager.value || props.userId) return
|
||||
const sequence = ++accountsSequence
|
||||
try {
|
||||
const data = dataOf(await http.get('/api/v1/system/notification/accounts', { params: { page: page.value } }))
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
:closable="false"
|
||||
/>
|
||||
|
||||
<div v-if="manager" class="notification-panel">
|
||||
<div v-if="manager && !userId" class="notification-panel">
|
||||
<h3>账号授权管理</h3>
|
||||
<el-table :data="accounts" stripe>
|
||||
<el-table-column prop="account" label="账号" min-width="110" />
|
||||
@ -111,11 +111,13 @@
|
||||
<script setup>
|
||||
import { usePage } from './SystemNotification.js'
|
||||
|
||||
const props = defineProps({ userId: { type: Number, default: 0 } })
|
||||
|
||||
const {
|
||||
manager, loading, submitting, error, status, accounts, page, total, form,
|
||||
secret, secretAccount, secretVisible, seconds, canManage, stateText, dirty,
|
||||
loadAccounts, selectAccount, refresh, save, rotate, clearSecret, copySecret, beforeLeave
|
||||
} = usePage()
|
||||
} = usePage(props)
|
||||
|
||||
defineExpose({ beforeLeave })
|
||||
</script>
|
||||
|
||||
@ -305,3 +305,23 @@
|
||||
flex: 0 0 auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.account-form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
column-gap: 18px;
|
||||
}
|
||||
|
||||
.account-form-wide {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.account-form > :deep(.el-form-item) {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.account-form {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ export function usePage() {
|
||||
|
||||
const selectedMembershipGroups = computed(() => {
|
||||
const user = selectedUser.value
|
||||
if (!user) return []
|
||||
if (!user && editModel.value !== 'create') return []
|
||||
return groups.accountGroups.value.filter(group =>
|
||||
groups.canManageGroups.value
|
||||
? editingGroupIDs.value.includes(group.id)
|
||||
@ -76,8 +76,8 @@ export function usePage() {
|
||||
})
|
||||
|
||||
const openMembershipDialog = () => {
|
||||
if (submitting.value || !selectedUser.value) return
|
||||
groups.editMembership(selectedUser.value, {
|
||||
if (submitting.value || (!selectedUser.value && editModel.value !== 'create')) return
|
||||
groups.editMembership(selectedUser.value || { Role: editUserInfo.role }, {
|
||||
role: editUserInfo.role,
|
||||
groupIDs: editingGroupIDs.value,
|
||||
onApply: ids => { editingGroupIDs.value = ids }
|
||||
@ -88,6 +88,13 @@ export function usePage() {
|
||||
|
||||
const externalEmailDialogVisible = ref(false)
|
||||
|
||||
const notificationDialogVisible = ref(false)
|
||||
const notificationPanel = ref(null)
|
||||
const canManageNotification = computed(() => globalStatus.hasCapability('notification.manage'))
|
||||
const closeNotificationDialog = async done => {
|
||||
if (await notificationPanel.value?.beforeLeave()) done()
|
||||
}
|
||||
|
||||
const securityDialogOpening = ref('')
|
||||
|
||||
const submitting = ref(false)
|
||||
@ -148,6 +155,8 @@ export function usePage() {
|
||||
const createUser = function () {
|
||||
if (!canCreateUser.value) return
|
||||
selectedUser.value = null
|
||||
editingGroupIDs.value = [0]
|
||||
editingMembershipVersion.value = 0
|
||||
editUserInfo.account = ""
|
||||
editUserInfo.name = ""
|
||||
editUserInfo.disabled = false
|
||||
@ -206,12 +215,12 @@ export function usePage() {
|
||||
}
|
||||
if (creating || roleChanged.value) newData.role = editUserInfo.role
|
||||
if (creating || editUserInfo.password !== '') newData.password = editUserInfo.password
|
||||
if (!creating && groups.canManageGroups.value && (membershipChanged.value || roleChanged.value)) {
|
||||
if (groups.canManageGroups.value && (creating || membershipChanged.value || roleChanged.value)) {
|
||||
if (editUserInfo.role !== 'admin' && editingGroupIDs.value.length !== 1) {
|
||||
throw new Error('普通用户只能属于一个分组,请在分组管理中选择一个分组')
|
||||
}
|
||||
newData.group_ids = [...editingGroupIDs.value]
|
||||
newData.membership_version = editingMembershipVersion.value
|
||||
if (!creating) newData.membership_version = editingMembershipVersion.value
|
||||
}
|
||||
const res = await requestWithRecentAuthentication(() => creating
|
||||
? http.post('/api/v1/user/create', newData)
|
||||
@ -221,8 +230,12 @@ export function usePage() {
|
||||
}
|
||||
ElNotification({ title: lang.succ, message: '用户信息已保存', type: 'success' })
|
||||
editUserInfo.password = ''
|
||||
reflushList()
|
||||
userInfoDialog.value = false
|
||||
if (creating) {
|
||||
handleEdit(0, res.data)
|
||||
} else {
|
||||
userInfoDialog.value = false
|
||||
}
|
||||
await reflushList()
|
||||
} catch (error) {
|
||||
if (!isRecentAuthenticationCancelled(error)) {
|
||||
ElNotification({
|
||||
@ -262,6 +275,10 @@ export function usePage() {
|
||||
recoveryCodeWebManaged,
|
||||
recoveryCodeDialogVisible,
|
||||
externalEmailDialogVisible,
|
||||
notificationDialogVisible,
|
||||
notificationPanel,
|
||||
canManageNotification,
|
||||
closeNotificationDialog,
|
||||
securityDialogOpening,
|
||||
openRecoveryCodeDialog,
|
||||
openExternalEmailDialog,
|
||||
|
||||
@ -162,14 +162,14 @@
|
||||
<el-dialog
|
||||
v-model="userInfoDialog"
|
||||
:title="title"
|
||||
width="450px"
|
||||
width="min(640px, 94vw)"
|
||||
class="premium-dialog"
|
||||
:close-on-click-modal="!submitting"
|
||||
:close-on-press-escape="!submitting"
|
||||
:show-close="!submitting"
|
||||
>
|
||||
<div class="dialog-content">
|
||||
<el-form label-position="top">
|
||||
<el-form label-position="top" class="account-form">
|
||||
<el-form-item :label="lang.account">
|
||||
<el-input :disabled="editModel === 'edit' || submitting" v-model="editUserInfo.account" />
|
||||
</el-form-item>
|
||||
@ -211,7 +211,7 @@
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="editModel === 'edit'" label="所属分组">
|
||||
<el-form-item label="所属分组" class="account-form-wide">
|
||||
<div class="membership-summary">
|
||||
<div class="membership-tags">
|
||||
<el-tag
|
||||
@ -237,21 +237,24 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<div v-if="editModel === 'edit'" class="security-edit-grid">
|
||||
<div class="security-edit-grid account-form-wide">
|
||||
<el-tag v-if="editModel === 'create'" class="account-form-wide" type="info">
|
||||
创建后配置安全与发信
|
||||
</el-tag>
|
||||
<div class="security-edit-item">
|
||||
<div>
|
||||
<strong>安全码</strong>
|
||||
<span v-if="recoveryCodeWebManaged">
|
||||
{{ recoveryCodeMeta(selectedUser?.RecoveryCodeConfigured).label }}
|
||||
</span>
|
||||
<span v-else>部署端管理</span>
|
||||
<span v-else-if="editModel === 'edit'">部署端管理</span>
|
||||
</div>
|
||||
<el-button
|
||||
:disabled="!recoveryCodeWebManaged || submitting"
|
||||
:loading="securityDialogOpening === 'recovery-code'"
|
||||
@click="openRecoveryCodeDialog"
|
||||
>
|
||||
{{ recoveryCodeWebManaged ? '管理安全码' : '仅支持本机管理' }}
|
||||
{{ recoveryCodeWebManaged || editModel === 'create' ? '管理安全码' : '仅支持本机管理' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="security-edit-item">
|
||||
@ -264,16 +267,23 @@
|
||||
</span>
|
||||
</div>
|
||||
<el-button
|
||||
:disabled="submitting"
|
||||
:disabled="!selectedUser?.ID || submitting"
|
||||
:loading="securityDialogOpening === 'external-email'"
|
||||
@click="openExternalEmailDialog"
|
||||
>
|
||||
编辑外部邮箱
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="canManageNotification" class="security-edit-item account-form-wide">
|
||||
<strong>系统通知发信</strong>
|
||||
<el-button
|
||||
:disabled="!selectedUser?.ID || submitting"
|
||||
@click="notificationDialogVisible = true"
|
||||
>管理发信授权</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-form-item>
|
||||
<el-form-item class="account-form-wide">
|
||||
<div class="status-switch">
|
||||
<span class="switch-label">{{ lang.status }}</span>
|
||||
<el-switch
|
||||
@ -293,12 +303,28 @@
|
||||
<span class="dialog-footer">
|
||||
<el-button :disabled="submitting" @click="userInfoDialog = false">{{ lang.cancel }}</el-button>
|
||||
<el-button type="primary" :disabled="!canEditSelectedUser" :loading="submitting" @click="submit">
|
||||
{{ lang.confirm }}
|
||||
{{ editModel === 'create' ? '创建并继续配置' : lang.confirm }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
v-model="notificationDialogVisible"
|
||||
title="系统通知发信"
|
||||
width="min(760px, 94vw)"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
:before-close="closeNotificationDialog"
|
||||
>
|
||||
<SystemNotification
|
||||
v-if="notificationDialogVisible && selectedUser?.ID"
|
||||
:key="selectedUser.ID"
|
||||
ref="notificationPanel"
|
||||
:user-id="selectedUser.ID"
|
||||
/>
|
||||
</el-dialog>
|
||||
|
||||
<RecoveryCodeDialog
|
||||
v-model="recoveryCodeDialogVisible"
|
||||
:user="selectedUser"
|
||||
@ -317,6 +343,7 @@
|
||||
import { usePage, Plus } from "./UserManagement.js";
|
||||
import RecoveryCodeDialog from "./RecoveryCodeDialog/RecoveryCodeDialog.vue";
|
||||
import UserExternalEmailDialog from "./UserExternalEmailDialog/UserExternalEmailDialog.vue";
|
||||
import SystemNotification from "../SystemNotification/SystemNotification.vue";
|
||||
|
||||
const {
|
||||
lang,
|
||||
@ -358,6 +385,10 @@ const {
|
||||
recoveryCodeWebManaged,
|
||||
recoveryCodeDialogVisible,
|
||||
externalEmailDialogVisible,
|
||||
notificationDialogVisible,
|
||||
notificationPanel,
|
||||
canManageNotification,
|
||||
closeNotificationDialog,
|
||||
securityDialogOpening,
|
||||
openRecoveryCodeDialog,
|
||||
openExternalEmailDialog,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user