Browse Source

Merge pull request #6601 from nocodb/fix/table

fix: css refactor token and user list
pull/6610/head
Sreehari jayaraj 1 year ago committed by GitHub
parent
commit
78796c99f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 100
      packages/nc-gui/components/account/Token.vue
  2. 65
      packages/nc-gui/components/account/UserList.vue
  3. 4
      packages/nc-gui/components/account/UsersModal.vue
  4. 2
      packages/nc-gui/components/dlg/ProjectDelete.vue
  5. 7
      packages/nc-gui/lang/en.json
  6. 13
      packages/nc-gui/pages/account/index.vue
  7. 2
      tests/playwright/pages/Account/Users.ts

100
packages/nc-gui/components/account/Token.vue

@ -160,10 +160,10 @@ const handleCancel = () => {
</script> </script>
<template> <template>
<div class="h-full overflow-y-scroll scrollbar-thin-dull pt-2"> <div class="h-full pt-2">
<div class="max-w-[810px] mx-auto p-4" data-testid="nc-token-list"> <div class="max-w-202 mx-auto px-4 h-full" data-testid="nc-token-list">
<div class="py-2 flex gap-4 items-center justify-between"> <div class="py-2 flex gap-4 items-baseline justify-between">
<h6 class="text-2xl my-4 text-left font-bold">{{ $t('title.apiTokens') }}</h6> <h6 class="text-2xl text-left font-bold">{{ $t('title.apiTokens') }}</h6>
<NcTooltip :disabled="!(isEeUI && tokens.length)"> <NcTooltip :disabled="!(isEeUI && tokens.length)">
<template #title>{{ $t('labels.tokenLimit') }}</template> <template #title>{{ $t('labels.tokenLimit') }}</template>
<NcButton <NcButton
@ -185,18 +185,23 @@ const handleCancel = () => {
</NcTooltip> </NcTooltip>
</div> </div>
<span>{{ $t('msg.apiTokenCreate') }}</span> <span>{{ $t('msg.apiTokenCreate') }}</span>
<div class="w-full mt-5 rounded-md h-136 overflow-y-scroll"> <div class="mt-5 h-[calc(100%-13rem)]">
<div> <div class="h-full w-full !overflow-hidden rounded-md">
<div class="flex w-full pl-5 bg-gray-50 border-1"> <div class="flex w-full pl-5 bg-gray-50 border-1 rounded-t-md">
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9">{{ $t('title.tokenName') }}</span> <span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9">{{ $t('title.tokenName') }}</span>
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9 text-start">{{ $t('title.creator') }}</span> <span class="py-3.5 text-gray-500 font-medium text-3.5 w-2/9 text-start">{{ $t('title.creator') }}</span>
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-3/9 text-start">{{ $t('labels.token') }}</span> <span class="py-3.5 text-gray-500 font-medium text-3.5 w-3/9 text-start">{{ $t('labels.token') }}</span>
<span class="py-3.5 pl-19 text-gray-500 font-medium text-3.5 w-2/9 text-start">{{ $t('labels.actions') }}</span> <span class="py-3.5 pl-19 text-gray-500 font-medium text-3.5 w-2/9 text-start">{{ $t('labels.actions') }}</span>
</div> </div>
<main> <div class="nc-scrollbar-md !overflow-y-auto flex flex-col h-[calc(100%-5rem)]">
<div v-if="showNewTokenModal"> <div v-if="showNewTokenModal">
<div class="flex gap-5 px-3 py-3.5 text-gray-500 font-medium text-3.5 w-full nc-token-generate"> <div
<div class="flex flex-col w-full"> class="flex gap-5 px-3 py-2.5 text-gray-500 font-medium text-3.5 w-full nc-token-generate border-b-1 border-l-1 border-r-1"
:class="{
'rounded-b-md': !tokens.length,
}"
>
<div class="flex w-full">
<a-input <a-input
:ref="selectInputOnMount" :ref="selectInputOnMount"
v-model:value="selectedTokenData.description" v-model:value="selectedTokenData.description"
@ -224,17 +229,19 @@ const handleCancel = () => {
</NcButton> </NcButton>
</div> </div>
</div> </div>
<NcDivider />
</div> </div>
<div v-if="!tokens.length" class="h-118 justify-center flex items-center"> <div
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('title.noLabels')" /> v-if="!tokens.length && !showNewTokenModal"
class="border-l-1 border-r-1 border-b-1 rounded-b-md justify-center flex items-center"
>
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noToken')" />
</div> </div>
<div <div
v-for="el of tokens" v-for="el of tokens"
:key="el.id" :key="el.id"
data-testid="nc-token-list" data-testid="nc-token-list"
class="flex border-1 pl-5 py-3 justify-between token" class="flex pl-5 py-3 justify-between token items-center border-l-1 border-r-1 border-b-1"
> >
<span class="text-black font-bold text-3.5 text-start w-2/9"> <span class="text-black font-bold text-3.5 text-start w-2/9">
<GeneralTruncateText placement="top" length="20"> <GeneralTruncateText placement="top" length="20">
@ -250,39 +257,42 @@ const handleCancel = () => {
<GeneralTruncateText v-if="el.token === selectedToken.id && selectedToken.isShow" placement="top" length="29"> <GeneralTruncateText v-if="el.token === selectedToken.id && selectedToken.isShow" placement="top" length="29">
{{ el.token }} {{ el.token }}
</GeneralTruncateText> </GeneralTruncateText>
<span v-else>**************************************</span> <span v-else>************************************</span>
</span> </span>
<!-- ACTIONS --> <!-- ACTIONS -->
<span class="text-gray-500 font-medium text-3.5 w-2/9"> <div class="flex justify-end items-center gap-3 pr-5 text-gray-500 font-medium text-3.5 w-2/9">
<div class="flex justify-end items-center gap-3 pr-5"> <NcTooltip placement="top">
<NcTooltip placement="top"> <template #title>{{ $t('labels.showOrHide') }}</template>
<template #title>{{ $t('labels.showOrHide') }}</template> <component
<component :is="iconMap.eye"
:is="iconMap.eye" class="nc-toggle-token-visibility hover::cursor-pointer w-h-4 mb-[1.8px]"
class="nc-toggle-token-visibility hover::cursor-pointer" @click="hideOrShowToken(el.token as string)"
@click="hideOrShowToken(el.token as string)" />
/> </NcTooltip>
</NcTooltip> <NcTooltip placement="top" class="h-4">
<NcTooltip placement="top" class="h-4"> <template #title>{{ $t('general.copy') }}</template>
<template #title>{{ $t('general.copy') }}</template> <component
<component :is="iconMap.copy" class="hover::cursor-pointer" @click="copyToken(el.token)" /> :is="iconMap.copy"
</NcTooltip> class="hover::cursor-pointer w-4 h-4 text-gray-600 mt-0.25"
<NcTooltip placement="top" class="mb-0.5"> @click="copyToken(el.token)"
<template #title>{{ $t('general.delete') }}</template> />
<component </NcTooltip>
:is="iconMap.delete" <NcTooltip placement="top" class="mb-0.5">
data-testid="nc-token-row-action-icon" <template #title>{{ $t('general.delete') }}</template>
class="nc-delete-icon hover::cursor-pointer" <component
@click="triggerDeleteModal(el.token as string, el.description as string)" :is="iconMap.delete"
/> data-testid="nc-token-row-action-icon"
</NcTooltip> class="nc-delete-icon hover::cursor-pointer w-4 h-4"
</div> @click="triggerDeleteModal(el.token as string, el.description as string)"
</span> />
</NcTooltip>
</div>
</div> </div>
</main> </div>
</div> </div>
</div> </div>
<div v-if="pagination.total > 10" class="flex items-center justify-center mt-15">
<div v-if="pagination.total > 10" class="flex items-center justify-center mt-5">
<a-pagination <a-pagination
v-model:current="currentPage" v-model:current="currentPage"
:total="pagination.total" :total="pagination.total"
@ -313,3 +323,9 @@ const handleCancel = () => {
</GeneralDeleteModal> </GeneralDeleteModal>
</div> </div>
</template> </template>
<style>
.token:last-child {
@apply border-b-1 rounded-b-md;
}
</style>

65
packages/nc-gui/components/account/UserList.vue

@ -153,9 +153,9 @@ const openDeleteModal = (user: UserType) => {
</script> </script>
<template> <template>
<div data-testid="nc-super-user-list"> <div data-testid="nc-super-user-list" class="h-full">
<div class="max-w-195 mx-auto"> <div class="max-w-195 mx-auto h-full">
<div class="text-2xl my-4 text-left font-weight-bold">{{ $t('title.userManagement') }}</div> <div class="text-2xl text-left font-weight-bold mb-4">{{ $t('title.userManagement') }}</div>
<div class="py-2 flex gap-4 items-center justify-between"> <div class="py-2 flex gap-4 items-center justify-between">
<a-input v-model:value="searchText" class="!max-w-90 !rounded-md" placeholder="Search members" @change="loadUsers()"> <a-input v-model:value="searchText" class="!max-w-90 !rounded-md" placeholder="Search members" @change="loadUsers()">
<template #prefix> <template #prefix>
@ -172,35 +172,39 @@ const openDeleteModal = (user: UserType) => {
</NcButton> </NcButton>
</div> </div>
</div> </div>
<div class="w-full mt-5 border-1 rounded-md h-[613px] max-w-250"> <div class="w-full rounded-md max-w-250 h-[calc(100%-12rem)] rounded-md overflow-hidden mt-5">
<div class="flex w-full bg-gray-50 border-b-1"> <div class="flex w-full bg-gray-50 border-1 rounded-t-md">
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-1/3 text-start pl-10">{{ $t('labels.email') }}</span> <div class="py-3.5 text-gray-500 font-medium text-3.5 w-2/3 text-start pl-6">{{ $t('labels.email') }}</div>
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-1/3 text-start pl-20">{{ $t('objects.role') }}</span> <div class="py-3.5 text-gray-500 font-medium text-3.5 w-1/3 text-start">{{ $t('objects.role') }}</div>
<span class="py-3.5 text-gray-500 font-medium text-3.5 w-1/3 text-end pl-42">{{ $t('labels.action') }}</span> <div class="flex py-3.5 text-gray-500 font-medium text-3.5 w-28 justify-end mr-4">
{{ $t('labels.action') }}
</div>
</div> </div>
<div v-if="isLoading" class="flex items-center justify-center text-center h-[513px]"> <div v-if="isLoading" class="flex items-center justify-center text-center h-[513px]">
<GeneralLoader size="xlarge" /> <GeneralLoader size="xlarge" />
</div> </div>
<!-- if users are empty --> <!-- if users are empty -->
<div v-else-if="!users.length" class="flex items-center justify-center text-center h-128.25"> <div v-else-if="!users.length" class="flex items-center justify-center text-center h-full">
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" /> <a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
</div> </div>
<section v-else class="tbody"> <section v-else class="tbody h-[calc(100%-4rem)] nc-scrollbar-md border-t-0 !overflow-auto">
<div <div
v-for="el of users" v-for="el of users"
:key="el.id" :key="el.id"
data-testid="nc-token-list" data-testid="nc-token-list"
class="flex py-3 justify-around px-5 border-b-1" class="user flex py-3 justify-around px-1 border-b-1 border-l-1 border-r-1"
:class="{ :class="{
'py-4': el.roles?.includes('super'), 'py-4': el.roles?.includes('super'),
}" }"
> >
<span class="text-3.5 text-start w-1/3 pl-5"> <div class="text-3.5 text-start w-2/3 pl-5 flex items-center">
{{ el.email }} <GeneralTruncateText length="29">
</span> {{ el.email }}
<span class="text-3.5 text-start w-1/3 pl-18"> </GeneralTruncateText>
</div>
<div class="text-3.5 text-start w-1/3">
<div v-if="el?.roles?.includes('super')" class="font-weight-bold">{{ $t('labels.superAdmin') }}</div> <div v-if="el?.roles?.includes('super')" class="font-weight-bold">{{ $t('labels.superAdmin') }}</div>
<a-select <NcSelect
v-else v-else
v-model:value="el.roles" v-model:value="el.roles"
class="w-55 nc-user-roles" class="w-55 nc-user-roles"
@ -228,9 +232,9 @@ const openDeleteModal = (user: UserType) => {
{{ $t('msg.info.roles.orgViewer') }} {{ $t('msg.info.roles.orgViewer') }}
</span> </span>
</a-select-option> </a-select-option>
</a-select> </NcSelect>
</span> </div>
<span class="w-1/3 pl-43"> <span class="w-26 flex items-center justify-end mr-4">
<div <div
class="flex items-center gap-2" class="flex items-center gap-2"
:class="{ :class="{
@ -238,23 +242,26 @@ const openDeleteModal = (user: UserType) => {
}" }"
> >
<NcDropdown :trigger="['click']"> <NcDropdown :trigger="['click']">
<MdiDotsVertical <NcButton size="xsmall" type="ghost">
class="border-1 !text-gray-600 h-5.5 w-5.5 rounded outline-0 p-0.5 nc-workspace-menu transform transition-transform !text-gray-400 cursor-pointer hover:(!text-gray-500 bg-gray-100)" <MdiDotsVertical
/> class="text-gray-600 h-5.5 w-5.5 rounded outline-0 p-0.5 nc-workspace-menu transform transition-transform !text-gray-400 cursor-pointer hover:(!text-gray-500 bg-gray-100)"
/>
</NcButton>
<template #overlay> <template #overlay>
<NcMenu> <NcMenu>
<template v-if="!el.roles?.includes('super')"> <template v-if="!el.roles?.includes('super')">
<!-- Resend invite Email --> <!-- Resend invite Email -->
<NcMenuItem @click="resendInvite(el)"> <NcMenuItem @click="resendInvite(el)">
<component :is="iconMap.email" class="flex text-gray-500" /> <component :is="iconMap.email" class="flex text-gray-600" />
<div>{{ $t('activity.resendInvite') }}</div> <div>{{ $t('activity.resendInvite') }}</div>
</NcMenuItem> </NcMenuItem>
<NcMenuItem @click="copyInviteUrl(el)"> <NcMenuItem @click="copyInviteUrl(el)">
<component :is="iconMap.copy" class="flex text-gray-500" /> <component :is="iconMap.copy" class="flex text-gray-600" />
<div>{{ $t('activity.copyInviteURL') }}</div> <div>{{ $t('activity.copyInviteURL') }}</div>
</NcMenuItem> </NcMenuItem>
<NcMenuItem @click="copyPasswordResetUrl(el)"> <NcMenuItem @click="copyPasswordResetUrl(el)">
<component :is="iconMap.copy" class="flex text-gray-500" /> <component :is="iconMap.copy" class="flex text-gray-600" />
<div>{{ $t('activity.copyPasswordResetURL') }}</div> <div>{{ $t('activity.copyPasswordResetURL') }}</div>
</NcMenuItem> </NcMenuItem>
</template> </template>
@ -271,7 +278,7 @@ const openDeleteModal = (user: UserType) => {
</div> </div>
</section> </section>
</div> </div>
<div v-if="pagination.total > 10" class="flex items-center justify-center mt-7"> <div v-if="pagination.total > 10" class="flex items-center justify-center mt-4">
<a-pagination <a-pagination
v-model:current="currentPage" v-model:current="currentPage"
:total="pagination.total" :total="pagination.total"
@ -285,7 +292,7 @@ const openDeleteModal = (user: UserType) => {
<div class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4"> <div class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4">
<GeneralIcon icon="account" class="nc-view-icon"></GeneralIcon> <GeneralIcon icon="account" class="nc-view-icon"></GeneralIcon>
<div <div
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75" class="text-ellipsis overflow-hidden select-none w-full pl-1.75"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }" :style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
> >
{{ deleteModalInfo?.email }} {{ deleteModalInfo?.email }}
@ -301,7 +308,7 @@ const openDeleteModal = (user: UserType) => {
</template> </template>
<style scoped> <style scoped>
.tbody div:nth-child(10) { .user:last-child {
border-bottom: none; @apply rounded-b-md;
} }
</style> </style>

4
packages/nc-gui/components/account/UsersModal.vue

@ -80,7 +80,7 @@ const copyUrl = async () => {
await copy(inviteUrl.value) await copy(inviteUrl.value)
// Copied shareable source url to clipboard! // Copied shareable source url to clipboard!
message.success(t('msg.success.shareableURLCopied')) message.success(t('msg.toast.inviteUrlCopy'))
} catch (e: any) { } catch (e: any) {
message.error(e.message) message.error(e.message)
} }
@ -124,7 +124,7 @@ const emailInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
<div class="flex flex-col mt-1 pb-5"> <div class="flex flex-col mt-1 pb-5">
<div class="flex flex-row items-center pl-1.5 pb-1 h-[1.1rem]"> <div class="flex flex-row items-center pl-1.5 pb-1 h-[1.1rem]">
<component :is="iconMap.account" /> <component :is="iconMap.account" />
<div class="text-xs ml-0.5 mt-0.5">{{ $t('activity.copyInviteToken') }}</div> <div class="text-xs ml-0.5 mt-0.5">{{ $t('activity.copyInviteURL') }}</div>
</div> </div>
<a-alert class="!mt-2" type="success" show-icon> <a-alert class="!mt-2" type="success" show-icon>

2
packages/nc-gui/components/dlg/ProjectDelete.vue

@ -52,7 +52,7 @@ const onDelete = async () => {
<GeneralDeleteModal v-model:visible="visible" :entity-name="$t('objects.project')" :on-delete="onDelete"> <GeneralDeleteModal v-model:visible="visible" :entity-name="$t('objects.project')" :on-delete="onDelete">
<template #entity-preview> <template #entity-preview>
<div v-if="base" class="flex flex-row items-center py-2 px-2.25 bg-gray-50 rounded-lg text-gray-700 mb-4"> <div v-if="base" class="flex flex-row items-center py-2 px-2.25 bg-gray-50 rounded-lg text-gray-700 mb-4">
<GeneralProjectIcon :type="base.type" class="nc-view-icon px-1.5"></GeneralProjectIcon> <GeneralProjectIcon :type="base.type" class="nc-view-icon px-1.5 w-10" />
<div <div
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75" class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }" :style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"

7
packages/nc-gui/lang/en.json

@ -388,6 +388,7 @@
} }
}, },
"labels": { "labels": {
"noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Only one token per user is allowed",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"toAddress": "To Address", "toAddress": "To Address",
@ -675,8 +676,8 @@
"newUser": "New User", "newUser": "New User",
"editUser": "Edit user", "editUser": "Edit user",
"deleteUser": "Remove user from base", "deleteUser": "Remove user from base",
"resendInvite": "Resend invite E-mail", "resendInvite": "Resend Invite E-mail",
"copyInviteURL": "Copy invite URL", "copyInviteURL": "Copy Invite URL",
"copyPasswordResetURL": "Copy password reset URL", "copyPasswordResetURL": "Copy password reset URL",
"newRole": "New role", "newRole": "New role",
"reloadRoles": "Reload roles", "reloadRoles": "Reload roles",
@ -1198,7 +1199,7 @@
"deleteProject": "Base deleted successfully", "deleteProject": "Base deleted successfully",
"authToken": "Auth token copied to clipboard", "authToken": "Auth token copied to clipboard",
"projInfo": "Copied base info to clipboard", "projInfo": "Copied base info to clipboard",
"inviteUrlCopy": "Copied invite URL to clipboard", "inviteUrlCopy": "Copied Invite URL to clipboard",
"createView": "View created successfully", "createView": "View created successfully",
"formEmailSMTP": "Please activate SMTP plugin in App store for enabling email notification", "formEmailSMTP": "Please activate SMTP plugin in App store for enabling email notification",
"collabView": "Successfully Switched to collaborative view", "collabView": "Successfully Switched to collaborative view",

13
packages/nc-gui/pages/account/index.vue

@ -145,7 +145,7 @@ const logout = async () => {
<!-- Sub Tabs --> <!-- Sub Tabs -->
<div class="flex flex-col w-full ml-65"> <div class="flex flex-col w-full ml-65">
<div class="flex flex-row p-3 items-center"> <div class="flex flex-row p-3 items-center h-14">
<div class="flex-1" /> <div class="flex-1" />
<LazyGeneralReleaseInfo /> <LazyGeneralReleaseInfo />
@ -185,8 +185,15 @@ const logout = async () => {
</NcDropdown> </NcDropdown>
</template> </template>
</div> </div>
<div class="flex flex-col container mx-auto mt-2"> <div
<NuxtPage /> class="flex flex-col container mx-auto"
:style="{
height: 'calc(100vh - 3.5rem)',
}"
>
<div class="mt-2 h-full">
<NuxtPage />
</div>
</div> </div>
</div> </div>
</div> </div>

2
tests/playwright/pages/Account/Users.ts

@ -88,7 +88,7 @@ export class AccountUsersPage extends BasePage {
async openRowActionMenu({ email }: { email: string }) { async openRowActionMenu({ email }: { email: string }) {
const userRow = await this.getUserRow({ email }); const userRow = await this.getUserRow({ email });
return userRow.locator(`.nc-icon`).click(); return userRow.locator(`.ant-btn`).click();
} }
async deleteUser({ email }: { email: string }) { async deleteUser({ email }: { email: string }) {

Loading…
Cancel
Save