| @@ -20,6 +20,7 @@ import { | |||||
| GetRewardFundResponse, | GetRewardFundResponse, | ||||
| GetRewardFundsRequest, | GetRewardFundsRequest, | ||||
| GetRewardFundsResponse, | GetRewardFundsResponse, | ||||
| GetUsersResponse, | |||||
| LoginResponse, | LoginResponse, | ||||
| NearlyCompleteFundsRequest, | NearlyCompleteFundsRequest, | ||||
| NearlyCompleteFundsResponse, | NearlyCompleteFundsResponse, | ||||
| @@ -31,6 +32,8 @@ import { | |||||
| const controller = new SignetRequestController(); | const controller = new SignetRequestController(); | ||||
| export const usersExist = () => controller.post<SuccessResponse, null>('/UsersExist', null); | |||||
| export const register = (username: string, password: string) => controller.post<SuccessResponse, AuthenticationRequest>('Register', { | export const register = (username: string, password: string) => controller.post<SuccessResponse, AuthenticationRequest>('Register', { | ||||
| username, | username, | ||||
| password, | password, | ||||
| @@ -128,3 +131,5 @@ export const distributeRewardFund = (rewardFundID: number, payments: RewardDistr | |||||
| payments, | payments, | ||||
| distribute, | distribute, | ||||
| }); | }); | ||||
| export const getUsers = () => controller.post<GetUsersResponse, null>('GetUsers', null); | |||||
| @@ -208,3 +208,13 @@ export interface DistributeRewardsRequest { | |||||
| payments: RewardDistributionInfo[]; | payments: RewardDistributionInfo[]; | ||||
| distribute: boolean; | distribute: boolean; | ||||
| } | } | ||||
| export interface User { | |||||
| username: string, | |||||
| password: string, | |||||
| admin: number, | |||||
| } | |||||
| export interface GetUsersResponse { | |||||
| users: User[]; | |||||
| } | |||||
| @@ -13,10 +13,11 @@ import HomeView from '@/views/HomeView.vue'; | |||||
| import FundView from '@/views/FundView.vue'; | import FundView from '@/views/FundView.vue'; | ||||
| import AddFundView from '@/views/AddFundView.vue'; | import AddFundView from '@/views/AddFundView.vue'; | ||||
| import hasPermission from '@/lib/auth'; | import hasPermission from '@/lib/auth'; | ||||
| import SignetRequestController from '@/api/requests'; | |||||
| import AdminView from '@/views/AdminView.vue'; | import AdminView from '@/views/AdminView.vue'; | ||||
| import ModifyQueueView from '@/views/ModifyQueueView.vue'; | import ModifyQueueView from '@/views/ModifyQueueView.vue'; | ||||
| import AdminDashboardView from '@/views/AdminDashboardView.vue'; | import AdminDashboardView from '@/views/AdminDashboardView.vue'; | ||||
| import { usersExist } from '@/api/composed'; | |||||
| import ModifyUserView from '@/views/ModifyUserView.vue'; | |||||
| const routes: Array<RouteRecordRaw> = [ | const routes: Array<RouteRecordRaw> = [ | ||||
| { | { | ||||
| @@ -44,8 +45,7 @@ const routes: Array<RouteRecordRaw> = [ | |||||
| meta: { | meta: { | ||||
| requiredRights: Privileges.AdminPlus, | requiredRights: Privileges.AdminPlus, | ||||
| accessible: async () => { | accessible: async () => { | ||||
| const controller = new SignetRequestController(); | |||||
| const canProceed = await controller.post<SuccessResponse, null>('/UsersExist', null); | |||||
| const canProceed = await usersExist(); | |||||
| return canProceed?.success; | return canProceed?.success; | ||||
| }, | }, | ||||
| title: 'Register', | title: 'Register', | ||||
| @@ -102,6 +102,24 @@ const routes: Array<RouteRecordRaw> = [ | |||||
| title: 'Add Group Fund', | title: 'Add Group Fund', | ||||
| }, | }, | ||||
| }, | }, | ||||
| { | |||||
| path: 'adduser', | |||||
| name: 'adduser', | |||||
| component: RegisterView, | |||||
| meta: { | |||||
| requiredRights: Privileges.AdminPlus, | |||||
| title: 'Add User', | |||||
| }, | |||||
| }, | |||||
| { | |||||
| path: 'modifyuser', | |||||
| name: 'modifyuser', | |||||
| component: ModifyUserView, | |||||
| meta: { | |||||
| requiredRights: Privileges.AdminPlus, | |||||
| title: 'Modify User', | |||||
| }, | |||||
| }, | |||||
| ], | ], | ||||
| meta: { | meta: { | ||||
| requiredRights: Privileges.Admin, | requiredRights: Privileges.Admin, | ||||
| @@ -3,12 +3,15 @@ | |||||
| <section class="section is-small px-0"> | <section class="section is-small px-0"> | ||||
| <template v-if="nearlyCompletedFunds.length > 0"> | <template v-if="nearlyCompletedFunds.length > 0"> | ||||
| <div class="title is-4 has-text-white-ter has-text-centered">Nearly Completed Funds</div> | <div class="title is-4 has-text-white-ter has-text-centered">Nearly Completed Funds</div> | ||||
| <div v-for="(fund, ind) in nearlyCompletedFunds" :key="ind"> | |||||
| <div v-for="(fund, ind) in nearlyCompletedFunds" :key="ind" class="my-3"> | |||||
| <RouterLink :to="`/fund/${fund.id}`"> | <RouterLink :to="`/fund/${fund.id}`"> | ||||
| <FundLink :fund="fund" :aside="`${fund.amountAvailable} remaining`"/> | <FundLink :fund="fund" :aside="`${fund.amountAvailable} remaining`"/> | ||||
| </RouterLink> | </RouterLink> | ||||
| </div> | </div> | ||||
| </template> | </template> | ||||
| <template v-else> | |||||
| No funds are nearly complete. | |||||
| </template> | |||||
| </section> | </section> | ||||
| </div> | </div> | ||||
| </template> | </template> | ||||
| @@ -29,14 +29,18 @@ const links = [ | |||||
| text: 'Add Fund', | text: 'Add Fund', | ||||
| to: '/admin/addfund', | to: '/admin/addfund', | ||||
| }, | }, | ||||
| { | |||||
| text: 'Modify Fund', | |||||
| to: '/admin/modifyfund', | |||||
| }, | |||||
| { | { | ||||
| text: 'Add/Modify Queue', | text: 'Add/Modify Queue', | ||||
| to: '/admin/modifyqueue', | to: '/admin/modifyqueue', | ||||
| }, | }, | ||||
| { | |||||
| text: 'Add User', | |||||
| to: '/admin/adduser', | |||||
| }, | |||||
| { | |||||
| text: 'Modify User', | |||||
| to: '/admin/modifyuser', | |||||
| }, | |||||
| ]; | ]; | ||||
| </script> | </script> | ||||
| @@ -0,0 +1,83 @@ | |||||
| <template> | |||||
| <section class="section"> | |||||
| <table> | |||||
| <tr> | |||||
| <th> | |||||
| Username | |||||
| </th> | |||||
| <th> | |||||
| Password | |||||
| </th> | |||||
| <th> | |||||
| Privileges | |||||
| </th> | |||||
| </tr> | |||||
| <tr v-for="user in users" :key="user.username"> | |||||
| <td>{{ user.username }}</td> | |||||
| <td> | |||||
| <template v-if="userData.username === user.username || userData.privileges < 2"> | |||||
| <input type="password" | |||||
| class="input is-small" :aria-label="`${user.username}'s Password`"> | |||||
| </template> | |||||
| <template v-else> | |||||
| ******** | |||||
| </template> | |||||
| </td> | |||||
| <td> | |||||
| <select class="select is-small" name="" id="" aria-label="User Privilege"> | |||||
| <option :value="privilege" | |||||
| :selected="getPrivilege(user.admin) === privilege" | |||||
| v-for="(privilege, i) in Object.values(privileges)" :key="i"> | |||||
| {{ privilege }} | |||||
| </option> | |||||
| </select> | |||||
| </td> | |||||
| </tr> | |||||
| </table> | |||||
| </section> | |||||
| </template> | |||||
| <script setup lang="ts"> | |||||
| import { | |||||
| Claims, | |||||
| Privileges, | |||||
| User, | |||||
| } from '@/api/types'; | |||||
| import { ref } from 'vue'; | |||||
| import { getUsers } from '@/api/composed'; | |||||
| import jwtDecode from 'jwt-decode'; | |||||
| import store from '@/store'; | |||||
| const users = ref<User[]>(); | |||||
| const resp = await getUsers(); | |||||
| users.value = resp?.users; | |||||
| const userData = ref<Claims>({ | |||||
| username: '', | |||||
| privileges: -1, | |||||
| exp: -1, | |||||
| }); | |||||
| userData.value = jwtDecode<Claims>(store.getters.getToken); | |||||
| const getPrivilege = (privilege: number) => Privileges[privilege]; | |||||
| const getPrivileges = () => Object.fromEntries( | |||||
| Object.entries(Privileges) | |||||
| .filter((p) => /^[0-9]+$/.test(p[1].toString())) | |||||
| .map((p) => p.reverse()), | |||||
| ); | |||||
| const privileges = getPrivileges(); | |||||
| </script> | |||||
| <style scoped lang="stylus"> | |||||
| table | |||||
| width 100% | |||||
| table th | |||||
| color #bdbdbd | |||||
| table td | |||||
| font-family monospace | |||||
| </style> | |||||