mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-04-09 18:03:49 +02:00
* refactor: dialogs * fix: storeAvatarImage * FriendLog.vue * FriendLog.vue * FriendLog.vue * GameLog.vue * fix: next day button jumping to the wrong date * sync master * fix: launchGame * Notification.vue * Feed.vue * Search.vue * Profile.vue * PlayerList.vue * Login.vue * utils * update dialog * del gameLog.pug * fix * fix: group role cannot be displayed currently * fix: "Hide Friends in Same Instance" hides players in unrelated private instances (#1210) * fix * fix: "Hide Friends in Same Instance" does not work when "Split Favorite Friends" is enabled * fix Notification.vue message * fix: deleteFavoriteNoConfirm * fix: feed status style * fix: infinite loading when deleting note * fix: private players will not be hidden when 'Hide Friends in Same Instance', and 'Hide Friends in Same Instance' will not work when 'Split Favorite Friends'
1685 lines
76 KiB
Vue
1685 lines
76 KiB
Vue
<template>
|
|
<safe-dialog
|
|
class="x-dialog"
|
|
:visible="groupMemberModeration.visible"
|
|
:title="t('dialog.group_member_moderation.header')"
|
|
append-to-body
|
|
top="5vh"
|
|
width="90vw"
|
|
@close="closeDialog">
|
|
<div>
|
|
<h3>{{ groupMemberModeration.groupRef.name }}</h3>
|
|
<el-tabs type="card" style="height: 100%">
|
|
<el-tab-pane :label="t('dialog.group_member_moderation.members')">
|
|
<div style="margin-top: 10px">
|
|
<el-button
|
|
type="default"
|
|
size="mini"
|
|
icon="el-icon-refresh"
|
|
:loading="isGroupMembersLoading"
|
|
circle
|
|
@click="loadAllGroupMembers"></el-button>
|
|
<span style="font-size: 14px; margin-left: 5px; margin-right: 5px">
|
|
{{ groupMemberModerationTable.data.length }}/{{
|
|
groupMemberModeration.groupRef.memberCount
|
|
}}
|
|
</span>
|
|
<div style="float: right; margin-top: 5px">
|
|
<span style="margin-right: 5px">{{ t('dialog.group.members.sort_by') }}</span>
|
|
<el-dropdown
|
|
trigger="click"
|
|
size="small"
|
|
style="margin-right: 5px"
|
|
:disabled="
|
|
Boolean(
|
|
isGroupMembersLoading ||
|
|
groupDialog.memberSearch.length ||
|
|
!hasGroupPermission(groupDialog.ref, 'group-bans-manage')
|
|
)
|
|
"
|
|
@click.native.stop>
|
|
<el-button size="mini">
|
|
<span
|
|
>{{ groupDialog.memberSortOrder.name }}
|
|
<i class="el-icon-arrow-down el-icon--right"></i
|
|
></span>
|
|
</el-button>
|
|
<el-dropdown-menu slot="dropdown">
|
|
<el-dropdown-item
|
|
v-for="item in groupDialogSortingOptions"
|
|
:key="item.name"
|
|
@click.native="setGroupMemberSortOrder(item)">
|
|
{{ item.name }}
|
|
</el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
<span style="margin-right: 5px">{{ t('dialog.group.members.filter') }}</span>
|
|
<el-dropdown
|
|
trigger="click"
|
|
size="small"
|
|
style="margin-right: 5px"
|
|
:disabled="
|
|
Boolean(
|
|
isGroupMembersLoading ||
|
|
groupDialog.memberSearch.length ||
|
|
!hasGroupPermission(groupDialog.ref, 'group-bans-manage')
|
|
)
|
|
"
|
|
@click.native.stop>
|
|
<el-button size="mini">
|
|
<span
|
|
>{{ groupDialog.memberFilter.name }}
|
|
<i class="el-icon-arrow-down el-icon--right"></i
|
|
></span>
|
|
</el-button>
|
|
<el-dropdown-menu slot="dropdown">
|
|
<el-dropdown-item
|
|
v-for="item in groupDialogFilterOptions"
|
|
:key="item.name"
|
|
@click.native="setGroupMemberFilter(item)"
|
|
v-text="item.name"></el-dropdown-item>
|
|
<el-dropdown-item
|
|
v-for="item in groupDialog.ref.roles"
|
|
v-if="!item.defaultRole"
|
|
:key="item.name"
|
|
@click.native="setGroupMemberFilter(item)"
|
|
v-text="item.name"></el-dropdown-item>
|
|
</el-dropdown-menu>
|
|
</el-dropdown>
|
|
</div>
|
|
<el-input
|
|
v-model="groupDialog.memberSearch"
|
|
:disabled="!hasGroupPermission(groupDialog.ref, 'group-bans-manage')"
|
|
clearable
|
|
size="mini"
|
|
:placeholder="t('dialog.group.members.search')"
|
|
style="margin-top: 10px; margin-bottom: 10px"
|
|
@input="groupMembersSearch"></el-input>
|
|
<br />
|
|
<el-button size="small" @click="selectAllGroupMembers">{{
|
|
t('dialog.group_member_moderation.select_all')
|
|
}}</el-button>
|
|
<data-tables v-bind="groupMemberModerationTable" style="margin-top: 10px">
|
|
<el-table-column width="55" prop="$selected">
|
|
<template slot-scope="scope">
|
|
<el-button type="text" size="mini" @click.stop>
|
|
<el-checkbox
|
|
v-model="scope.row.$selected"
|
|
@change="
|
|
groupMemberModerationTableSelectionChange(scope.row)
|
|
"></el-checkbox>
|
|
</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.avatar')"
|
|
width="70"
|
|
prop="photo">
|
|
<template slot-scope="scope">
|
|
<el-popover placement="right" height="500px" trigger="hover">
|
|
<img
|
|
slot="reference"
|
|
v-lazy="userImage(scope.row.user)"
|
|
class="friends-list-avatar" />
|
|
<img
|
|
v-lazy="userImageFull(scope.row.user)"
|
|
class="friends-list-avatar"
|
|
style="height: 500px; cursor: pointer"
|
|
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
|
</el-popover>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.display_name')"
|
|
width="160"
|
|
prop="$displayName"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span style="cursor: pointer" @click="showUserDialog(scope.row.userId)">
|
|
<span
|
|
v-if="randomUserColours"
|
|
:style="{ color: scope.row.user.$userColour }"
|
|
v-text="scope.row.user.displayName"></span>
|
|
<span v-else v-text="scope.row.user.displayName"></span>
|
|
</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column :label="t('dialog.group_member_moderation.roles')" prop="roleIds" sortable>
|
|
<template slot-scope="scope">
|
|
<template v-for="(roleId, index) in scope.row.roleIds">
|
|
<span
|
|
v-for="(role, rIndex) in groupMemberModeration.groupRef.roles"
|
|
v-if="role?.id === roleId"
|
|
:key="rIndex"
|
|
>{{ role.name
|
|
}}<span v-if="index < scope.row.roleIds.length - 1">, </span></span
|
|
>
|
|
</template>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.notes')"
|
|
prop="managerNotes"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span @click.stop v-text="scope.row.managerNotes"></span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.joined_at')"
|
|
width="170"
|
|
prop="joinedAt"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span>{{ scope.row.joinedAt | formatDate('long') }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.visibility')"
|
|
width="120"
|
|
prop="visibility"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span v-text="scope.row.visibility"></span>
|
|
</template>
|
|
</el-table-column>
|
|
</data-tables>
|
|
</div>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane
|
|
:label="t('dialog.group_member_moderation.bans')"
|
|
:disabled="!hasGroupPermission(groupDialog.ref, 'group-bans-manage')">
|
|
<div style="margin-top: 10px">
|
|
<el-button
|
|
type="default"
|
|
size="mini"
|
|
icon="el-icon-refresh"
|
|
:loading="isGroupMembersLoading"
|
|
circle
|
|
@click="getAllGroupBans(groupMemberModeration.id)"></el-button>
|
|
<span style="font-size: 14px; margin-left: 5px; margin-right: 5px">{{
|
|
groupBansModerationTable.data.length
|
|
}}</span>
|
|
<br />
|
|
<el-input
|
|
v-model="groupBansModerationTable.filters[0].value"
|
|
clearable
|
|
size="mini"
|
|
:placeholder="t('dialog.group.members.search')"
|
|
style="margin-top: 10px; margin-bottom: 10px"></el-input>
|
|
<br />
|
|
<el-button size="small" @click="selectAllGroupBans">{{
|
|
t('dialog.group_member_moderation.select_all')
|
|
}}</el-button>
|
|
<data-tables v-bind="groupBansModerationTable" style="margin-top: 10px">
|
|
<el-table-column width="55" prop="$selected">
|
|
<template slot-scope="scope">
|
|
<el-button type="text" size="mini" @click.stop>
|
|
<el-checkbox
|
|
v-model="scope.row.$selected"
|
|
@change="
|
|
groupMemberModerationTableSelectionChange(scope.row)
|
|
"></el-checkbox>
|
|
</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.avatar')"
|
|
width="70"
|
|
prop="photo">
|
|
<template slot-scope="scope">
|
|
<el-popover placement="right" height="500px" trigger="hover">
|
|
<img
|
|
slot="reference"
|
|
v-lazy="userImage(scope.row.user)"
|
|
class="friends-list-avatar" />
|
|
<img
|
|
v-lazy="userImageFull(scope.row.user)"
|
|
class="friends-list-avatar"
|
|
style="height: 500px; cursor: pointer"
|
|
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
|
</el-popover>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.display_name')"
|
|
width="160"
|
|
prop="$displayName"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span style="cursor: pointer" @click="showUserDialog(scope.row.userId)">
|
|
<span
|
|
v-if="randomUserColours"
|
|
:style="{ color: scope.row.user.$userColour }"
|
|
v-text="scope.row.user.displayName"></span>
|
|
<span v-else v-text="scope.row.user.displayName"></span>
|
|
</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column :label="t('dialog.group_member_moderation.roles')" prop="roleIds" sortable>
|
|
<template slot-scope="scope">
|
|
<template v-for="(roleId, index) in scope.row.roleIds">
|
|
<span
|
|
v-for="(role, rIndex) in groupMemberModeration.groupRef.roles"
|
|
v-if="role.id === roleId"
|
|
:key="rIndex"
|
|
>{{ role.name }}</span
|
|
>
|
|
<span v-if="index < scope.row.roleIds.length - 1">, </span>
|
|
</template>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.notes')"
|
|
prop="managerNotes"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span @click.stop v-text="scope.row.managerNotes"></span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.joined_at')"
|
|
width="170"
|
|
prop="joinedAt"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span>{{ scope.row.joinedAt | formatDate('long') }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.banned_at')"
|
|
width="170"
|
|
prop="bannedAt"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span>{{ scope.row.bannedAt | formatDate('long') }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
</data-tables>
|
|
</div>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane
|
|
:label="t('dialog.group_member_moderation.invites')"
|
|
:disabled="!hasGroupPermission(groupDialog.ref, 'group-invites-manage')">
|
|
<div style="margin-top: 10px">
|
|
<el-button
|
|
type="default"
|
|
size="mini"
|
|
icon="el-icon-refresh"
|
|
:loading="isGroupMembersLoading"
|
|
circle
|
|
@click="getAllGroupInvitesAndJoinRequests(groupMemberModeration.id)"></el-button>
|
|
<br />
|
|
<el-tabs>
|
|
<el-tab-pane>
|
|
<span slot="label">
|
|
<span style="font-weight: bold; font-size: 16px">{{
|
|
t('dialog.group_member_moderation.sent_invites')
|
|
}}</span>
|
|
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{
|
|
groupInvitesModerationTable.data.length
|
|
}}</span>
|
|
</span>
|
|
<el-button size="small" @click="selectAllGroupInvites">{{
|
|
t('dialog.group_member_moderation.select_all')
|
|
}}</el-button>
|
|
<data-tables v-bind="groupInvitesModerationTable" style="margin-top: 10px">
|
|
<el-table-column width="55" prop="$selected">
|
|
<template slot-scope="scope">
|
|
<el-button type="text" size="mini" @click.stop>
|
|
<el-checkbox
|
|
v-model="scope.row.$selected"
|
|
@change="
|
|
groupMemberModerationTableSelectionChange(scope.row)
|
|
"></el-checkbox>
|
|
</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.avatar')"
|
|
width="70"
|
|
prop="photo">
|
|
<template slot-scope="scope">
|
|
<el-popover placement="right" height="500px" trigger="hover">
|
|
<img
|
|
slot="reference"
|
|
v-lazy="userImage(scope.row.user)"
|
|
class="friends-list-avatar" />
|
|
<img
|
|
v-lazy="userImageFull(scope.row.user)"
|
|
class="friends-list-avatar"
|
|
style="height: 500px; cursor: pointer"
|
|
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
|
</el-popover>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.display_name')"
|
|
width="160"
|
|
prop="$displayName"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span style="cursor: pointer" @click="showUserDialog(scope.row.userId)">
|
|
<span
|
|
v-if="randomUserColours"
|
|
:style="{ color: scope.row.user.$userColour }"
|
|
v-text="scope.row.user.displayName"></span>
|
|
<span v-else v-text="scope.row.user.displayName"></span>
|
|
</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.notes')"
|
|
prop="managerNotes"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span @click.stop v-text="scope.row.managerNotes"></span>
|
|
</template>
|
|
</el-table-column>
|
|
</data-tables>
|
|
<br />
|
|
<el-button
|
|
:disabled="
|
|
Boolean(
|
|
progressCurrent ||
|
|
!hasGroupPermission(groupDialog.ref, 'group-invites-manage')
|
|
)
|
|
"
|
|
@click="groupMembersDeleteSentInvite"
|
|
>{{ t('dialog.group_member_moderation.delete_sent_invite') }}</el-button
|
|
>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane>
|
|
<span slot="label">
|
|
<span style="font-weight: bold; font-size: 16px">{{
|
|
t('dialog.group_member_moderation.join_requests')
|
|
}}</span>
|
|
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{
|
|
groupJoinRequestsModerationTable.data.length
|
|
}}</span>
|
|
</span>
|
|
<el-button size="small" @click="selectAllGroupJoinRequests">{{
|
|
t('dialog.group_member_moderation.select_all')
|
|
}}</el-button>
|
|
<data-tables v-bind="groupJoinRequestsModerationTable" style="margin-top: 10px">
|
|
<el-table-column width="55" prop="$selected">
|
|
<template slot-scope="scope">
|
|
<el-button type="text" size="mini" @click.stop>
|
|
<el-checkbox
|
|
v-model="scope.row.$selected"
|
|
@change="
|
|
groupMemberModerationTableSelectionChange(scope.row)
|
|
"></el-checkbox>
|
|
</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.avatar')"
|
|
width="70"
|
|
prop="photo">
|
|
<template slot-scope="scope">
|
|
<el-popover placement="right" height="500px" trigger="hover">
|
|
<img
|
|
slot="reference"
|
|
v-lazy="userImage(scope.row.user)"
|
|
class="friends-list-avatar" />
|
|
<img
|
|
v-lazy="userImageFull(scope.row.user)"
|
|
class="friends-list-avatar"
|
|
style="height: 500px; cursor: pointer"
|
|
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
|
</el-popover>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.display_name')"
|
|
width="160"
|
|
prop="$displayName"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span style="cursor: pointer" @click="showUserDialog(scope.row.userId)">
|
|
<span
|
|
v-if="randomUserColours"
|
|
:style="{ color: scope.row.user.$userColour }"
|
|
v-text="scope.row.user.displayName"></span>
|
|
<span v-else v-text="scope.row.user.displayName"></span>
|
|
</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.notes')"
|
|
prop="managerNotes"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span @click.stop v-text="scope.row.managerNotes"></span>
|
|
</template>
|
|
</el-table-column>
|
|
</data-tables>
|
|
<br />
|
|
<el-button
|
|
:disabled="
|
|
Boolean(
|
|
progressCurrent ||
|
|
!hasGroupPermission(groupDialog.ref, 'group-invites-manage')
|
|
)
|
|
"
|
|
@click="groupMembersAcceptInviteRequest"
|
|
>{{ t('dialog.group_member_moderation.accept_join_requests') }}</el-button
|
|
>
|
|
<el-button
|
|
:disabled="
|
|
Boolean(
|
|
progressCurrent ||
|
|
!hasGroupPermission(groupDialog.ref, 'group-invites-manage')
|
|
)
|
|
"
|
|
@click="groupMembersRejectInviteRequest"
|
|
>{{ t('dialog.group_member_moderation.reject_join_requests') }}</el-button
|
|
>
|
|
<el-button
|
|
:disabled="
|
|
Boolean(
|
|
progressCurrent ||
|
|
!hasGroupPermission(groupDialog.ref, 'group-invites-manage')
|
|
)
|
|
"
|
|
@click="groupMembersBlockJoinRequest"
|
|
>{{ t('dialog.group_member_moderation.block_join_requests') }}</el-button
|
|
>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane>
|
|
<span slot="label">
|
|
<span style="font-weight: bold; font-size: 16px">{{
|
|
t('dialog.group_member_moderation.blocked_requests')
|
|
}}</span>
|
|
<span style="color: #909399; font-size: 12px; margin-left: 5px">{{
|
|
groupBlockedModerationTable.data.length
|
|
}}</span>
|
|
</span>
|
|
<el-button size="small" @click="selectAllGroupBlocked">{{
|
|
t('dialog.group_member_moderation.select_all')
|
|
}}</el-button>
|
|
<data-tables v-bind="groupBlockedModerationTable" style="margin-top: 10px">
|
|
<el-table-column width="55" prop="$selected">
|
|
<template slot-scope="scope">
|
|
<el-button type="text" size="mini" @click.stop>
|
|
<el-checkbox
|
|
v-model="scope.row.$selected"
|
|
@change="
|
|
groupMemberModerationTableSelectionChange(scope.row)
|
|
"></el-checkbox>
|
|
</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.avatar')"
|
|
width="70"
|
|
prop="photo">
|
|
<template slot-scope="scope">
|
|
<el-popover placement="right" height="500px" trigger="hover">
|
|
<img
|
|
slot="reference"
|
|
v-lazy="userImage(scope.row.user)"
|
|
class="friends-list-avatar" />
|
|
<img
|
|
v-lazy="userImageFull(scope.row.user)"
|
|
class="friends-list-avatar"
|
|
style="height: 500px; cursor: pointer"
|
|
@click="showFullscreenImageDialog(userImageFull(scope.row.user))" />
|
|
</el-popover>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.display_name')"
|
|
width="160"
|
|
prop="$displayName"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span style="cursor: pointer" @click="showUserDialog(scope.row.userId)">
|
|
<span
|
|
v-if="randomUserColours"
|
|
:style="{ color: scope.row.user.$userColour }"
|
|
v-text="scope.row.user.displayName"></span>
|
|
<span v-else v-text="scope.row.user.displayName"></span>
|
|
</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.notes')"
|
|
prop="managerNotes"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span @click.stop v-text="scope.row.managerNotes"></span>
|
|
</template>
|
|
</el-table-column>
|
|
</data-tables>
|
|
<br />
|
|
<el-button
|
|
:disabled="
|
|
Boolean(
|
|
progressCurrent ||
|
|
!hasGroupPermission(groupDialog.ref, 'group-invites-manage')
|
|
)
|
|
"
|
|
@click="groupMembersDeleteBlockedRequest"
|
|
>{{ t('dialog.group_member_moderation.delete_blocked_requests') }}</el-button
|
|
>
|
|
</el-tab-pane>
|
|
</el-tabs>
|
|
</div>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane
|
|
:label="t('dialog.group_member_moderation.logs')"
|
|
:disabled="!hasGroupPermission(groupDialog.ref, 'group-audit-view')">
|
|
<div style="margin-top: 10px">
|
|
<el-button
|
|
type="default"
|
|
size="mini"
|
|
icon="el-icon-refresh"
|
|
:loading="isGroupMembersLoading"
|
|
circle
|
|
@click="getAllGroupLogs(groupMemberModeration.id)"></el-button>
|
|
<span style="font-size: 14px; margin-left: 5px; margin-right: 5px">{{
|
|
groupLogsModerationTable.data.length
|
|
}}</span>
|
|
<br />
|
|
<div style="display: flex; justify-content: space-between; align-items: center">
|
|
<div>
|
|
<el-select
|
|
v-model="selectedAuditLogTypes"
|
|
multiple
|
|
collapse-tags
|
|
:placeholder="t('dialog.group_member_moderation.filter_type')"
|
|
style="margin: 10px 0; width: 250px">
|
|
<el-option-group :label="t('dialog.group_member_moderation.select_type')">
|
|
<el-option
|
|
v-for="type in groupMemberModeration.auditLogTypes"
|
|
:key="type"
|
|
class="x-friend-item"
|
|
:label="getAuditLogTypeName(type)"
|
|
:value="type">
|
|
<div class="detail">
|
|
<span class="name" v-text="getAuditLogTypeName(type)"></span>
|
|
</div>
|
|
</el-option>
|
|
</el-option-group>
|
|
</el-select>
|
|
<el-input
|
|
v-model="groupLogsModerationTable.filters[0].value"
|
|
:placeholder="t('dialog.group_member_moderation.search_placeholder')"
|
|
style="display: inline-block; width: 150px; margin: 10px"
|
|
clearable></el-input>
|
|
</div>
|
|
<div>
|
|
<el-button @click="showGroupLogsExportDialog">{{
|
|
t('dialog.group_member_moderation.export_logs')
|
|
}}</el-button>
|
|
</div>
|
|
</div>
|
|
<br />
|
|
<data-tables v-bind="groupLogsModerationTable" style="margin-top: 10px">
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.created_at')"
|
|
width="170"
|
|
prop="created_at"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span>{{ scope.row.created_at | formatDate('long') }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.type')"
|
|
width="190"
|
|
prop="eventType"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span v-text="scope.row.eventType"></span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.display_name')"
|
|
width="160"
|
|
prop="actorDisplayName"
|
|
sortable>
|
|
<template slot-scope="scope">
|
|
<span style="cursor: pointer" @click="showUserDialog(scope.row.actorId)">
|
|
<span v-text="scope.row.actorDisplayName"></span>
|
|
</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column
|
|
:label="t('dialog.group_member_moderation.description')"
|
|
prop="description">
|
|
<template slot-scope="scope">
|
|
<span v-text="scope.row.description"></span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column :label="t('dialog.group_member_moderation.data')" prop="data">
|
|
<template slot-scope="scope">
|
|
<span
|
|
v-if="Object.keys(scope.row.data).length"
|
|
v-text="JSON.stringify(scope.row.data)"></span>
|
|
</template>
|
|
</el-table-column>
|
|
</data-tables>
|
|
</div>
|
|
</el-tab-pane>
|
|
</el-tabs>
|
|
|
|
<br />
|
|
<br />
|
|
<span class="name">{{ t('dialog.group_member_moderation.user_id') }}</span>
|
|
<br />
|
|
<el-input
|
|
v-model="selectUserId"
|
|
size="mini"
|
|
style="margin-top: 5px; width: 340px"
|
|
:placeholder="t('dialog.group_member_moderation.user_id_placeholder')"
|
|
clearable></el-input>
|
|
<el-button size="small" :disabled="!selectUserId" @click="selectGroupMemberUserId">{{
|
|
t('dialog.group_member_moderation.select_user')
|
|
}}</el-button>
|
|
<br />
|
|
<br />
|
|
<span class="name">{{ t('dialog.group_member_moderation.selected_users') }}</span>
|
|
<el-button
|
|
type="default"
|
|
size="mini"
|
|
icon="el-icon-delete"
|
|
circle
|
|
style="margin-left: 5px"
|
|
@click="clearSelectedGroupMembers"></el-button>
|
|
<br />
|
|
<el-tag
|
|
v-for="user in selectedUsersArray"
|
|
:key="user.id"
|
|
type="info"
|
|
:disable-transitions="true"
|
|
style="margin-right: 5px; margin-top: 5px"
|
|
closable
|
|
@close="deleteSelectedGroupMember(user)">
|
|
<span
|
|
>{{ user.user?.displayName }}
|
|
<i v-if="user.membershipStatus !== 'member'" class="el-icon-warning" style="margin-left: 5px"></i
|
|
></span>
|
|
</el-tag>
|
|
<br />
|
|
<br />
|
|
<span class="name">{{ t('dialog.group_member_moderation.notes') }}</span>
|
|
<el-input
|
|
v-model="note"
|
|
class="extra"
|
|
type="textarea"
|
|
:rows="2"
|
|
:autosize="{ minRows: 1, maxRows: 20 }"
|
|
:placeholder="t('dialog.group_member_moderation.note_placeholder')"
|
|
size="mini"
|
|
resize="none"
|
|
style="margin-top: 5px"></el-input>
|
|
<br />
|
|
<br />
|
|
<span class="name">{{ t('dialog.group_member_moderation.selected_roles') }}</span>
|
|
<br />
|
|
<el-select
|
|
v-model="selectedRoles"
|
|
clearable
|
|
multiple
|
|
:placeholder="t('dialog.group_member_moderation.choose_roles_placeholder')"
|
|
filterable
|
|
style="margin-top: 5px">
|
|
<el-option-group :label="t('dialog.group_member_moderation.roles')">
|
|
<el-option
|
|
v-for="role in groupMemberModeration.groupRef.roles"
|
|
:key="role.id"
|
|
class="x-friend-item"
|
|
:label="role.name"
|
|
:value="role.id"
|
|
style="height: auto">
|
|
<div class="detail">
|
|
<span class="name" v-text="role.name"></span>
|
|
</div>
|
|
</el-option>
|
|
</el-option-group>
|
|
</el-select>
|
|
<br />
|
|
<br />
|
|
<span class="name">{{ t('dialog.group_member_moderation.actions') }}</span>
|
|
<br />
|
|
<el-button
|
|
:disabled="
|
|
Boolean(
|
|
!selectedRoles.length ||
|
|
progressCurrent ||
|
|
!hasGroupPermission(groupDialog.ref, 'group-roles-assign')
|
|
)
|
|
"
|
|
@click="groupMembersAddRoles"
|
|
>{{ t('dialog.group_member_moderation.add_roles') }}</el-button
|
|
>
|
|
<el-button
|
|
:disabled="
|
|
Boolean(
|
|
!selectedRoles.length ||
|
|
progressCurrent ||
|
|
!hasGroupPermission(groupDialog.ref, 'group-roles-assign')
|
|
)
|
|
"
|
|
@click="groupMembersRemoveRoles"
|
|
>{{ t('dialog.group_member_moderation.remove_roles') }}</el-button
|
|
>
|
|
<el-button
|
|
:disabled="Boolean(progressCurrent || !hasGroupPermission(groupDialog.ref, 'group-members-manage'))"
|
|
@click="groupMembersSaveNote"
|
|
>{{ t('dialog.group_member_moderation.save_note') }}</el-button
|
|
>
|
|
<el-button
|
|
:disabled="Boolean(progressCurrent || !hasGroupPermission(groupDialog.ref, 'group-members-remove'))"
|
|
@click="groupMembersKick"
|
|
>{{ t('dialog.group_member_moderation.kick') }}</el-button
|
|
>
|
|
<el-button
|
|
:disabled="Boolean(progressCurrent || !hasGroupPermission(groupDialog.ref, 'group-bans-manage'))"
|
|
@click="groupMembersBan"
|
|
>{{ t('dialog.group_member_moderation.ban') }}</el-button
|
|
>
|
|
<el-button
|
|
:disabled="Boolean(progressCurrent || !hasGroupPermission(groupDialog.ref, 'group-bans-manage'))"
|
|
@click="groupMembersUnban"
|
|
>{{ t('dialog.group_member_moderation.unban') }}</el-button
|
|
>
|
|
<span v-if="progressCurrent" style="margin-top: 10px">
|
|
<i class="el-icon-loading" style="margin-left: 5px; margin-right: 5px"></i>
|
|
{{ t('dialog.group_member_moderation.progress') }} {{ progressCurrent }}/{{ progressTotal }}
|
|
</span>
|
|
<el-button v-if="progressCurrent" style="margin-left: 5px" @click="progressTotal = 0">{{
|
|
t('dialog.group_member_moderation.cancel')
|
|
}}</el-button>
|
|
</div>
|
|
<group-member-moderation-export-dialog
|
|
:is-group-logs-export-dialog-visible.sync="isGroupLogsExportDialogVisible"
|
|
:group-logs-moderation-table="groupLogsModerationTable" />
|
|
</safe-dialog>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { getCurrentInstance, inject, ref, watch } from 'vue';
|
|
import { useI18n } from 'vue-i18n-bridge';
|
|
import { groupRequest, userRequest } from '../../../api';
|
|
import { useModerationTable, useSelectedUsers } from '../../../composables/group/useGroupMemberModeration';
|
|
import { hasGroupPermission } from '../../../composables/group/utils';
|
|
import GroupMemberModerationExportDialog from './GroupMemberModerationExportDialog.vue';
|
|
|
|
const API = inject('API');
|
|
const showUserDialog = inject('showUserDialog');
|
|
const userImage = inject('userImage');
|
|
const userImageFull = inject('userImageFull');
|
|
const showFullscreenImageDialog = inject('showFullscreenImageDialog');
|
|
|
|
const { t } = useI18n();
|
|
const instance = getCurrentInstance();
|
|
const $message = instance.proxy.$message;
|
|
|
|
const props = defineProps({
|
|
isGroupMembersLoading: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
groupDialog: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
groupDialogSortingOptions: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
groupDialogFilterOptions: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
randomUserColours: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
groupMemberModeration: {
|
|
type: Object,
|
|
required: true
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits([
|
|
'update:is-group-members-loading',
|
|
'close-dialog',
|
|
'load-all-group-members',
|
|
'set-group-member-sort-order',
|
|
'set-group-member-filter',
|
|
'group-members-search'
|
|
]);
|
|
|
|
const {
|
|
groupInvitesModerationTable,
|
|
groupJoinRequestsModerationTable,
|
|
groupBlockedModerationTable,
|
|
groupLogsModerationTable,
|
|
groupBansModerationTable,
|
|
groupMemberModerationTable,
|
|
initializePageSize,
|
|
deselectGroupMember
|
|
} = useModerationTable();
|
|
|
|
const {
|
|
selectedUsers,
|
|
selectedUsersArray,
|
|
groupMemberModerationTableSelectionChange,
|
|
deselectedUsers,
|
|
setSelectedUsers
|
|
} = useSelectedUsers();
|
|
|
|
const selectUserId = ref('');
|
|
const progressCurrent = ref(0);
|
|
const progressTotal = ref(0);
|
|
const selectedRoles = ref([]);
|
|
const selectedAuditLogTypes = ref([]);
|
|
const note = ref('');
|
|
const isGroupLogsExportDialogVisible = ref(false);
|
|
|
|
watch(
|
|
() => props.groupMemberModeration.visible,
|
|
(newVal) => {
|
|
if (newVal) {
|
|
if (props.groupMemberModeration.id !== props.groupDialog.id) {
|
|
return;
|
|
}
|
|
groupMemberModerationTable.data = [];
|
|
groupBansModerationTable.data = [];
|
|
groupInvitesModerationTable.data = [];
|
|
groupJoinRequestsModerationTable.data = [];
|
|
groupBlockedModerationTable.data = [];
|
|
groupLogsModerationTable.data = [];
|
|
Object.assign(selectedUsers, {});
|
|
selectUserId.value = '';
|
|
selectedRoles.value = [];
|
|
note.value = '';
|
|
}
|
|
}
|
|
);
|
|
|
|
watch(
|
|
() => props.groupDialog.members,
|
|
(newVal) => {
|
|
if (newVal) {
|
|
setGroupMemberModerationTable(newVal);
|
|
}
|
|
},
|
|
{ immediate: true, deep: true }
|
|
);
|
|
|
|
watch(
|
|
() => props.groupDialog.memberSearchResults,
|
|
(newVal) => {
|
|
if (newVal) {
|
|
setGroupMemberModerationTable(newVal);
|
|
}
|
|
},
|
|
{ immediate: true, deep: true }
|
|
);
|
|
|
|
// created()
|
|
initializePageSize();
|
|
|
|
function loadAllGroupMembers() {
|
|
emit('load-all-group-members');
|
|
}
|
|
function setGroupMemberSortOrder(item) {
|
|
emit('set-group-member-sort-order', item);
|
|
}
|
|
function setGroupMemberFilter(filter) {
|
|
emit('set-group-member-filter', filter);
|
|
}
|
|
function groupMembersSearch() {
|
|
emit('group-members-search');
|
|
}
|
|
function updateIsGroupMembersLoading(value) {
|
|
emit('update:is-group-members-loading', value);
|
|
}
|
|
function closeDialog() {
|
|
emit('close-dialog');
|
|
}
|
|
|
|
function setGroupMemberModerationTable(data) {
|
|
if (!Array.isArray(data)) {
|
|
return;
|
|
}
|
|
groupMemberModerationTable.data = data.map((member) => ({
|
|
...member,
|
|
$selected: Boolean(selectedUsers[member.userId])
|
|
}));
|
|
}
|
|
|
|
function handleGroupMemberRoleChange(args) {
|
|
// 'GROUP:MEMBER:ROLE:CHANGE'
|
|
if (props.groupDialog.id === args.params.groupId) {
|
|
props.groupDialog.members.forEach((member) => {
|
|
if (member.userId === args.params.userId) {
|
|
member.roleIds = args.json;
|
|
return true;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
async function groupMembersDeleteSentInvite() {
|
|
const D = props.groupMemberModeration;
|
|
const users = [...selectedUsersArray.value];
|
|
const memberCount = users.length;
|
|
progressTotal.value = memberCount;
|
|
let allSuccess = true;
|
|
for (let i = 0; i < memberCount; i++) {
|
|
if (!progressTotal.value) {
|
|
break;
|
|
}
|
|
const user = users[i];
|
|
progressCurrent.value = i + 1;
|
|
if (user.userId === API.currentUser.id) {
|
|
continue;
|
|
}
|
|
console.log(`Deleting group invite ${user.userId} ${i + 1}/${memberCount}`);
|
|
try {
|
|
await groupRequest.deleteSentGroupInvite({
|
|
groupId: D.id,
|
|
userId: user.userId
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
$message({
|
|
message: `Failed to delete group invites: ${err}`,
|
|
type: 'error'
|
|
});
|
|
allSuccess = false;
|
|
}
|
|
}
|
|
if (allSuccess) {
|
|
$message({
|
|
message: `Deleted ${memberCount} group invites`,
|
|
type: 'success'
|
|
});
|
|
}
|
|
progressCurrent.value = 0;
|
|
progressTotal.value = 0;
|
|
getAllGroupInvites(D.id, groupInvitesModerationTable);
|
|
}
|
|
|
|
function selectAllGroupMembers() {
|
|
groupMemberModerationTable.data.forEach((row) => {
|
|
row.$selected = true;
|
|
setSelectedUsers(row.userId, row);
|
|
});
|
|
}
|
|
|
|
async function getAllGroupBans(groupId) {
|
|
groupBansModerationTable.data = [];
|
|
const params = { groupId, n: 100, offset: 0 };
|
|
const count = 50; // 5000 max
|
|
updateIsGroupMembersLoading(true);
|
|
const fetchedBans = [];
|
|
try {
|
|
for (let i = 0; i < count; i++) {
|
|
const args = await groupRequest.getGroupBans(params);
|
|
if (args && args.json) {
|
|
if (props.groupMemberModeration.id !== args.params.groupId) {
|
|
continue;
|
|
}
|
|
args.json.forEach((json) => {
|
|
const ref = API.applyGroupMember(json);
|
|
fetchedBans.push(ref);
|
|
});
|
|
if (args.json.length < params.n) {
|
|
break;
|
|
}
|
|
params.offset += params.n;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
groupBansModerationTable.data = fetchedBans;
|
|
} catch {
|
|
$message({
|
|
message: 'Failed to get group bans',
|
|
type: 'error'
|
|
});
|
|
} finally {
|
|
updateIsGroupMembersLoading(false);
|
|
}
|
|
}
|
|
|
|
function selectAllGroupBans() {
|
|
groupBansModerationTable.data.forEach((row) => {
|
|
row.$selected = true;
|
|
setSelectedUsers(row.userId, row);
|
|
});
|
|
}
|
|
|
|
async function groupMembersBan() {
|
|
const D = props.groupMemberModeration;
|
|
const users = [...selectedUsersArray.value];
|
|
const memberCount = users.length;
|
|
progressTotal.value = memberCount;
|
|
for (let i = 0; i < memberCount; i++) {
|
|
if (!progressTotal.value) break;
|
|
const user = users[i];
|
|
progressCurrent.value = i + 1;
|
|
if (user.userId === API.currentUser.id) continue;
|
|
console.log(`Banning ${user.userId} ${i + 1}/${memberCount}`);
|
|
try {
|
|
await groupRequest.banGroupMember({ groupId: D.id, userId: user.userId });
|
|
} catch (err) {
|
|
console.error(err);
|
|
$message({
|
|
message: `Failed to ban group member: ${err}`,
|
|
type: 'error'
|
|
});
|
|
}
|
|
}
|
|
$message({ message: `Banned ${memberCount} group members`, type: 'success' });
|
|
progressCurrent.value = 0;
|
|
progressTotal.value = 0;
|
|
getAllGroupBans(D.id);
|
|
deselectedUsers(null, true);
|
|
}
|
|
|
|
async function groupMembersUnban() {
|
|
const D = props.groupMemberModeration;
|
|
const users = [...selectedUsersArray.value];
|
|
const memberCount = users.length;
|
|
progressTotal.value = memberCount;
|
|
let allSuccess = true;
|
|
for (let i = 0; i < memberCount; i++) {
|
|
if (!progressTotal.value) break;
|
|
const user = users[i];
|
|
progressCurrent.value = i + 1;
|
|
if (user.userId === API.currentUser.id) continue;
|
|
console.log(`Unbanning ${user.userId} ${i + 1}/${memberCount}`);
|
|
try {
|
|
await groupRequest.unbanGroupMember({ groupId: D.id, userId: user.userId });
|
|
} catch (err) {
|
|
console.error(err);
|
|
$message({
|
|
message: `Failed to unban group member: ${err}`,
|
|
type: 'error'
|
|
});
|
|
allSuccess = false;
|
|
}
|
|
}
|
|
|
|
if (allSuccess) {
|
|
$message({ message: `Unbanned ${memberCount} group members`, type: 'success' });
|
|
}
|
|
progressCurrent.value = 0;
|
|
progressTotal.value = 0;
|
|
getAllGroupBans(D.id);
|
|
deselectedUsers(null, true);
|
|
}
|
|
|
|
async function groupMembersKick() {
|
|
const D = props.groupMemberModeration;
|
|
const users = [...selectedUsersArray.value];
|
|
const memberCount = users.length;
|
|
progressTotal.value = memberCount;
|
|
let allSuccess = true;
|
|
for (let i = 0; i < memberCount; i++) {
|
|
if (!progressTotal.value) break;
|
|
const user = users[i];
|
|
progressCurrent.value = i + 1;
|
|
if (user.userId === API.currentUser.id) continue;
|
|
|
|
console.log(`Kicking ${user.userId} ${i + 1}/${memberCount}`);
|
|
try {
|
|
await groupRequest.kickGroupMember({ groupId: D.id, userId: user.userId });
|
|
} catch (err) {
|
|
console.error(err);
|
|
$message({
|
|
message: `Failed to kick group member: ${err}`,
|
|
type: 'error'
|
|
});
|
|
allSuccess = false;
|
|
}
|
|
}
|
|
if (allSuccess) {
|
|
$message({ message: `Kicked ${memberCount} group members`, type: 'success' });
|
|
}
|
|
progressCurrent.value = 0;
|
|
progressTotal.value = 0;
|
|
deselectedUsers(null, true);
|
|
}
|
|
|
|
async function groupMembersSaveNote() {
|
|
const D = props.groupMemberModeration;
|
|
const users = [...selectedUsersArray.value];
|
|
const memberCount = users.length;
|
|
progressTotal.value = memberCount;
|
|
const noteToSave = note.value;
|
|
let allSuccess = true;
|
|
for (let i = 0; i < memberCount; i++) {
|
|
if (!progressTotal.value) break;
|
|
const user = users[i];
|
|
progressCurrent.value = i + 1;
|
|
if (user.managerNotes === noteToSave) {
|
|
continue;
|
|
}
|
|
console.log(`Setting note ${noteToSave} for ${user.userId} ${i + 1}/${memberCount}`);
|
|
try {
|
|
await groupRequest.setGroupMemberProps(user.userId, D.id, { managerNotes: noteToSave });
|
|
} catch (err) {
|
|
console.error(err);
|
|
$message({
|
|
message: `Failed to set group member note for ${err}`,
|
|
type: 'error'
|
|
});
|
|
allSuccess = false;
|
|
}
|
|
}
|
|
if (allSuccess) {
|
|
$message({ message: `Saved notes for ${memberCount} group members`, type: 'success' });
|
|
}
|
|
progressCurrent.value = 0;
|
|
progressTotal.value = 0;
|
|
deselectedUsers(null, true);
|
|
}
|
|
|
|
async function groupMembersRemoveRoles() {
|
|
const D = props.groupMemberModeration;
|
|
const users = [...selectedUsersArray.value];
|
|
const rolesToRemoveSet = new Set(selectedRoles.value);
|
|
const memberCount = users.length;
|
|
progressTotal.value = memberCount;
|
|
let allSuccess = true;
|
|
for (let i = 0; i < memberCount; i++) {
|
|
if (!progressTotal.value) break;
|
|
const user = users[i];
|
|
progressCurrent.value = i + 1;
|
|
const currentRoles = new Set(user.roleIds || []);
|
|
const rolesToRemoveForUser = [];
|
|
rolesToRemoveSet.forEach((roleId) => {
|
|
if (currentRoles.has(roleId)) {
|
|
rolesToRemoveForUser.push(roleId);
|
|
}
|
|
});
|
|
|
|
if (!rolesToRemoveForUser.length) continue;
|
|
|
|
for (const roleId of rolesToRemoveForUser) {
|
|
console.log(`Removing role ${roleId} from ${user.userId} ${i + 1}/${memberCount}`);
|
|
|
|
try {
|
|
const args = await groupRequest.removeGroupMemberRole({
|
|
groupId: D.id,
|
|
userId: user.userId,
|
|
roleId
|
|
});
|
|
handleGroupMemberRoleChange(args);
|
|
} catch (err) {
|
|
console.error(err);
|
|
$message({
|
|
message: `Failed to remove group member roles: ${err}`,
|
|
type: 'error'
|
|
});
|
|
allSuccess = false;
|
|
}
|
|
}
|
|
}
|
|
if (allSuccess) {
|
|
$message({
|
|
message: `Roles removed`,
|
|
type: 'success'
|
|
});
|
|
}
|
|
progressCurrent.value = 0;
|
|
progressTotal.value = 0;
|
|
deselectedUsers(null, true);
|
|
}
|
|
|
|
async function groupMembersAddRoles() {
|
|
const D = props.groupMemberModeration;
|
|
const users = [...selectedUsersArray.value];
|
|
const rolesToAddSet = new Set(selectedRoles.value);
|
|
const memberCount = users.length;
|
|
progressTotal.value = memberCount;
|
|
let allSuccess = true;
|
|
for (let i = 0; i < memberCount; i++) {
|
|
if (!progressTotal.value) break;
|
|
const user = users[i];
|
|
progressCurrent.value = i + 1;
|
|
|
|
const currentRoles = new Set(user.roleIds || []);
|
|
const rolesToAddForUser = [];
|
|
rolesToAddSet.forEach((roleId) => {
|
|
if (!currentRoles.has(roleId)) {
|
|
rolesToAddForUser.push(roleId);
|
|
}
|
|
});
|
|
|
|
if (!rolesToAddForUser.length) continue;
|
|
|
|
for (const roleId of rolesToAddForUser) {
|
|
console.log(`Adding role: ${roleId} to ${user.userId} ${i + 1}/${memberCount}`);
|
|
try {
|
|
const args = await groupRequest.addGroupMemberRole({ groupId: D.id, userId: user.userId, roleId });
|
|
handleGroupMemberRoleChange(args);
|
|
} catch (err) {
|
|
console.error(err);
|
|
$message({
|
|
message: `Failed to add group member roles: ${err}`,
|
|
type: 'error'
|
|
});
|
|
allSuccess = false;
|
|
}
|
|
}
|
|
}
|
|
if (allSuccess) {
|
|
$message({
|
|
message: `Added group member roles`,
|
|
type: 'success'
|
|
});
|
|
}
|
|
progressCurrent.value = 0;
|
|
progressTotal.value = 0;
|
|
deselectedUsers(null, true);
|
|
}
|
|
|
|
function deleteSelectedGroupMember(user) {
|
|
deselectedUsers(user.userId);
|
|
deselectGroupMember(user.userId);
|
|
}
|
|
|
|
function clearSelectedGroupMembers() {
|
|
deselectedUsers(null, true);
|
|
deselectGroupMember();
|
|
}
|
|
|
|
async function selectGroupMemberUserId() {
|
|
const userIdInput = selectUserId.value;
|
|
if (!userIdInput) return;
|
|
|
|
const regexUserId = /usr_[0-9A-Fa-f]{8}-([0-9A-Fa-f]{4}-){3}[0-9A-Fa-f]{12}/g;
|
|
let match;
|
|
const userIdList = new Set();
|
|
while ((match = regexUserId.exec(userIdInput)) !== null) {
|
|
userIdList.add(match[0]);
|
|
}
|
|
|
|
if (userIdList.size === 0) {
|
|
// for those users missing the usr_ prefix
|
|
userIdList.add(userIdInput);
|
|
}
|
|
const promises = [];
|
|
userIdList.forEach((userId) => {
|
|
promises.push(addGroupMemberToSelection(userId));
|
|
});
|
|
await Promise.allSettled(promises);
|
|
selectUserId.value = '';
|
|
}
|
|
|
|
async function addGroupMemberToSelection(userId) {
|
|
const D = props.groupMemberModeration;
|
|
// fetch member if there is one
|
|
// banned members don't have a user object
|
|
let member = {};
|
|
const memberArgs = await groupRequest.getGroupMember({ groupId: D.id, userId });
|
|
if (memberArgs && memberArgs.json) {
|
|
member = API.applyGroupMember(memberArgs.json);
|
|
}
|
|
if (member && member.user) {
|
|
setSelectedUsers(member.userId, member);
|
|
return;
|
|
}
|
|
const userArgs = await userRequest.getCachedUser({
|
|
userId
|
|
});
|
|
member.userId = userArgs.json.id;
|
|
member.user = userArgs.json;
|
|
member.displayName = userArgs.json.displayName;
|
|
setSelectedUsers(member.userId, member);
|
|
}
|
|
|
|
async function getAllGroupLogs(groupId) {
|
|
groupLogsModerationTable.data = [];
|
|
const params = { groupId, n: 100, offset: 0 };
|
|
if (selectedAuditLogTypes.value.length) {
|
|
params.eventTypes = selectedAuditLogTypes.value;
|
|
}
|
|
const count = 50; // 5000 max
|
|
updateIsGroupMembersLoading(true);
|
|
|
|
try {
|
|
for (let i = 0; i < count; i++) {
|
|
const args = await groupRequest.getGroupLogs(params);
|
|
if (args) {
|
|
if (props.groupMemberModeration.id !== args.params.groupId) {
|
|
continue;
|
|
}
|
|
|
|
for (const json of args.json.results) {
|
|
const existsInData = groupLogsModerationTable.data.some((dataItem) => dataItem.id === json.id);
|
|
if (!existsInData) {
|
|
groupLogsModerationTable.data.push(json);
|
|
}
|
|
}
|
|
}
|
|
params.offset += params.n;
|
|
if (!args.json.hasNext) {
|
|
break;
|
|
}
|
|
}
|
|
} catch {
|
|
$message({
|
|
message: 'Failed to get group logs',
|
|
type: 'error'
|
|
});
|
|
} finally {
|
|
updateIsGroupMembersLoading(false);
|
|
}
|
|
}
|
|
|
|
async function groupMembersDeleteBlockedRequest() {
|
|
const D = props.groupMemberModeration;
|
|
const users = [...selectedUsersArray.value];
|
|
const memberCount = users.length;
|
|
progressTotal.value = memberCount;
|
|
|
|
let allSuccess = true;
|
|
for (let i = 0; i < memberCount; i++) {
|
|
if (!progressTotal.value) break;
|
|
const user = users[i];
|
|
progressCurrent.value = i + 1;
|
|
|
|
if (user.userId === API.currentUser.id) {
|
|
continue;
|
|
}
|
|
|
|
console.log(`Deleting blocked group request for ${user.userId} ${i + 1}/${memberCount}`);
|
|
try {
|
|
await groupRequest.deleteBlockedGroupRequest({ groupId: D.id, userId: user.userId });
|
|
} catch (err) {
|
|
console.error(err);
|
|
$message({
|
|
message: `Failed to delete blocked group requests: ${err}`,
|
|
type: 'error'
|
|
});
|
|
allSuccess = false;
|
|
}
|
|
}
|
|
if (allSuccess) {
|
|
$message({
|
|
message: `Deleted ${memberCount} blocked group requests`,
|
|
type: 'success'
|
|
});
|
|
}
|
|
progressCurrent.value = 0;
|
|
progressTotal.value = 0;
|
|
getAllGroupInvitesAndJoinRequests();
|
|
deselectedUsers(null, true);
|
|
}
|
|
|
|
async function groupMembersBlockJoinRequest() {
|
|
const D = props.groupMemberModeration;
|
|
const users = [...selectedUsersArray.value];
|
|
const memberCount = users.length;
|
|
progressTotal.value = memberCount;
|
|
let allSuccess = true;
|
|
for (let i = 0; i < memberCount; i++) {
|
|
if (!progressTotal.value) break;
|
|
const user = users[i];
|
|
progressCurrent.value = i + 1;
|
|
if (user.userId === API.currentUser.id) continue;
|
|
|
|
console.log(`Blocking group join request from ${user.userId} ${i + 1}/${memberCount}`);
|
|
try {
|
|
await groupRequest.blockGroupInviteRequest({ groupId: D.id, userId: user.userId });
|
|
} catch (err) {
|
|
console.error(err);
|
|
$message({
|
|
message: `Failed to block group join requests: ${err}`,
|
|
type: 'error'
|
|
});
|
|
allSuccess = false;
|
|
}
|
|
}
|
|
if (allSuccess) {
|
|
$message({
|
|
message: `Blocked ${memberCount} group join requests`,
|
|
type: 'success'
|
|
});
|
|
}
|
|
progressCurrent.value = 0;
|
|
progressTotal.value = 0;
|
|
getAllGroupInvitesAndJoinRequests();
|
|
deselectedUsers(null, true);
|
|
}
|
|
|
|
async function groupMembersRejectInviteRequest() {
|
|
const D = props.groupMemberModeration;
|
|
const users = [...selectedUsersArray.value];
|
|
const memberCount = users.length;
|
|
progressTotal.value = memberCount;
|
|
|
|
let allSuccess = true;
|
|
for (let i = 0; i < memberCount; i++) {
|
|
if (!progressTotal.value) break;
|
|
const user = users[i];
|
|
progressCurrent.value = i + 1;
|
|
if (user.userId === API.currentUser.id) continue;
|
|
|
|
console.log(`Rejecting group join request from ${user.userId} ${i + 1}/${memberCount}`);
|
|
try {
|
|
await groupRequest.rejectGroupInviteRequest({ groupId: D.id, userId: user.userId });
|
|
} catch (err) {
|
|
console.error(err);
|
|
$message({
|
|
message: `Failed to reject group join requests: ${err}`,
|
|
type: 'error'
|
|
});
|
|
allSuccess = false;
|
|
}
|
|
}
|
|
if (allSuccess) {
|
|
$message({
|
|
message: `Rejected ${memberCount} group join requests`,
|
|
type: 'success'
|
|
});
|
|
}
|
|
progressCurrent.value = 0;
|
|
progressTotal.value = 0;
|
|
getAllGroupInvitesAndJoinRequests();
|
|
deselectedUsers(null, true);
|
|
}
|
|
|
|
async function groupMembersAcceptInviteRequest() {
|
|
const D = props.groupMemberModeration;
|
|
const users = [...selectedUsersArray.value];
|
|
const memberCount = users.length;
|
|
progressTotal.value = memberCount;
|
|
let allSuccess = true;
|
|
for (let i = 0; i < memberCount; i++) {
|
|
if (!progressTotal.value) break;
|
|
const user = users[i];
|
|
progressCurrent.value = i + 1;
|
|
if (user.userId === API.currentUser.id) continue;
|
|
|
|
console.log(`Accepting group join request from ${user.userId} ${i + 1}/${memberCount}`);
|
|
try {
|
|
await groupRequest.acceptGroupInviteRequest({ groupId: D.id, userId: user.userId });
|
|
} catch (err) {
|
|
console.error(err);
|
|
$message({
|
|
message: `Failed to accept group join requests: ${err}`,
|
|
type: 'error'
|
|
});
|
|
allSuccess = false;
|
|
}
|
|
}
|
|
if (allSuccess) {
|
|
$message({
|
|
message: `Accepted ${memberCount} group join requests`,
|
|
type: 'success'
|
|
});
|
|
}
|
|
progressCurrent.value = 0;
|
|
progressTotal.value = 0;
|
|
getAllGroupInvitesAndJoinRequests();
|
|
deselectedUsers(null, true);
|
|
}
|
|
|
|
async function getAllGroupInvitesAndJoinRequests(groupId) {
|
|
try {
|
|
await Promise.all([
|
|
getAllGroupInvites(groupId),
|
|
getAllGroupJoinRequests(groupId),
|
|
getAllGroupBlockedRequests(groupId)
|
|
]);
|
|
} catch (error) {
|
|
console.error('Error fetching group invites/requests:', error);
|
|
}
|
|
}
|
|
|
|
async function getAllGroupBlockedRequests(groupId) {
|
|
groupBlockedModerationTable.data = [];
|
|
const params = { groupId, n: 100, offset: 0, blocked: true };
|
|
const count = 50; // 5000
|
|
updateIsGroupMembersLoading(true);
|
|
|
|
try {
|
|
for (let i = 0; i < count; i++) {
|
|
const args = await groupRequest.getGroupJoinRequests(params);
|
|
if (props.groupMemberModeration.id !== args.params.groupId) {
|
|
return;
|
|
}
|
|
const targetTable = args.params.blocked
|
|
? groupBlockedModerationTable
|
|
: groupJoinRequestsModerationTable;
|
|
for (const json of args.json) {
|
|
const ref = API.applyGroupMember(json);
|
|
targetTable.data.push(ref);
|
|
}
|
|
params.offset += params.n;
|
|
if (args.json.length < params.n) {
|
|
break;
|
|
}
|
|
}
|
|
} catch {
|
|
$message({
|
|
message: 'Failed to get group join requests',
|
|
type: 'error'
|
|
});
|
|
} finally {
|
|
updateIsGroupMembersLoading(false);
|
|
}
|
|
}
|
|
|
|
async function getAllGroupJoinRequests(groupId) {
|
|
groupJoinRequestsModerationTable.data = [];
|
|
const params = { groupId, n: 100, offset: 0, blocked: false };
|
|
const count = 50; // 5000 max
|
|
updateIsGroupMembersLoading(true);
|
|
try {
|
|
for (let i = 0; i < count; i++) {
|
|
const args = await groupRequest.getGroupJoinRequests(params);
|
|
if (props.groupMemberModeration.id !== args.params.groupId) {
|
|
return;
|
|
}
|
|
const targetTable = args.params.blocked
|
|
? groupBlockedModerationTable
|
|
: groupJoinRequestsModerationTable;
|
|
for (const json of args.json) {
|
|
const ref = API.applyGroupMember(json);
|
|
targetTable.data.push(ref);
|
|
}
|
|
params.offset += params.n;
|
|
if (args.json.length < params.n) {
|
|
break;
|
|
}
|
|
}
|
|
} catch {
|
|
$message({
|
|
message: 'Failed to get group join requests',
|
|
type: 'error'
|
|
});
|
|
} finally {
|
|
updateIsGroupMembersLoading(false);
|
|
}
|
|
}
|
|
|
|
async function getAllGroupInvites(groupId) {
|
|
groupInvitesModerationTable.data = [];
|
|
const params = { groupId, n: 100, offset: 0 };
|
|
const count = 50; // 5000 max
|
|
updateIsGroupMembersLoading(true);
|
|
|
|
try {
|
|
for (let i = 0; i < count; i++) {
|
|
const args = await groupRequest.getGroupInvites(params);
|
|
if (args) {
|
|
if (props.groupMemberModeration.id !== args.params.groupId) {
|
|
return;
|
|
}
|
|
|
|
for (const json of args.json) {
|
|
const ref = API.applyGroupMember(json);
|
|
groupInvitesModerationTable.data.push(ref);
|
|
}
|
|
}
|
|
params.offset += params.n;
|
|
if (args.json.length < params.n) {
|
|
break;
|
|
}
|
|
if (!props.groupMemberModeration.visible) {
|
|
break;
|
|
}
|
|
}
|
|
} catch {
|
|
$message({
|
|
message: 'Failed to get group invites',
|
|
type: 'error'
|
|
});
|
|
} finally {
|
|
updateIsGroupMembersLoading(false); // Use emit
|
|
}
|
|
}
|
|
|
|
function selectAllGroupInvites() {
|
|
groupInvitesModerationTable.data.forEach((row) => {
|
|
row.$selected = true;
|
|
setSelectedUsers(row.userId, row);
|
|
});
|
|
}
|
|
|
|
function selectAllGroupJoinRequests() {
|
|
groupJoinRequestsModerationTable.data.forEach((row) => {
|
|
row.$selected = true;
|
|
setSelectedUsers(row.userId, row);
|
|
});
|
|
}
|
|
|
|
function selectAllGroupBlocked() {
|
|
groupBlockedModerationTable.data.forEach((row) => {
|
|
row.$selected = true;
|
|
setSelectedUsers(row.userId, row);
|
|
});
|
|
}
|
|
|
|
function showGroupLogsExportDialog() {
|
|
isGroupLogsExportDialogVisible.value = true;
|
|
}
|
|
|
|
function getAuditLogTypeName(auditLogType) {
|
|
if (!auditLogType) return '';
|
|
return auditLogType
|
|
.replace('group.', '')
|
|
.replace(/\./g, ' ')
|
|
.replace(/\b\w/g, (l) => l.toUpperCase());
|
|
}
|
|
</script>
|