Skip to content

Commit

Permalink
Merge pull request #322 from MerginMaps/redesign-final-1
Browse files Browse the repository at this point in the history
Redesign final 1
  • Loading branch information
MarcelGeo authored Nov 11, 2024
2 parents b1d085a + 8c4915c commit 45444a0
Show file tree
Hide file tree
Showing 16 changed files with 223 additions and 57 deletions.
9 changes: 6 additions & 3 deletions server/mergin/auth/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
user_account_closed,
)
from .bearer import encode_token
from .models import User, LoginHistory
from .models import User, LoginHistory, UserProfile
from .schemas import UserSchema, UserSearchSchema, UserProfileSchema, UserInfoSchema
from .forms import (
LoginForm,
Expand Down Expand Up @@ -449,13 +449,16 @@ def get_paginated_users(
:rtype: Dict[str: List[User], str: Integer]
"""
users = User.query.filter(
users = User.query.join(UserProfile).filter(
is_(User.username.ilike("deleted_%"), False) | is_(User.active, True)
)

if like:
users = users.filter(
User.username.ilike(f"%{like}%") | User.email.ilike(f"%{like}%")
User.username.ilike(f"%{like}%")
| User.email.ilike(f"%{like}%")
| UserProfile.first_name.ilike(f"%{like}%")
| UserProfile.last_name.ilike(f"%{like}%")
)

if descending and order_by:
Expand Down
2 changes: 1 addition & 1 deletion web-app/packages/admin-app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="/favicon.ico" />
<title>Mergin Maps</title>
<title>Mergin Maps Admin Panel</title>
</head>
<body>
<noscript>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const sidebarItems = computed<SideBarItemModel[]>(() => [

<template>
<side-bar-template :sidebarItems="sidebarItems">
<template #subtitle>Administration</template>
<template #subtitle>Admin panel</template>
<template #footer>
<sidebar-footer />
</template>
Expand Down
5 changes: 4 additions & 1 deletion web-app/packages/admin-lib/src/modules/admin/adminApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ApiRequestSuccessInfo,
errorUtils,
LoginData,
PaginatedUsersParams,
UserProfileResponse,
UserResponse /*, getDefaultRetryOptions */
} from '@mergin/lib'
Expand All @@ -27,7 +28,9 @@ export const AdminApi = {
return AdminModule.httpService.post('/app/admin/login', data)
},

async fetchUsers(params: UsersParams): Promise<AxiosResponse<UsersResponse>> {
async fetchUsers(
params: PaginatedUsersParams
): Promise<AxiosResponse<UsersResponse>> {
return AdminModule.httpService.get(`/app/admin/users`, { params })
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<span class="p-input-icon-left w-full">
<i class="ti ti-search paragraph-p3"></i>
<PInputText
placeholder="Search members"
placeholder="Search accounts"
data-cy="search-members-field"
v-model="searchByName"
class="w-full"
Expand Down Expand Up @@ -141,9 +141,9 @@ export default defineComponent({
},
searchByName: '',
headers: [
{ field: 'username', header: 'Name', sortable: true },
{ field: 'username', header: 'Username', sortable: true },
{ field: 'email', header: 'Email', sortable: true },
{ field: 'profile.name', header: 'Username' },
{ field: 'profile.name', header: 'Full name' },
{ field: 'active', header: 'Active' }
] as TableDataHeader[]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
:sortable="header.sortable"
>
<template #body="slotProps">
<router-link
class="title-t4"
:to="{
name: `adminWorkspace`,
params: { id: slotProps.data.workspace_id }
}"
>
{{ slotProps.data.workspace }}
</router-link>
{{ slotProps.data.workspace }}
</template>
</PColumn>

Expand All @@ -74,16 +66,21 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
:sortable="header.sortable"
>
<template #body="slotProps">
<templte v-if="slotProps.data.removed_at">{{
slotProps.data.name
}}</templte>
<router-link
v-else
:to="{
name: 'project',
params: {
namespace: slotProps.data.workspace,
projectName: slotProps.data.name
}
}"
class="font-semibold"
>
<strong>{{ slotProps.data.name }}</strong>
{{ slotProps.data.name }}
</router-link>
</template>
</PColumn>
Expand Down Expand Up @@ -119,7 +116,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
:sortable="header.sortable"
>
<template #body="slotProps">
<span :title="$filters.datetime(slotProps.data.removed_at)">
<span
:title="`Scheduled for removal at ${$filters.datetime(
slotProps.data.removed_at
)}`"
>
{{ $filters.timediff(slotProps.data.removed_at) }}
</span>
</template>
Expand All @@ -131,12 +132,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
class="justify-center px-0"
v-if="slotProps.data.removed_at"
>
<div style="text-align: end">
<div class="flex align-items-center gap-1">
<PButton
label="Restore"
severity="secondary"
size="small"
@click="confirmRestore(slotProps.data)"
/>
<PButton
label="Delete"
severity="danger"
size="small"
@click="confirmDelete(slotProps.data)"
/>
</div>
</div>
</template>
Expand Down Expand Up @@ -228,7 +236,7 @@ export default defineComponent({
{ header: 'Name', field: 'name', sortable: true },
{ header: 'Last Update', field: 'updated', sortable: true },
{ header: 'Size', field: 'disk_usage', sortable: true },
{ header: 'Removed', field: 'removed_at', sortable: true },
{ header: 'Scheduled removal at', field: 'removed_at', sortable: true },
{ header: 'Removed by', field: 'removed_by', sortable: true },
{ header: '', field: 'buttons', sortable: false }
]
Expand All @@ -241,7 +249,11 @@ export default defineComponent({
methods: {
...mapActions(useDialogStore, { showDialog: 'show' }),
...mapActions(useNotificationStore, ['error', 'show']),
...mapActions(useAdminStore, ['getProjects', 'restoreProject']),
...mapActions(useAdminStore, [
'getProjects',
'restoreProject',
'deleteProject'
]),
paginating(options) {
this.options = options
Expand Down Expand Up @@ -302,6 +314,31 @@ export default defineComponent({
})
},
confirmDelete(item) {
const props: ConfirmDialogProps = {
text: `Are you sure you want to permanently delete this project?`,
description: `Deleting this project will remove it
and all its data. This action cannot be undone. Type in project name to confirm:`,
hint: item.name,
confirmText: 'Delete permanently',
confirmField: {
label: 'Project name',
expected: item.name
},
severity: 'danger'
}
const listeners = {
confirm: async () => {
await this.deleteProject({ projectId: item.id })
this.fetchProjects()
}
}
this.showDialog({
component: ConfirmDialog,
params: { props, listeners, dialog: { header: 'Delete project' } }
})
},
onRefresh() {
this.fetchProjects()
}
Expand Down
7 changes: 5 additions & 2 deletions web-app/packages/admin-lib/src/modules/admin/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
errorUtils,
htmlUtils,
LoginPayload,
PaginatedUsersParams,
SortingOptions,
useFormStore,
useInstanceStore,
Expand All @@ -14,6 +15,9 @@ import {
} from '@mergin/lib'
import { defineStore, getActivePinia } from 'pinia'
import Cookies from 'universal-cookie'

import { AdminRoutes } from './routes'

import { AdminApi } from '@/modules/admin/adminApi'
import {
LatestServerVersionResponse,
Expand All @@ -22,7 +26,6 @@ import {
UpdateUserPayload,
UsersResponse
} from '@/modules/admin/types'
import { AdminRoutes } from './routes'

export interface AdminState {
loading: boolean
Expand Down Expand Up @@ -110,7 +113,7 @@ export const useAdminStore = defineStore('adminModule', {
this.isServerConfigHidden = value
},

async fetchUsers(payload) {
async fetchUsers(payload: { params: PaginatedUsersParams }) {
const notificationStore = useNotificationStore()

this.setLoading(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,17 @@
</div>
</app-section>
</app-container>
<app-container>
<app-container v-if="userStore.loggedUser?.id !== user?.id">
<app-section>
<template #title>Advanced</template>
<app-settings :items="settingsItems">
<template #notifications>
<div class="flex-shrink-0 paragraph-p1">
<i v-if="profile?.receive_notifications" class="ti ti-check" />
<i v-else class="ti ti-x" />
<PInputSwitch
:model-value="profile?.receive_notifications"
disabled
/>
</div>
</template>
<template #adminAccess>
Expand All @@ -78,9 +80,15 @@
class="flex align-items-center flex-shrink-0"
data-cy="profile-notification"
>
<PInputSwitch
<PButton
:severity="user?.is_admin ? 'danger' : 'warning'"
:modelValue="user?.is_admin"
@change="switchAdminAccess"
@click="switchAdminAccess"
:label="
!user?.is_admin
? 'Grant admin access'
: 'Revoke admin access'
"
/>
</div>
</div>
Expand All @@ -89,7 +97,7 @@
<div class="flex-shrink-0">
<PButton
@click="changeStatusDialog"
:severity="user?.active ? 'danger' : 'secondary'"
:severity="user?.active ? 'warning' : 'secondary'"
:label="
user?.active ? 'Deactivate account' : 'Activate account'
"
Expand Down Expand Up @@ -121,7 +129,8 @@ import {
AppContainer,
ConfirmDialogProps,
AppSettings,
AppSettingsItemConfig
AppSettingsItemConfig,
useUserStore
} from '@mergin/lib'
import { computed, watch } from 'vue'
import { useRoute } from 'vue-router'
Expand All @@ -132,6 +141,7 @@ import { useAdminStore } from '@/modules/admin/store'
const route = useRoute()
const adminStore = useAdminStore()
const dialogStore = useDialogStore()
const userStore = useUserStore()
const settingsItems = computed<AppSettingsItemConfig[]>(() => [
{
Expand All @@ -144,7 +154,9 @@ const settingsItems = computed<AppSettingsItemConfig[]>(() => [
{
key: 'adminAccess',
title: 'Access to admin panel',
description: 'Enabling this option will provide access to the admin panel.'
description: user.value?.is_admin
? 'User has access to the admin panel.'
: 'User does not have access to the admin panel.'
},
{
key: 'accountActivation',
Expand Down Expand Up @@ -181,12 +193,18 @@ watch(
)
const changeStatusDialog = () => {
const props: ConfirmDialogProps = {
confirmText: 'Yes',
text: user.value.active
? 'Do you really want deactivate this account?'
: 'Do you really want activate this account?'
}
const props: ConfirmDialogProps = user.value.active
? {
confirmText: 'Deactivate',
severity: 'warning',
text: 'Do you really want deactivate this account?',
description:
'Deactivating this account will lead to a temporary ban from Mergin Maps usage.'
}
: {
text: 'Do you really want activate this account?',
confirmText: 'Activate'
}
const dialog = { header: 'User activation' }
const listeners = {
confirm: async () => {
Expand Down Expand Up @@ -233,12 +251,42 @@ const confirmDeleteUser = () => {
}
const switchAdminAccess = async () => {
await adminStore.updateUser({
username: user.value.username,
data: {
active: user.value.active,
is_admin: !user.value.is_admin
}
const props: ConfirmDialogProps = !user.value?.is_admin
? {
text: `Are you sure to grant access to admin panel to this user?`,
description: `This person will have full management access to all data on the server. They will see all users and projects and can update or remove them.`,
hint: user.value.username,
confirmText: 'Grant access',
confirmField: {
label: 'Username',
expected: user.value.username
},
severity: 'warning'
}
: {
text: `Are you sure you want to revoke access to admin panel to this user?`,
description: `This person will no longer have access to the admin panel.`,
hint: user.value.username,
confirmText: 'Revoke access',
confirmField: {
label: 'Username',
expected: user.value.username
},
severity: 'danger'
}
const listeners = {
confirm: async () =>
await adminStore.updateUser({
username: user.value.username,
data: {
active: user.value.active,
is_admin: !user.value.is_admin
}
})
}
dialogStore.show({
component: ConfirmDialog,
params: { props, listeners, dialog: { header: 'Admin access' } }
})
}
</script>
Expand Down
Loading

0 comments on commit 45444a0

Please sign in to comment.