WIP: Enable OpenAPI spec generation and integrate with frontend #21

Draft
Mark.TwoFive wants to merge 19 commits from feat/openApiIntegration into main
7 changed files with 261 additions and 53 deletions
Showing only changes of commit b997a5c273 - Show all commits

View file

@ -1,7 +1,10 @@
<template> <template>
<v-app> <v-app>
<v-app-bar> <v-app-bar>
<v-app-bar-nav-icon icon="mdi-home" to="/"/> <v-app-bar-nav-icon
icon="mdi-home"
to="/"
/>
</v-app-bar> </v-app-bar>
<v-main> <v-main>
<router-view /> <router-view />

View file

@ -0,0 +1,84 @@
<script setup lang="ts">
import { ref, useId, useTemplateRef } from "vue";
const formModel = ref(false);
const formRef = useTemplateRef("form");
const domainInputId = useId();
const domainValue = ref("");
const primaryDomain = ref("");
const domains = ref<string[]>([]);
const domainRules = [
(value: string) => !!value || "A value must be provided to add the domain.",
(value: string) => new RegExp("^[a-z0-9][a-z0-9.]+$").test(value) || "Invalid domain characters provided. (To use some characters, convert the domain to xn-domain format.)",
]
function addDomain() {
if (formRef.value?.errors.some(({id}) => domainInputId === id)) {
return;
}
if (!primaryDomain.value) {
primaryDomain.value = domainValue.value;
}
domains.value.push(domainValue.value);
domainValue.value = "";
}
const isSubmitting = ref(false);
async function submitNewCertificate() {
}
</script>
<template>
<v-form
ref="form"
v-model="formModel"
:disabled="isSubmitting"
@submit="submitNewCertificate"
>
<h2>New certificate</h2>
<fieldset>
<legend>Subject details</legend>
<v-text-field
:id="domainInputId"
v-model="domainValue"
label="Domain(s)"
:rules="domainRules"
@keydown.enter="addDomain"
/>
<ul v-if="domains.length">
<li
v-for="domain in domains"
:key="domain"
>
<span>{{ domain }}</span>
<v-tooltip
v-if="primaryDomain === domain"
text="This is the primary domain for this certificate. It will be used for the certificate common name."
>
<template #activator="{ props }">
<v-icon
v-bind="props"
icon="mdi-start-circle-outline"
/>
</template>
</v-tooltip>
</li>
</ul>
</fieldset>
<v-btn
class="w-100 w-md-auto ml-md-auto"
type="submit"
>
Send certificate request
</v-btn>
</v-form>
</template>
<style scoped>
</style>

View file

@ -5,8 +5,12 @@
max-width="900" max-width="900"
> >
<div class="text-center"> <div class="text-center">
<div class="text-body-2 font-weight-light mb-n1">Welcome to</div> <div class="text-body-2 font-weight-light mb-n1">
<h1 class="text-h2 font-weight-bold">home-cert-manager</h1> Welcome to
</div>
<h1 class="text-h2 font-weight-bold">
home-cert-manager
</h1>
</div> </div>
<div class="py-4" /> <div class="py-4" />
@ -26,7 +30,9 @@
</template> </template>
<template #title> <template #title>
<h2 class="text-h5 font-weight-bold">Get started</h2> <h2 class="text-h5 font-weight-bold">
Get started
</h2>
</template> </template>
</v-card> </v-card>
</v-col> </v-col>

View file

@ -5,7 +5,10 @@
<v-form v-model="valid"> <v-form v-model="valid">
<v-container> <v-container>
<v-row> <v-row>
<v-col cols="12" md="4"> <v-col
cols="12"
md="4"
>
<v-text-field <v-text-field
v-model="subject.country" v-model="subject.country"
:rules="subject.countryRules" :rules="subject.countryRules"
@ -13,7 +16,10 @@
required required
/> />
</v-col> </v-col>
<v-col cols="12" md="4"> <v-col
cols="12"
md="4"
>
<v-text-field <v-text-field
v-model="subject.state" v-model="subject.state"
:counter="10" :counter="10"
@ -23,7 +29,10 @@
/> />
</v-col> </v-col>
<v-col cols="12" md="4"> <v-col
cols="12"
md="4"
>
<v-text-field <v-text-field
v-model="subject.city" v-model="subject.city"
:rules="subject.cityRules" :rules="subject.cityRules"
@ -33,7 +42,10 @@
</v-col> </v-col>
</v-row> </v-row>
<v-row> <v-row>
<v-col cols="12" md="4"> <v-col
cols="12"
md="4"
>
<v-text-field <v-text-field
v-model="subject.organization" v-model="subject.organization"
:rules="subject.organizationRules" :rules="subject.organizationRules"
@ -41,7 +53,10 @@
required required
/> />
</v-col> </v-col>
<v-col cols="12" md="4"> <v-col
cols="12"
md="4"
>
<v-text-field <v-text-field
v-model="subject.orgUnit" v-model="subject.orgUnit"
:rules="subject.orgUnitRules" :rules="subject.orgUnitRules"
@ -49,7 +64,10 @@
required required
/> />
</v-col> </v-col>
<v-col cols="12" md="4"> <v-col
cols="12"
md="4"
>
<v-text-field <v-text-field
v-model="subject.commonName" v-model="subject.commonName"
:counter="10" :counter="10"
@ -60,26 +78,48 @@
</v-col> </v-col>
</v-row> </v-row>
<v-row> <v-row>
<v-col cols="12" md="4"> <v-col
<v-text-field type="number" v-model="validity.duration" :rules="validity.durationRules" label="Duration for validity in Days"/> cols="12"
md="4"
>
<v-text-field
v-model="validity.duration"
type="number"
:rules="validity.durationRules"
label="Duration for validity in Days"
/>
</v-col> </v-col>
</v-row> </v-row>
<v-row> <v-row>
<v-col cols="12" md="4"> <v-col
<v-text-field label="Alternative Names" v-model="ui.altName"> cols="12"
<template v-slot:append> md="4"
<v-btn icon="mdi-plus" color="green" :disabled="!ui.altName" @click="addAltName"/> >
<v-text-field
v-model="ui.altName"
label="Alternative Names"
>
<template #append>
<v-btn
icon="mdi-plus"
color="green"
:disabled="!ui.altName"
@click="addAltName"
/>
</template> </template>
</v-text-field> </v-text-field>
</v-col> </v-col>
<v-col cols="12" md="8"> <v-col
cols="12"
md="8"
>
<v-combobox <v-combobox
v-model="subject.altNames" v-model="subject.altNames"
:items="subject.altNames" :items="subject.altNames"
label="Alternative Names" label="Alternative Names"
multiple multiple
> >
<template v-slot:selection="data"> <template #selection="data">
<v-chip <v-chip
:key="JSON.stringify(data.item)" :key="JSON.stringify(data.item)"
v-bind="data" v-bind="data"
@ -92,8 +132,14 @@
</v-col> </v-col>
</v-row> </v-row>
<v-row> <v-row>
<v-col cols="12" md="12"> <v-col
<v-textarea v-model="trustingAuthority" label="Trusting Authority"/> cols="12"
md="12"
>
<v-textarea
v-model="trustingAuthority"
label="Trusting Authority"
/>
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>
@ -101,7 +147,12 @@
</v-card-item> </v-card-item>
<v-card-actions> <v-card-actions>
<v-col class="text-right"> <v-col class="text-right">
<v-btn :disabled="!valid" @click="requestCertificate" text="Request certificate" prepend-icon="mdi-certificate"/> <v-btn
:disabled="!valid"
text="Request certificate"
prepend-icon="mdi-certificate"
@click="requestCertificate"
/>
</v-col> </v-col>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
@ -109,7 +160,7 @@
<script lang="ts"> <script lang="ts">
import type { Validated } from "@/types/util"; import type { Validated } from "@/types/util";
import type { Certificate, Subject, Validity } from "@/types/certificate"; import type { Certificate, Subject } from "@/types/certificate";
const requiredValidation = (fieldName: string) => { const requiredValidation = (fieldName: string) => {
return (val: string) => { return (val: string) => {

View file

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { fetchClient, type Schemas } from "@/plugins/client.ts"; import { fetchClient, type Schemas } from "@/plugins/client.ts";
import { useRouter } from "vue-router";
const certificates = ref<Schemas["Certificate"][]>([]); const certificates = ref<Schemas["Certificate"][]>([]);
@ -13,35 +14,98 @@ onMounted(() => {
getCertificates(); getCertificates();
}); });
const router = useRouter();
function navigateToNew() {
router.push("/certificates/new");
}
function navigateToImport() {
router.push("/certificates/import");
}
</script> </script>
<template> <template>
<main> <v-container>
<article>
<h2>Currently known certificates</h2> <h2>Currently known certificates</h2>
<section id="no-certs-found" v-if="certificates.length === 0"> <section
v-if="certificates.length === 0"
id="no-certs-found"
>
<v-row> <v-row>
<v-col cols="12" class="text-center"> <v-col
<h3>There seems to be nothing in here.</h3> cols="12"
class="text-center pt-4"
>
<p class="font-weight-bold">
There seems to be nothing in here.
</p>
<p>Why don't you start with one of the following options.</p> <p>Why don't you start with one of the following options.</p>
<v-row> <v-row class="mt-4">
<v-col cols="12" md="6"> <v-col
<v-btn primary>Request new certificate</v-btn> cols="12"
md="6"
>
<v-btn
color="primary"
@click="navigateToNew"
>
Request new certificate
</v-btn>
</v-col> </v-col>
<v-col cols="12" md="6"> <v-col
<v-btn>Import an existing certificate</v-btn> cols="12"
md="6"
>
<v-btn
color="primary"
@click="navigateToImport"
>
Import an existing certificate
</v-btn>
</v-col> </v-col>
</v-row> </v-row>
</v-col> </v-col>
</v-row> </v-row>
</section> </section>
<section aria-label="Bekannte Zertifikate"> <section
<ul v-if="certificates.length > 0"> v-if="certificates.length > 0"
<li v-for="cert in certificates" :key="cert.fingerprint"> id="known-certificates"
>
<ul>
<li
v-for="cert in certificates"
:key="cert.fingerprint"
>
<p>{{ cert.fingerprint }}</p> <p>{{ cert.fingerprint }}</p>
</li> </li>
</ul> </ul>
<v-row>
<v-col
cols="12"
md="6"
>
<v-btn @click="navigateToNew">
Request a new certificate
</v-btn>
</v-col>
<v-col
cols="12"
md="6"
>
<v-btn
color="primary"
@click="navigateToImport"
>
Import an existing certificate
</v-btn>
</v-col>
</v-row>
</section> </section>
</main> </article>
</v-container>
</template> </template>
<style scoped> <style scoped>

View file

@ -1,9 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import CertificateEditor from "@/components/CertificateEditor.vue";
</script> </script>
<template> <template>
<h2>New certificate</h2> <v-container>
<CertificateEditor />
</v-container>
</template> </template>
<style scoped> <style scoped>

View file

@ -13,9 +13,7 @@ export interface Validity {
to: Date; to: Date;
} }
export interface Extensions { export type Extensions = object;
}
export interface Certificate { export interface Certificate {
issuer: Subject; issuer: Subject;