implement api key page

This commit is contained in:
MiniDigger 2022-03-21 20:21:19 +01:00
parent 0887a25ae2
commit 7bed534ecc
6 changed files with 174 additions and 7 deletions

View File

@ -19,6 +19,7 @@ Stuff that needs to be done before I consider this a successful POC
- [x] date formatting needs to go thru i18n
- [x] investigate why eslint/prettier don't auto fix
- [x] actually implement page transitions (as opposed to popping up below the page)
- [ ] validation of forms/inputs etc
## Big list of pages!
@ -114,10 +115,10 @@ once QA has passed, the checkboxes can be removed and the page can be ~~striked
- [ ] qa
* settings (empty)
- api-keys
- [ ] fetch
- [ ] layout
- [ ] functionality
- [ ] design
- [x] fetch
- [x] layout
- [ ] functionality (loading animations, validation)
- [x] design
- [ ] qa
* [project]
- [ ] fetch

View File

@ -0,0 +1,5 @@
<template>
<div class="rounded bg-red-500 p-4">
<slot></slot>
</div>
</template>

View File

@ -15,7 +15,7 @@ const props = defineProps<{
</script>
<template>
<label class="group relative cursor-pointer pl-30px customCheckboxContainer">
<label class="group relative cursor-pointer pl-30px customCheckboxContainer w-max">
<template v-if="props.label">{{ props.label }}</template>
<input v-model="value" type="checkbox" class="hidden" v-bind="$attrs" />
<span

View File

@ -15,5 +15,5 @@ const props = defineProps<{
<template>
<!-- todo make fancy -->
<input v-model="date" type="date" />
<input v-model="date" type="date" v-bind="$attrs" />
</template>

View File

@ -0,0 +1,23 @@
<script lang="ts" setup>
import { computed } from "vue";
const emit = defineEmits<{
(e: "update:modelValue", value: string): void;
}>();
const value = computed({
get: () => props.modelValue,
set: (v) => emit("update:modelValue", v),
});
const props = defineProps<{
modelValue: string;
label?: string;
}>();
</script>
<template>
<!-- todo make fancy -->
<label>
<template v-if="label">{{ label }}</template>
<input v-model="value" type="text" class="ml-2" v-bind="$attrs" />
</label>
</template>

View File

@ -1,5 +1,137 @@
<script lang="ts" setup>
import PageTitle from "~/components/design/PageTitle.vue";
import { useI18n } from "vue-i18n";
import InputText from "~/components/ui/InputText.vue";
import Button from "~/components/design/Button.vue";
import { ref, watch } from "vue";
import { useContext } from "vite-ssr/vue";
import { useInternalApi } from "~/composables/useApi";
import { ApiKey, HangarApiException, IPermission } from "hangar-api";
import { useRoute } from "vue-router";
import { AxiosError } from "axios";
import { handleRequestError } from "~/composables/useErrorHandling";
import InputCheckbox from "~/components/ui/InputCheckbox.vue";
import Table from "~/components/design/Table.vue";
import Alert from "~/components/design/Alert.vue";
import Card from "~/components/design/Card.vue";
const ctx = useContext();
const i18n = useI18n();
const route = useRoute();
const apiKeys = ref(await useInternalApi<ApiKey[]>("api-keys/existing-keys/" + route.params.user));
const possiblePerms = await useInternalApi<IPermission[]>("api-keys/possible-perms/" + route.params.user);
// TODO validation, loading animations, validation loading
const name = ref("");
const loading = ref(false);
const selectedPerms = ref([]);
const validateLoading = ref(false);
const nameErrorMessages = ref([]);
watch(name, async (val: string) => {
if (!val) return;
validateLoading.value = true;
nameErrorMessages.value = [];
await useInternalApi(`api-keys/check-key/${route.params.user}`, true, "get", {
name: name.value,
}).catch((err: AxiosError<HangarApiException>) => {
if (!err.response?.data.isHangarApiException) {
return handleRequestError(err, ctx, i18n);
}
nameErrorMessages.value.push(i18n.t(err.response.data.message || ""));
});
validateLoading.value = false;
});
async function create() {
loading.value = true;
const key = await useInternalApi<string>(`api-keys/create-key/${route.params.user}`, true, "post", {
name: name.value,
permissions: selectedPerms.value,
}).catch((err) => handleRequestError(err, ctx, i18n));
if (key) {
apiKeys.value.unshift({
token: key,
name: name.value,
permissions: selectedPerms.value,
createdAt: new Date().toISOString(),
});
// TODO success notification
// this.$util.success(i18n.$t('apiKeys.success.create', [name.value]));
}
loading.value = true;
}
async function deleteKey(key: ApiKey) {
loading.value = false;
await useInternalApi(`api-keys/delete-key/${route.params.user}`, true, "post", {
content: key.name,
}).catch((err) => handleRequestError(err, ctx, i18n));
apiKeys.value = apiKeys.value.filter((k) => k.name !== key.name);
// TODO success notification
// this.$util.success(i18n.t('apiKeys.success.delete', [key.name]));
loading.value = true;
}
</script>
<template>
<h1>api keys settings</h1>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card>
<template #header>
<PageTitle>{{ i18n.t("apiKeys.createNew") }}</PageTitle>
</template>
<InputText v-model="name" :label="i18n.t('apiKeys.name')"></InputText>
<Button class="ml-2" @click="create">{{ i18n.t("apiKeys.createKey") }}</Button>
<div class="grid autofix mt-4">
<InputCheckbox v-for="perm in possiblePerms" :key="perm" v-model="selectedPerms" :label="perm" :value="perm" />
</div>
</Card>
<Card>
<template #header>
<PageTitle>{{ i18n.t("apiKeys.existing") }}</PageTitle>
</template>
<Table>
<thead>
<tr>
<th>
{{ i18n.t("apiKeys.name") }}
</th>
<th>
{{ i18n.t("apiKeys.key") }}
</th>
<th>
{{ i18n.t("apiKeys.keyIdentifier") }}
</th>
<th>
{{ i18n.t("apiKeys.permissions") }}
</th>
<th>
{{ i18n.t("apiKeys.delete") }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="key in apiKeys" :key="key.name">
<td>{{ key.name }}</td>
<td>{{ key.token }}</td>
<td>{{ key.tokenIdentifier }}</td>
<td>{{ key.permissions.join(", ") }}</td>
<td>
<Button @click="deleteKey(key)">{{ i18n.t("apiKeys.deleteKey") }}</Button>
</td>
</tr>
<tr v-if="apiKeys.length === 0">
<td colspan="5">
<Alert class="mt-4">
{{ i18n.t("apiKeys.noKeys") }}
</Alert>
</td>
</tr>
</tbody>
</Table>
</Card>
</div>
</template>
<route lang="yaml">
@ -7,3 +139,9 @@ meta:
requireCurrentUser: true
requireGlobalPerm: ["EDIT_API_KEYS"]
</route>
<style lang="scss" scoped>
.autofix {
grid-template-columns: repeat(auto-fit, 250px);
}
</style>