Skip to content

Commit

Permalink
feat: #1059 application admin management UI (#1162)
Browse files Browse the repository at this point in the history
Co-authored-by: Nicola Saglioni <nicola.saglioni@encora.com>
  • Loading branch information
NickSaglioni and NickSaglioni authored Feb 6, 2024
1 parent 9a18497 commit 05a1124
Show file tree
Hide file tree
Showing 15 changed files with 272 additions and 49 deletions.
4 changes: 4 additions & 0 deletions frontend/src/assets/styles/layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ body {
margin: 0;
}

label {
margin-bottom: 0.5rem;
}

// Container
.main {
position: absolute;
Expand Down
188 changes: 188 additions & 0 deletions frontend/src/components/grantaccess/GrantApplicationAdmin.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { ErrorMessage, Field, Form as VeeForm } from 'vee-validate';
import { object, string } from 'yup';
import router from '@/router';
import Dropdown from 'primevue/dropdown';
import { AdminMgmtApiService } from '@/services/ApiServiceFactory';
import { UserType, type FamApplication } from 'fam-app-acsctl-api';
import type { FamAppAdminCreateRequest } from 'fam-admin-mgmt-api/model/fam-app-admin-create-request';
import type { FamApplicationGetResponse } from 'fam-admin-mgmt-api/model/fam-application-get-response';
import Button from '@/components/common/Button.vue';
import { IconSize } from '@/enum/IconEnum';
import { Severity } from '@/enum/SeverityEnum';
import { isLoading } from '@/store/LoadingState';
import { setNotificationMsg } from '@/store/NotificationState';
const defaultFormData = {
userId: '',
application: Number,
};
const formData = ref(JSON.parse(JSON.stringify(defaultFormData))); // clone default input
const formValidationSchema = object({
userId: string()
.required('User ID is required')
.min(2, 'User ID must be at least 2 characters')
.nullable(),
application: object().required('Application is required'),
});
const applications = ref<FamApplicationGetResponse[]>();
// This is going to be changed when the new backend API is ready
onMounted(async () => {
applications.value = (
await AdminMgmtApiService.applicationsApi.getApplications()
).data;
});
/* ------------------ User information method ------------------------- */
const userIdChange = (userId: string) => {
formData.value.userId = userId;
};
const verifyUserIdPassed = ref(false);
const setVerifyUserIdPassed = (verifiedResult: boolean) => {
verifyUserIdPassed.value = verifiedResult;
};
/* ---------------------- Form method ---------------------------------- */
const cancelForm = () => {
formData.value = defaultFormData;
router.push('/dashboard');
};
const toRequestPayload = (formData: any) => {
const request = {
user_name: formData.userId,
application_id: formData.application.application_id,
user_type_code: UserType.I,
} as FamAppAdminCreateRequest;
return request;
};
const handleSubmit = async () => {
const data = toRequestPayload(formData.value);
await AdminMgmtApiService.applicationAdminApi
.createApplicationAdmin(data)
.then(() => {
setNotificationMsg(
Severity.success,
`Admin privilege has been added to ${formData.value.userId.toUpperCase()} for application ${
formData.value.application.application_name
}`
);
})
.catch((error) => {
if (error.response?.status === 409) {
setNotificationMsg(
Severity.warning,
error.response.data.detail
);
} else if (
error.response.data.detail.code === 'self_grant_prohibited'
) {
setNotificationMsg(
Severity.error,
'Granting admin privilege to self is not allowed.'
);
} else {
setNotificationMsg(
Severity.success,
`An error has occured. ${error.response.data.detail.description}`
);
}
})
.finally(() => {
router.push('/dashboard');
});
};
</script>
<template>
<PageTitle
title="Add application admin"
subtitle="Add a new application admin. All fields are mandatory unless noted"
/>
<VeeForm
ref="form"
v-slot="{ errors, meta }"
:validation-schema="formValidationSchema"
as="div"
>
<div class="page-body">
<form id="grantAdminForm" class="form-container">
<StepContainer
title="User information"
:subtitle="`Enter the user information to add a new admin ${
formData.application
? 'to ' + formData.application.application_name
: ''
}`"
>
<UserNameInput
:domain="UserType.I"
:userId="formData.userId"
helper-text="Only IDIR usernames are allowed"
@change="userIdChange"
@setVerifyResult="setVerifyUserIdPassed"
/>
</StepContainer>
<StepContainer
title="Add application"
subtitle="Select an application this user will be able to manage"
:divider="false"
>
<Field
name="application"
aria-label="Application Select"
v-model="formData.application"
>
<div class="application-admin-group">
<label>Select application</label>
<Dropdown
v-model="formData.application"
:options="(applications as FamApplication[])"
optionLabel="application_description"
placeholder="Choose an application"
/>
</div>
<ErrorMessage
class="invalid-feedback"
name="application"
style="display: inline"
/>
</Field>
</StepContainer>
<div class="button-stack">
<Button
type="button"
id="grantAccessCancel"
class="w100"
severity="secondary"
label="Cancel"
:disabled="isLoading()"
@click="cancelForm()"
>&nbsp;</Button
>
<Button
type="button"
id="grantAccessSubmit"
class="w100"
label="Submit Application"
:disabled="
!(meta.valid && verifyUserIdPassed) || isLoading()
"
@click="handleSubmit()"
>
<Icon icon="checkmark" :size="IconSize.small" />
</Button>
</div>
</form>
</div>
</VeeForm>
</template>
<style scoped lang="scss">
.application-admin-group {
display: grid;
}
</style>
6 changes: 5 additions & 1 deletion frontend/src/components/grantaccess/form/UserNameInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ const props = defineProps({
domain: { type: String, required: true },
userId: { type: String, default: '' },
fieldId: { type: String, default: 'userId' },
helperText: {
type: String,
default: 'Enter and verify the username for this user',
},
});
const emit = defineEmits(['change', 'setVerifyResult']);
Expand Down Expand Up @@ -86,7 +90,7 @@ watch(
id="userIdInput-helper"
class="helper-text"
v-if="!errorMessage"
>Enter and verify the username for this user</small
>{{ helperText }}</small
>
<ErrorMessage
class="invalid-feedback"
Expand Down
26 changes: 9 additions & 17 deletions frontend/src/components/managePermissions/ManagePermissions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
isApplicationSelected,
selectedApplication,
setSelectedApplication,
selectedApplicationId
selectedApplicationId,
} from '@/store/ApplicationState';
import { isLoading } from '@/store/LoadingState';
import {
Expand All @@ -26,7 +26,7 @@ import {
deletAndRefreshUserRoleAssignments,
deleteAndRefreshApplicationAdmin,
fetchUserRoleAssignments,
fetchApplicationAdmins
fetchApplicationAdmins,
} from '@/services/fetchData';
import { Severity } from '@/enum/SeverityEnum';
import { IconSize } from '@/enum/IconEnum';
Expand All @@ -46,9 +46,9 @@ const userRoleAssignments = shallowRef<FamApplicationUserRoleAssignmentGet[]>(
props.userRoleAssignments
);
const applicationAdmins = shallowRef<
FamAppAdminGetResponse[]
>(props.applicationAdmins);
const applicationAdmins = shallowRef<FamAppAdminGetResponse[]>(
props.applicationAdmins
);
onUnmounted(() => {
resetNotification();
Expand Down Expand Up @@ -84,26 +84,25 @@ const deleteUserRoleAssignment = async (
`An error has occured. ${error.response.data.detail.description}`
);
}
}
};
const deleteAppAdmin = async (admin: FamAppAdminGetResponse) => {
try {
applicationAdmins.value = await deleteAndRefreshApplicationAdmin(
admin.application_admin_id,
admin.application_admin_id
);
setNotificationMsg(
Severity.success,
`You removed ${admin.user.user_name}'s admin privilege`
);
} catch (error: any) {
setNotificationMsg(
Severity.error,
`An error has occured. ${error.response.data.detail.description}`
);
}
}
};
</script>

<template>
Expand Down Expand Up @@ -149,10 +148,7 @@ const deleteAppAdmin = async (admin: FamAppAdminGetResponse) => {
@deleteAppAdmin="deleteAppAdmin"
/>
</TabPanel>
<TabPanel
header="Users"
v-else
>
<TabPanel header="Users" v-else>
<template #header>
<Icon icon="user" :size="IconSize.small" />
</template>
Expand Down Expand Up @@ -185,10 +181,6 @@ const deleteAppAdmin = async (admin: FamAppAdminGetResponse) => {
@import '@/assets/styles/base.scss';
.application-group {
display: grid;
label {
margin-bottom: 0.5rem;
}
}
.application-dropdown {
Expand Down
Loading

0 comments on commit 05a1124

Please sign in to comment.