💄 Improved UI regarding Login
This commit is contained in:
parent
40e2721b82
commit
2930460102
@ -1,3 +1,5 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
export class User
|
export class User
|
||||||
{
|
{
|
||||||
username?: string;
|
username?: string;
|
||||||
@ -5,6 +7,40 @@ export class User
|
|||||||
password?: string;
|
password?: string;
|
||||||
roles?: string[];
|
roles?: string[];
|
||||||
initial?: boolean;
|
initial?: boolean;
|
||||||
|
|
||||||
|
static get(onSuccess: (users: User[]) => void)
|
||||||
|
{
|
||||||
|
axios.get(ApiConfig.getHttpBase() + '/users', {
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer " + requireToken()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
onSuccess(response.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static create(user: UserCreation, onSuccess: () => void)
|
||||||
|
{
|
||||||
|
axios.post(ApiConfig.getHttpBase() + '/users', user, {
|
||||||
|
headers: {
|
||||||
|
Authorization: "Bearer " + requireToken()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
onSuccess();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserCreation
|
||||||
|
{
|
||||||
|
firstname?: string;
|
||||||
|
lastname?: string;
|
||||||
|
email?: string;
|
||||||
|
password?: string;
|
||||||
|
username?: string;
|
||||||
|
role: string = "USER";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasAnyRole(user: User | undefined, requiredRoles: string[])
|
export function hasAnyRole(user: User | undefined, requiredRoles: string[])
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<p class="grid-element">{{ configMap.metadata.name }}</p>
|
<p class="grid-element">{{ configMap.metadata.name }}</p>
|
||||||
<p class="grid-element">{{ configMap.metadata.namespace }}</p>
|
<p class="grid-element">{{ configMap.metadata.namespace }}</p>
|
||||||
|
<p class="grid-element">{{ calcAge(configMap.metadata.creationTimestamp) }}</p>
|
||||||
<p class="grid-element"><span v-if="configMap.data">{{ Object.keys(configMap.data).length }}</span><span v-else>-</span></p>
|
<p class="grid-element"><span v-if="configMap.data">{{ Object.keys(configMap.data).length }}</span><span v-else>-</span></p>
|
||||||
<div class="grid-element">
|
<div class="grid-element">
|
||||||
<ActionButton>delete</ActionButton>
|
<ActionButton>delete</ActionButton>
|
||||||
@ -11,6 +12,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ConfigMap } from '~/classes/ConfigMap';
|
import type { ConfigMap } from '~/classes/ConfigMap';
|
||||||
|
import { calcAge } from '~/classes/Pod';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
configMap: ConfigMap
|
configMap: ConfigMap
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<p class="grid-element">{{ namespace.metadata.name }}</p>
|
<p class="grid-element">{{ namespace.metadata.name }}</p>
|
||||||
|
<p class="grid-element">{{ calcAge(namespace.metadata.creationTimestamp) }}</p>
|
||||||
<div class="grid-element">
|
<div class="grid-element">
|
||||||
<ActionButton>delete</ActionButton>
|
<ActionButton>delete</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
@ -9,6 +10,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Namespace } from '~/classes/Namespace';
|
import type { Namespace } from '~/classes/Namespace';
|
||||||
|
import { calcAge } from '~/classes/Pod';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
namespace: Namespace
|
namespace: Namespace
|
||||||
|
|||||||
@ -1,15 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<SidebarTemplate>
|
<SidebarTemplate>
|
||||||
<div class="nav">
|
<div class="nav">
|
||||||
<NuxtLink class="namespace" to="/account/settings">Account</NuxtLink>
|
<NuxtLink class="namespace" to="/account/settings/password">Password</NuxtLink>
|
||||||
<NuxtLink class="namespace" to="/account/users">Users</NuxtLink>
|
<NuxtLink class="namespace" to="/account/settings/users">Users</NuxtLink>
|
||||||
<NuxtLink class="namespace" to="/account/password">Password</NuxtLink>
|
</div>
|
||||||
|
<div class="left-center">
|
||||||
|
<UiIcon>account_circle</UiIcon>
|
||||||
|
<p>{{ user.username }}</p>
|
||||||
</div>
|
</div>
|
||||||
</SidebarTemplate>
|
</SidebarTemplate>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
const user = requireUser();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-width: 10rem;
|
||||||
}
|
}
|
||||||
.namespace, .resources {
|
.namespace, .resources {
|
||||||
padding: 0.35rem 0.5rem;
|
padding: 0.35rem 0.5rem;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="content-l">
|
<div class="content-l">
|
||||||
<h2>Account</h2>
|
<h2>Account</h2>
|
||||||
<div class="content-m">
|
<div class="content-m tile-l">
|
||||||
<h3>Passwort ändern</h3>
|
<h3>Passwort ändern</h3>
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<UiInput label="Passwort">
|
<UiInput label="Passwort">
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Dataset } from './Dataset';
|
import type { Dataset } from './Dataset';
|
||||||
import { TimeScale, LinearScale, LineController, PointElement, LineElement, Chart, type ChartConfiguration } from 'chart.js';
|
import { TimeScale, LinearScale, LineController, PointElement, LineElement, Chart, Filler, type ChartConfiguration } from 'chart.js';
|
||||||
import 'chartjs-adapter-moment';
|
import 'chartjs-adapter-moment';
|
||||||
|
|
||||||
Chart.register(TimeScale)
|
Chart.register(TimeScale)
|
||||||
@ -14,6 +14,7 @@ Chart.register(LinearScale)
|
|||||||
Chart.register(PointElement)
|
Chart.register(PointElement)
|
||||||
Chart.register(LineController)
|
Chart.register(LineController)
|
||||||
Chart.register(LineElement)
|
Chart.register(LineElement)
|
||||||
|
Chart.register(Filler)
|
||||||
|
|
||||||
|
|
||||||
const id = Math.random().toString().replaceAll(".", "");
|
const id = Math.random().toString().replaceAll(".", "");
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<p class="grid-element" v-if="deployment.metadata">{{ deployment.metadata.name }}</p>
|
<p class="grid-element">{{ deployment.metadata.name }}</p>
|
||||||
<p class="grid-element" v-if="deployment.metadata">{{ deployment.metadata.namespace }}</p>
|
<p class="grid-element">{{ deployment.metadata.namespace }}</p>
|
||||||
<p class="grid-element" v-if="deployment.spec">{{ deployment.spec.replicas }}</p>
|
<p class="grid-element">{{ calcAge(deployment.metadata.creationTimestamp) }}</p>
|
||||||
|
<p class="grid-element">{{ deployment.spec.replicas }}</p>
|
||||||
<div class="grid-element action-buttons">
|
<div class="grid-element action-buttons">
|
||||||
<ActionButton>delete</ActionButton>
|
<ActionButton>delete</ActionButton>
|
||||||
<ActionButton>autorenew</ActionButton>
|
<ActionButton>autorenew</ActionButton>
|
||||||
@ -15,6 +16,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Deployment } from '~/classes/Deployment';
|
import { Deployment } from '~/classes/Deployment';
|
||||||
import RescaleDeploymentPopup from '../RescaleDeploymentPopup.vue';
|
import RescaleDeploymentPopup from '../RescaleDeploymentPopup.vue';
|
||||||
|
import { calcAge } from '~/classes/Pod';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
deployment: Deployment
|
deployment: Deployment
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div @click="() => showViewPopup = true">
|
<div @click="() => showViewPopup = true">
|
||||||
<p class="grid-element">{{ ingress.metadata.name }}</p>
|
<p class="grid-element cursor">{{ ingress.metadata.name }}</p>
|
||||||
<p class="grid-element">{{ ingress.metadata.namespace }}</p>
|
<p class="grid-element">{{ ingress.metadata.namespace }}</p>
|
||||||
|
<p class="grid-element">{{ calcAge(ingress.metadata.creationTimestamp) }}</p>
|
||||||
<p class="grid-element">{{ ingress.spec.ingressClassName }}</p>
|
<p class="grid-element">{{ ingress.spec.ingressClassName }}</p>
|
||||||
<p class="grid-element">{{ ingress.spec.rules.length }}</p>
|
<p class="grid-element">{{ ingress.spec.rules.length }}</p>
|
||||||
<div class="grid-element action-buttons">
|
<div class="grid-element action-buttons">
|
||||||
@ -14,6 +15,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Ingress } from '~/classes/Ingress';
|
import type { Ingress } from '~/classes/Ingress';
|
||||||
import IngressViewPopup from './view/IngressViewPopup.vue';
|
import IngressViewPopup from './view/IngressViewPopup.vue';
|
||||||
|
import { calcAge } from '~/classes/Pod';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
ingress: Ingress
|
ingress: Ingress
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<p>Name</p>
|
<p>Name</p>
|
||||||
<p>Namespace</p>
|
<p>Namespace</p>
|
||||||
|
<p>Age</p>
|
||||||
<p>Entries</p>
|
<p>Entries</p>
|
||||||
<p>Aktionen</p>
|
<p>Aktionen</p>
|
||||||
</div>
|
</div>
|
||||||
@ -28,6 +29,6 @@ onUnmounted(() => {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.config-map-container {
|
.config-map-container {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr 1fr auto;
|
grid-template-columns: auto auto auto 1fr auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -4,6 +4,7 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<p>Name</p>
|
<p>Name</p>
|
||||||
<p>Namespace</p>
|
<p>Namespace</p>
|
||||||
|
<p>Age</p>
|
||||||
<p>Replicas</p>
|
<p>Replicas</p>
|
||||||
<p>Aktionen</p>
|
<p>Aktionen</p>
|
||||||
</div>
|
</div>
|
||||||
@ -28,6 +29,6 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.deployment-container {
|
.deployment-container {
|
||||||
grid-template-columns: auto 1fr 1fr auto;
|
grid-template-columns: auto auto auto 1fr auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -4,6 +4,7 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<p>Name</p>
|
<p>Name</p>
|
||||||
<p>Namespace</p>
|
<p>Namespace</p>
|
||||||
|
<p>Age</p>
|
||||||
<p>Ingress Class Name</p>
|
<p>Ingress Class Name</p>
|
||||||
<p>Rules</p>
|
<p>Rules</p>
|
||||||
<p>Actions</p>
|
<p>Actions</p>
|
||||||
@ -28,6 +29,6 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.ingress-container {
|
.ingress-container {
|
||||||
grid-template-columns: 1fr 1fr 1fr 1fr auto;
|
grid-template-columns: auto auto auto auto 1fr auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -3,6 +3,7 @@
|
|||||||
<div class="resource-container namespace-container">
|
<div class="resource-container namespace-container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<p>Name</p>
|
<p>Name</p>
|
||||||
|
<p>Age</p>
|
||||||
<p>Actions</p>
|
<p>Actions</p>
|
||||||
</div>
|
</div>
|
||||||
<NamespaceComponent :namespace="namespace" v-for="namespace, index in namespaces" class="resource" :class="{ even: index % 2 }"></NamespaceComponent>
|
<NamespaceComponent :namespace="namespace" v-for="namespace, index in namespaces" class="resource" :class="{ even: index % 2 }"></NamespaceComponent>
|
||||||
@ -26,6 +27,6 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.namespace-container {
|
.namespace-container {
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: auto 1fr auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -4,7 +4,7 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<p>Name</p>
|
<p>Name</p>
|
||||||
<p>Namespace</p>
|
<p>Namespace</p>
|
||||||
<p>Alter</p>
|
<p>Age</p>
|
||||||
<p>Storage Class</p>
|
<p>Storage Class</p>
|
||||||
<p>Access Modes</p>
|
<p>Access Modes</p>
|
||||||
<p>Aktionen</p>
|
<p>Aktionen</p>
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
<div class="resource-container persistent-volume-container">
|
<div class="resource-container persistent-volume-container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<p>Name</p>
|
<p>Name</p>
|
||||||
<p>Alter</p>
|
<p>Age</p>
|
||||||
<p>Aktionen</p>
|
<p>Aktionen</p>
|
||||||
</div>
|
</div>
|
||||||
<PersistentVolumeComponent v-for="persistentVolume, index in persistentVolumes" :persistent-volume="persistentVolume" class="resource" :class="{ even: index % 2 }"></PersistentVolumeComponent>
|
<PersistentVolumeComponent v-for="persistentVolume, index in persistentVolumes" :persistent-volume="persistentVolume" class="resource" :class="{ even: index % 2 }"></PersistentVolumeComponent>
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<p>Pod</p>
|
<p>Pod</p>
|
||||||
<p>Namespace</p>
|
<p>Namespace</p>
|
||||||
<p>Alter</p>
|
<p>Age</p>
|
||||||
<p>Node</p>
|
<p>Node</p>
|
||||||
<p>Containers</p>
|
<p>Containers</p>
|
||||||
<p>Status</p>
|
<p>Status</p>
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
<UiButton v-if="false" icon="add" class="extra-small" reverse @click="() => secretAddComponent.open()">Add</UiButton>
|
<UiButton v-if="false" icon="add" class="extra-small" reverse @click="() => secretAddComponent.open()">Add</UiButton>
|
||||||
</div>
|
</div>
|
||||||
<p>Namespace</p>
|
<p>Namespace</p>
|
||||||
<p>Alter</p>
|
<p>Age</p>
|
||||||
<p>Aktionen</p>
|
<p>Aktionen</p>
|
||||||
</div>
|
</div>
|
||||||
<SecretComponent v-for="secret, index in secrets" :secret="secret" class="resource" :class="{ even: index % 2 }"></SecretComponent>
|
<SecretComponent v-for="secret, index in secrets" :secret="secret" class="resource" :class="{ even: index % 2 }"></SecretComponent>
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
<p>Service</p>
|
<p>Service</p>
|
||||||
<p>Namespace</p>
|
<p>Namespace</p>
|
||||||
|
<p>Age</p>
|
||||||
<p>Type</p>
|
<p>Type</p>
|
||||||
<p>Aktionen</p>
|
<p>Aktionen</p>
|
||||||
</div>
|
</div>
|
||||||
@ -27,6 +28,6 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.service-container {
|
.service-container {
|
||||||
grid-template-columns: auto auto 1fr auto;
|
grid-template-columns: auto auto auto 1fr auto;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -2,7 +2,7 @@
|
|||||||
<div class="resource">
|
<div class="resource">
|
||||||
<p class="grid-element">{{ secret.metadata.name }}</p>
|
<p class="grid-element">{{ secret.metadata.name }}</p>
|
||||||
<p class="grid-element">{{ secret.metadata.namespace }}</p>
|
<p class="grid-element">{{ secret.metadata.namespace }}</p>
|
||||||
<p class="grid-element">-</p>
|
<p class="grid-element">{{ calcAge(secret.metadata.creationTimestamp) }}</p>
|
||||||
<div class="grid-element action-buttons">
|
<div class="grid-element action-buttons">
|
||||||
<ActionButton>edit</ActionButton>
|
<ActionButton>edit</ActionButton>
|
||||||
<ActionButton>delete</ActionButton>
|
<ActionButton>delete</ActionButton>
|
||||||
@ -11,6 +11,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { calcAge } from '~/classes/Pod';
|
||||||
import type { Secret } from '~/classes/Secret';
|
import type { Secret } from '~/classes/Secret';
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|||||||
@ -1,17 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<p class="grid-element" v-if="service.metadata">{{ service.metadata.name }}</p>
|
<p class="grid-element">{{ service.metadata.name }}</p>
|
||||||
<p class="grid-element" v-if="service.metadata">{{ service.metadata.namespace }}</p>
|
<p class="grid-element">{{ service.metadata.namespace }}</p>
|
||||||
|
<p class="grid-element">{{ calcAge(service.metadata.creationTimestamp) }}</p>
|
||||||
<p class="grid-element" v-if="service.spec">{{ service.spec.type }}</p>
|
<p class="grid-element" v-if="service.spec">{{ service.spec.type }}</p>
|
||||||
<div class="grid-element">
|
<div class="grid-element">
|
||||||
<ActionButton v-if="hasAnyRole(getUser(), ['admin', 'maintainer'])">delete</ActionButton>
|
<ActionButton>delete</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { calcAge } from '~/classes/Pod';
|
||||||
import type { Service } from '~/classes/Service';
|
import type { Service } from '~/classes/Service';
|
||||||
import { hasAnyRole } from '~/classes/User';
|
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
service: Service
|
service: Service
|
||||||
|
|||||||
13
components/ui/Prompt.ts
Normal file
13
components/ui/Prompt.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export class Prompt
|
||||||
|
{
|
||||||
|
constructor (
|
||||||
|
public text: string,
|
||||||
|
public type: PromptType
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PromptType
|
||||||
|
{
|
||||||
|
ERROR = "error",
|
||||||
|
SUCCESS = "success"
|
||||||
|
}
|
||||||
@ -37,6 +37,7 @@ const props = defineProps<{
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
.button.square {
|
.button.square {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|||||||
27
components/ui/UiError.vue
Normal file
27
components/ui/UiError.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
<div class="spaced-center padding-m error" v-if="error">
|
||||||
|
<p>{{ error }}</p>
|
||||||
|
<UiIcon class="pointer" @click="() => emit('close')">close</UiIcon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
error?: string
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'close'): void
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.error {
|
||||||
|
background-color: rgb(218, 57, 57);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
.error * {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
34
components/ui/UiPompt.vue
Normal file
34
components/ui/UiPompt.vue
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<template>
|
||||||
|
<div class="spaced-center padding-m prompt" v-if="prompt" :class="prompt.type">
|
||||||
|
<p>{{ prompt.text }}</p>
|
||||||
|
<UiIcon class="pointer" @click="() => emit('close')">close</UiIcon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Prompt } from './Prompt';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
prompt?: Prompt
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'close'): void
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.prompt {
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
background-color: rgb(218, 57, 57);
|
||||||
|
}
|
||||||
|
.success {
|
||||||
|
background-color: rgb(43, 161, 49);
|
||||||
|
}
|
||||||
|
.prompt * {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -7,7 +7,7 @@
|
|||||||
<div class="left-center">
|
<div class="left-center">
|
||||||
<NuxtLink to="/account/inspect/nodes/_all">Inspect</NuxtLink>
|
<NuxtLink to="/account/inspect/nodes/_all">Inspect</NuxtLink>
|
||||||
<NuxtLink to="/account/monitorings/nodes">Monitorings</NuxtLink>
|
<NuxtLink to="/account/monitorings/nodes">Monitorings</NuxtLink>
|
||||||
<NuxtLink to="/account/settings">Settings</NuxtLink>
|
<NuxtLink to="/account/settings/password">Settings</NuxtLink>
|
||||||
<p class="pointer" @click="logout()">Logout</p>
|
<p class="pointer" @click="logout()">Logout</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="monitorings-page">
|
<div class="monitorings-page">
|
||||||
<SidebarTemplate>
|
<SidebarTemplate>
|
||||||
<div class="content-l">
|
<NuxtLink class="resources" to="/account/monitorings/nodes">Nodes</NuxtLink>
|
||||||
<h2>Kubooboo</h2>
|
|
||||||
<NuxtLink class="resources" to="/account/monitorings/nodes">Nodes</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</SidebarTemplate>
|
</SidebarTemplate>
|
||||||
<NuxtPage></NuxtPage>
|
<NuxtPage></NuxtPage>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="settings-page">
|
<div class="settings-page">
|
||||||
<SettingsSidebar></SettingsSidebar>
|
<SettingsSidebar></SettingsSidebar>
|
||||||
<div>
|
<div class="padding-l">
|
||||||
<NuxtPage></NuxtPage>
|
<NuxtPage></NuxtPage>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,11 +1,46 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="content-l">
|
||||||
|
<h2>Change Password</h2>
|
||||||
|
<UiPompt :prompt="prompt" @close="prompt = undefined"></UiPompt>
|
||||||
|
<div class="col-2 tile-l">
|
||||||
|
<UiInput label="New Password" required>
|
||||||
|
<input type="password" v-model="password">
|
||||||
|
</UiInput>
|
||||||
|
<UiInput label="Repeat Password" required>
|
||||||
|
<input type="password" v-model="repeatPassword">
|
||||||
|
</UiInput>
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
<UiButton class="width-6rem" icon="change_circle" reverse @click="() => change()">Change</UiButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { Prompt, PromptType } from '~/components/ui/Prompt';
|
||||||
|
import { changePassword } from '~/requests/user';
|
||||||
|
import UiPompt from '~/components/ui/UiPompt.vue';
|
||||||
|
|
||||||
|
const password = ref('');
|
||||||
|
const repeatPassword = ref('');
|
||||||
|
|
||||||
|
const prompt: Ref<Prompt | undefined> = ref(undefined);
|
||||||
|
function change()
|
||||||
|
{
|
||||||
|
if (password.value && password.value === repeatPassword.value)
|
||||||
|
{
|
||||||
|
changePassword(requireUser().username, password.value, () => {
|
||||||
|
password.value = '';
|
||||||
|
repeatPassword.value = '';
|
||||||
|
prompt.value = new Prompt("Password changed successfully.", PromptType.SUCCESS);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prompt.value = new Prompt("Passwords don't match.", PromptType.ERROR);
|
||||||
|
throw new Error("Passwords don't match.");
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
65
pages/account/settings/users/add.vue
Normal file
65
pages/account/settings/users/add.vue
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<div class="content-l">
|
||||||
|
<h2>Add User</h2>
|
||||||
|
<UiError :error="error" @close="error = undefined"></UiError>
|
||||||
|
<div class="col-2 tile-l">
|
||||||
|
<UiInput label="First Name">
|
||||||
|
<input type="text" v-model="user.firstname">
|
||||||
|
</UiInput>
|
||||||
|
<UiInput label="Last Name">
|
||||||
|
<input type="text" v-model="user.lastname">
|
||||||
|
</UiInput>
|
||||||
|
</div>
|
||||||
|
<div class="col-2 tile-l">
|
||||||
|
<UiInput label="Username" required>
|
||||||
|
<input type="text" v-model="user.username">
|
||||||
|
</UiInput>
|
||||||
|
<UiInput label="E-Mail" required>
|
||||||
|
<input type="text" v-model="user.email">
|
||||||
|
</UiInput>
|
||||||
|
</div>
|
||||||
|
<div class="col-2 tile-l">
|
||||||
|
<UiInput label="Role" required>
|
||||||
|
<select v-model="user.role">
|
||||||
|
<option value="USER">User</option>
|
||||||
|
<option value="ADMIN">Admin</option>
|
||||||
|
</select>
|
||||||
|
</UiInput>
|
||||||
|
<UiInput label="Password" required>
|
||||||
|
<input type="password" v-model="user.password">
|
||||||
|
</UiInput>
|
||||||
|
</div>
|
||||||
|
<div class="center">
|
||||||
|
<UiButton class="width-6rem" @click="() => useRouter().push('/account/settings/users')">Zurück</UiButton>
|
||||||
|
<UiButton class="width-6rem" @click="() => create()">Add</UiButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { User, UserCreation } from '~/classes/User';
|
||||||
|
import UiError from '~/components/ui/UiError.vue';
|
||||||
|
|
||||||
|
const user = ref(new UserCreation());
|
||||||
|
|
||||||
|
const error: Ref<string | undefined> = ref(undefined);
|
||||||
|
function create()
|
||||||
|
{
|
||||||
|
const _user = user.value;
|
||||||
|
if (_user.email && _user.username && _user.role && _user.password)
|
||||||
|
{
|
||||||
|
User.create(user.value, () => {
|
||||||
|
useRouter().push('/account/settings/users');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
error.value = "Please fill out all required fields.";
|
||||||
|
throw new Error("Invalid user object.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
65
pages/account/settings/users/index.vue
Normal file
65
pages/account/settings/users/index.vue
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<div class="content-l">
|
||||||
|
<div class="spaced-center">
|
||||||
|
<h2>Users</h2>
|
||||||
|
<UiButton icon="person_add" reverse @click="() => useRouter().push('/account/settings/users/add')">Add</UiButton>
|
||||||
|
</div>
|
||||||
|
<div class="content-l">
|
||||||
|
<div class="user-container">
|
||||||
|
<div class="contents">
|
||||||
|
<h3 class="gray">Username</h3>
|
||||||
|
<h3 class="gray">E-Mail</h3>
|
||||||
|
<h3 class="gray">Roles</h3>
|
||||||
|
</div>
|
||||||
|
<div v-for="user in users" class="contents user-row">
|
||||||
|
<p>{{ user.username }}</p>
|
||||||
|
<p>{{ Optional.ofNullable(user.email).orElse("-") }}</p>
|
||||||
|
<p v-if="user.roles">{{ user.roles.join(", ") }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { User } from '~/classes/User';
|
||||||
|
|
||||||
|
const users: Ref<User[] | undefined> = ref(undefined);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
User.get((_users) => {
|
||||||
|
users.value = _users;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.user-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
row-gap: 0.5rem;
|
||||||
|
}
|
||||||
|
.user-container > :first-child > * {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.contents {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
.contents > * {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
.gray {
|
||||||
|
background-color: var(--shade-light);
|
||||||
|
}
|
||||||
|
.user-row > * {
|
||||||
|
background-color: var(--tile-color);
|
||||||
|
}
|
||||||
|
.contents > :first-child {
|
||||||
|
border-top-left-radius: 0.25rem;
|
||||||
|
border-bottom-left-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
.contents > :last-child {
|
||||||
|
border-top-right-radius: 0.5rem;
|
||||||
|
border-bottom-right-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,17 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="login-wrapper">
|
<div class="login-wrapper">
|
||||||
<div class="tile-l login-window">
|
<div class="tile-l content-l login-window">
|
||||||
<h2>Kubooboo</h2>
|
<div class="content-l">
|
||||||
<img class="logo" src="@/assets/transparent_logo.png" alt="">
|
<h2 class="center">Kubooboo</h2>
|
||||||
<form class="login content-xl" @submit.prevent="doLogin()">
|
<div class="center">
|
||||||
|
<img class="logo" src="@/assets/transparent_logo.png" alt=""></img>
|
||||||
|
</div>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<h1>Login</h1>
|
<h1>Login</h1>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<form class="login content-l" @submit.prevent="doLogin()">
|
||||||
<div class="content-l">
|
<div class="content-l">
|
||||||
<UiInput label="Username">
|
<UiError :error="error" @close="() => { error = undefined }"></UiError>
|
||||||
|
<UiInput label="Username" required>
|
||||||
<input type="text" v-model="loginCredentials.username">
|
<input type="text" v-model="loginCredentials.username">
|
||||||
</UiInput>
|
</UiInput>
|
||||||
<UiInput label="Passwort">
|
<UiInput label="Passwort" required>
|
||||||
<input type="password" v-model="loginCredentials.password">
|
<input type="password" v-model="loginCredentials.password">
|
||||||
</UiInput>
|
</UiInput>
|
||||||
</div>
|
</div>
|
||||||
@ -30,20 +35,39 @@ import { login } from '~/requests/login';
|
|||||||
import { jwtDecode } from 'jwt-decode';
|
import { jwtDecode } from 'jwt-decode';
|
||||||
import { getUser } from '~/requests/user';
|
import { getUser } from '~/requests/user';
|
||||||
import { Session } from '~/classes/Session';
|
import { Session } from '~/classes/Session';
|
||||||
|
import UiError from '~/components/ui/UiError.vue';
|
||||||
|
|
||||||
const loginCredentials = ref(new User());
|
const loginCredentials = ref(new User());
|
||||||
|
|
||||||
|
const error: Ref<string | undefined> = ref(undefined);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
function doLogin()
|
function doLogin()
|
||||||
{
|
{
|
||||||
loading.value = true;
|
if (loginCredentials.value.username && loginCredentials.value.password)
|
||||||
login(loginCredentials.value, (token: string) => {
|
{
|
||||||
const decode = jwtDecode(token) as any;
|
loading.value = true;
|
||||||
getUser(decode.upn, token, (user: User) => {
|
login(loginCredentials.value, (token: string) => {
|
||||||
setSessionCookie(new Session(user, token), decode.exp as number);
|
const decode = jwtDecode(token) as any;
|
||||||
useRouter().push('/account/inspect/nodes/_all');
|
getUser(decode.upn, token, (user: User) => {
|
||||||
|
setSessionCookie(new Session(user, token), decode.exp as number);
|
||||||
|
useRouter().push('/account/inspect/nodes/_all');
|
||||||
|
});
|
||||||
|
}, (code?: string) => {
|
||||||
|
if (code === 'user_not_found')
|
||||||
|
{
|
||||||
|
error.value = "User does not exist.";
|
||||||
|
}
|
||||||
|
if (code === 'wrong_password')
|
||||||
|
{
|
||||||
|
error.value = "Invalid password.";
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Error("Cannot send invalid form.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSessionCookie(session: Session, exp: number)
|
function setSessionCookie(session: Session, exp: number)
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import axios from "axios";
|
import axios, { AxiosError, type AxiosResponse } from "axios";
|
||||||
import type { User } from "~/classes/User";
|
import type { User } from "~/classes/User";
|
||||||
|
|
||||||
export function login(user: User, onSuccess: (token: string) => void)
|
export function login(user: User, onSuccess: (token: string) => void, onError: (code?: string) => void)
|
||||||
{
|
{
|
||||||
axios.post<string>(ApiConfig.getHttpBase() + '/login', user)
|
axios.post<string>(ApiConfig.getHttpBase() + '/login', user)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
onSuccess(response.data);
|
onSuccess(response.data);
|
||||||
})
|
})
|
||||||
.catch();
|
.catch((error: AxiosError<string, any>) => {
|
||||||
|
onError(error.response?.data);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user