@@ -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> |