| @@ -20,6 +20,7 @@ import { | |||
| GetRewardFundResponse, | |||
| GetRewardFundsRequest, | |||
| GetRewardFundsResponse, | |||
| GetUsersResponse, | |||
| LoginResponse, | |||
| NearlyCompleteFundsRequest, | |||
| NearlyCompleteFundsResponse, | |||
| @@ -31,6 +32,8 @@ import { | |||
| 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', { | |||
| username, | |||
| password, | |||
| @@ -128,3 +131,5 @@ export const distributeRewardFund = (rewardFundID: number, payments: RewardDistr | |||
| payments, | |||
| distribute, | |||
| }); | |||
| export const getUsers = () => controller.post<GetUsersResponse, null>('GetUsers', null); | |||
| @@ -208,3 +208,13 @@ export interface DistributeRewardsRequest { | |||
| payments: RewardDistributionInfo[]; | |||
| 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 AddFundView from '@/views/AddFundView.vue'; | |||
| import hasPermission from '@/lib/auth'; | |||
| import SignetRequestController from '@/api/requests'; | |||
| import AdminView from '@/views/AdminView.vue'; | |||
| import ModifyQueueView from '@/views/ModifyQueueView.vue'; | |||
| import AdminDashboardView from '@/views/AdminDashboardView.vue'; | |||
| import { usersExist } from '@/api/composed'; | |||
| import ModifyUserView from '@/views/ModifyUserView.vue'; | |||
| const routes: Array<RouteRecordRaw> = [ | |||
| { | |||
| @@ -44,8 +45,7 @@ const routes: Array<RouteRecordRaw> = [ | |||
| meta: { | |||
| requiredRights: Privileges.AdminPlus, | |||
| accessible: async () => { | |||
| const controller = new SignetRequestController(); | |||
| const canProceed = await controller.post<SuccessResponse, null>('/UsersExist', null); | |||
| const canProceed = await usersExist(); | |||
| return canProceed?.success; | |||
| }, | |||
| title: 'Register', | |||
| @@ -102,6 +102,24 @@ const routes: Array<RouteRecordRaw> = [ | |||
| 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: { | |||
| requiredRights: Privileges.Admin, | |||
| @@ -3,12 +3,15 @@ | |||
| <section class="section is-small px-0"> | |||
| <template v-if="nearlyCompletedFunds.length > 0"> | |||
| <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}`"> | |||
| <FundLink :fund="fund" :aside="`${fund.amountAvailable} remaining`"/> | |||
| </RouterLink> | |||
| </div> | |||
| </template> | |||
| <template v-else> | |||
| No funds are nearly complete. | |||
| </template> | |||
| </section> | |||
| </div> | |||
| </template> | |||
| @@ -29,14 +29,18 @@ const links = [ | |||
| text: 'Add Fund', | |||
| to: '/admin/addfund', | |||
| }, | |||
| { | |||
| text: 'Modify Fund', | |||
| to: '/admin/modifyfund', | |||
| }, | |||
| { | |||
| text: 'Add/Modify Queue', | |||
| to: '/admin/modifyqueue', | |||
| }, | |||
| { | |||
| text: 'Add User', | |||
| to: '/admin/adduser', | |||
| }, | |||
| { | |||
| text: 'Modify User', | |||
| to: '/admin/modifyuser', | |||
| }, | |||
| ]; | |||
| </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> | |||