Skip to content

Commit b39929c

Browse files
fix dynamic page param issue
1 parent 10a6bfd commit b39929c

10 files changed

Lines changed: 258 additions & 20 deletions

File tree

frontend/kubecloud-v2/assets/scss/vuetify.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
);
77

88
.text-accent {
9-
@extend .text-grey;
9+
color: rgba(255, 255, 255, 0.7) !important;
1010
}
1111

1212
.rounded-xl {

frontend/kubecloud-v2/components/AppNavbar.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838

3939
<v-spacer />
4040

41-
<template v-if="!authenticated">
41+
<div v-if="!authenticated">
4242
<span v-if="$route.path === ROUTES.SignIn()" class="text-caption opacity-70 mr-2">
4343
Don't have an account?
4444
</span>
@@ -65,7 +65,7 @@
6565
to="/register"
6666
text="Register"
6767
/> -->
68-
</template>
68+
</div>
6969

7070
<v-menu v-else>
7171
<template #activator="{ props }">

frontend/kubecloud-v2/components/DialogCardLayout.vue

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,10 @@
3030
<slot />
3131
</v-card-text>
3232

33-
<template v-if="$slots.actions">
34-
<v-divider />
35-
36-
<v-card-actions class="px-6 py-4 flex-row-reverse justify-start">
37-
<slot name="actions" />
38-
</v-card-actions>
39-
</template>
33+
<v-divider v-if="$slots.actions" />
34+
<v-card-actions v-if="$slots.actions" class="px-6 py-4 flex-row-reverse justify-start">
35+
<slot name="actions" />
36+
</v-card-actions>
4037
</v-card>
4138
</template>
4239

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<template>
2+
<div class="d-flex ga-4">
3+
<VAvatar
4+
size="x-large"
5+
rounded="lg"
6+
variant="tonal"
7+
icon="mdi-key-outline"
8+
color="primary"
9+
/>
10+
11+
<div class="flex-grow-1">
12+
<p class="text-subtitle-1 font-weight-bold" v-text="publicKey.name" />
13+
14+
<div class="d-flex align-center ga-2 mt-2 w-100">
15+
<p
16+
class="text-subtitle-2 border d-inline-block px-3 py-1 rounded-lg w-100"
17+
:style="{
18+
backgroundColor: 'rgb(var(--v-bg-2))',
19+
maxWidth: '600px',
20+
width: '100%',
21+
}"
22+
>
23+
<span class="text-accent" v-text="formattedPublicKey" />
24+
</p>
25+
26+
<VBtn
27+
icon
28+
size="x-small"
29+
variant="plain"
30+
@click="console.log('copied')"
31+
>
32+
<VIcon icon="mdi-content-copy" size="small" color="accent" />
33+
</VBtn>
34+
</div>
35+
36+
<div class="d-flex align-center ga-1 mt-1">
37+
<VIcon icon="mdi-calendar-outline" size="x-small" color="accent" />
38+
<span class="text-subtitle-2 text-accent">Added on {{ addedAt }}</span>
39+
</div>
40+
</div>
41+
42+
<div class="align-self-center">
43+
<VBtn
44+
variant="text"
45+
prepend-icon="mdi-trash-can-outline"
46+
color="error"
47+
size="small"
48+
text="Remove"
49+
:loading="isLoading"
50+
@click="deleteKey()"
51+
/>
52+
</div>
53+
</div>
54+
</template>
55+
56+
<script setup lang="ts">
57+
import type { ModelsSSHKey } from "~/generated/api"
58+
59+
const props = defineProps<{ publicKey: ModelsSSHKey }>()
60+
const emit = defineEmits<{ (e: "delete"): void }>()
61+
62+
const formattedPublicKey = computed(() => {
63+
const key = props.publicKey.public_key
64+
return `${key.slice(0, 25)} ... ${key.slice(-25)}`
65+
})
66+
67+
const addedAt = useDateFormat(() => props.publicKey.created_at, "MMM d, YYYY")
68+
69+
const api = useApi()
70+
const toast = useToast()
71+
const { isLoading, execute: deleteKey } = useAsyncState(async () => {
72+
const { data } = await api.users.deleteSshKey((props.publicKey as any).ID)
73+
toast.success({ message: data.message })
74+
emit("delete")
75+
}, null, { immediate: false })
76+
</script>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<template>
2+
<v-form @submit.prevent="confirm($event.target as HTMLFormElement)">
3+
<v-card :style="{ padding: '0 !important' }">
4+
<v-card-title class="px-6 py-4">
5+
<div class="d-flex align-center justify-space-between">
6+
<h3 class="text-h5 font-weight-bold">
7+
Add Link
8+
</h3>
9+
</div>
10+
</v-card-title>
11+
12+
<v-divider />
13+
14+
<v-card-text>
15+
<v-row>
16+
<v-col cols="12">
17+
<v-text-field
18+
label="SSH Key Name"
19+
name="name"
20+
placeholder="Enter the ssh key name"
21+
variant="outlined"
22+
hide-details
23+
autofocus
24+
/>
25+
<!-- :rules="[
26+
(v) => !!v || 'Key name is required',
27+
(v) => v.length >= 3 || 'Key name must be at least 3 characters',
28+
(v) => v.length <= 255 || 'Key name must be less than 255 characters',
29+
]" -->
30+
</v-col>
31+
32+
<v-col cols="12">
33+
<v-textarea
34+
label="Public SSH Key"
35+
name="public_key"
36+
placeholder="Enter the public ssh key"
37+
variant="outlined"
38+
hide-details
39+
no-resize
40+
rows="3"
41+
/>
42+
</v-col>
43+
44+
<v-col cols="12">
45+
<VBtn
46+
type="button"
47+
tabindex="-1"
48+
prepend-icon="mdi-key-plus"
49+
variant="plain"
50+
border
51+
color="primary"
52+
text="Generate SSH Key"
53+
@click="console.warn('generate ssh key button clicked')"
54+
/>
55+
</v-col>
56+
</v-row>
57+
</v-card-text>
58+
<v-divider />
59+
60+
<v-card-actions class="px-6 py-4 flex-row-reverse justify-start">
61+
<v-btn variant="text" color="primary" type="submit">
62+
Add
63+
</v-btn>
64+
<v-btn variant="plain" type="button" @click="$emit('cancel')">
65+
Cancel
66+
</v-btn>
67+
</v-card-actions>
68+
</v-card>
69+
</v-form>
70+
</template>
71+
72+
<script setup lang="ts">
73+
import type { HandlersSSHKeyInput } from "~/generated/api"
74+
75+
const emit = defineEmits<{
76+
(e: "confirm", value: HandlersSSHKeyInput): void
77+
(e: "cancel"): void
78+
}>()
79+
80+
function confirm(event: HTMLFormElement) {
81+
const form = new FormData(event)
82+
const name = form.get("name") as string
83+
const public_key = form.get("public_key") as string
84+
emit("confirm", { name, public_key })
85+
}
86+
</script>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<template>
2+
<div>
3+
slug {{ $route.params.ids }}
4+
</div>
5+
</template>
6+
7+
<script setup lang="ts">
8+
definePageMeta({
9+
middleware: (to) => {
10+
const ids = to.params.ids as string[]
11+
if (ids.length !== 1) {
12+
return abortNavigation()
13+
}
14+
},
15+
})
16+
// const { slug } = useRoute()
17+
</script>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
11
<template>
22
<div>Deploy cluster</div>
33
</template>
4+
5+
<script setup lang="ts">
6+
const { drawer, container } = inject(DashboardLayoutCtxKey)!
7+
8+
onMounted(drawer.close)
9+
onUnmounted(drawer.open)
10+
11+
onMounted(container.fluidize)
12+
onUnmounted(container.containerize)
13+
</script>

frontend/kubecloud-v2/pages/dashboard/clusters/index.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
<div>
33
<h1>Dashboard Clusters {{ isLoading }}</h1>
44

5+
<VBtn :to="ROUTES.Dashboard.Clusters.Deploy()" text="Deploy Cluster" />
6+
<VBtn :to="ROUTES.Dashboard.Clusters('123')" text="Deploy Cluster" />
7+
58
<pre>
69
{{ state }}
710
</pre>
Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,67 @@
11
<template>
2-
<div :style="{ width: '50%' }">
3-
<h1>Dashboard SSH {{ isLoading }}</h1>
2+
<div>
3+
<h1 class="text-h5 font-weight-bold">
4+
SSH Keys
5+
</h1>
6+
<p class="text-body-2 mt-1 text-accent">
7+
Manage your SSH keys for secure server access accross your infrastructure.
8+
</p>
49

5-
<pre>
6-
{{ state }}
7-
</pre>
10+
<div class="mt-8 d-flex justify-end">
11+
<VBtn
12+
variant="tonal"
13+
color="primary"
14+
prepend-icon="mdi-plus"
15+
text="Add SSH Key"
16+
:loading="isLoading"
17+
@click="onAddSSHKey()"
18+
/>
19+
</div>
20+
21+
<VCard class="mt-4" :style="{ padding: '0 !important' }">
22+
<VCardText v-if="keys.length === 0" class="text-body-2 text-center text-accent py-8">
23+
No SSH keys found.
24+
</VCardText>
25+
26+
<VCardText
27+
v-for="(key, index) in keys"
28+
:key="key.id"
29+
:class="{ 'border-t': index !== 0 }"
30+
>
31+
<SSHKey :public-key="key" @delete="keys = keys.filter((_, i) => i !== index)" />
32+
</VCardText>
33+
</VCard>
34+
35+
<v-dialog :model-value="isRevealed" max-width="500" scrollable @update:model-value="cancel()">
36+
<AddSSHKeyDialogCard @confirm="confirm($event)" />
37+
</v-dialog>
838
</div>
939
</template>
1040

1141
<script setup lang="ts">
42+
import type { HandlersSSHKeyInput } from "~/generated/api"
43+
1244
const api = useApi()
1345
14-
const { state, isLoading } = useAsyncState(async () => {
46+
const { state: keys } = useAsyncState(async () => {
1547
const { data } = await api.users.listSshKeys()
16-
return data
17-
}, null)
48+
return data.data ?? []
49+
}, [])
50+
51+
const toast = useToast()
52+
const { execute: addSSHKey, isLoading } = useAsyncState(async (value: HandlersSSHKeyInput) => {
53+
const { data } = await api.users.addSshKey(value)
54+
toast.success({ message: data.message })
55+
if (data.data) {
56+
keys.value = [...keys.value, data.data]
57+
}
58+
}, null, { immediate: false })
59+
60+
const { isRevealed, reveal, cancel, confirm } = useDialog<undefined, HandlersSSHKeyInput>()
61+
async function onAddSSHKey() {
62+
const { data, isCanceled } = await reveal()
63+
if (!isCanceled && data) {
64+
addSSHKey(undefined, data!)
65+
}
66+
}
1867
</script>

frontend/kubecloud-v2/utils/routes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Admin.Workflows = () => `${Admin()}/workflows`
3838

3939
// dashboard routes
4040
interface DashboardRoutes {
41-
Clusters: (() => string) & { Deploy: () => string }
41+
Clusters: ((id?: string) => string) & { Deploy: () => string }
4242
MyNodes: (() => string) & { Explorer: () => string }
4343
SshKeys: () => string
4444
Funds: () => string
@@ -48,7 +48,7 @@ interface DashboardRoutes {
4848
}
4949

5050
const Dashboard: (() => string) & DashboardRoutes = () => "/dashboard"
51-
Dashboard.Clusters = (() => `${Dashboard()}/clusters`) as DashboardRoutes["Clusters"]
51+
Dashboard.Clusters = ((id?: string) => `${Dashboard()}/clusters${id ? `/${id}` : ""}`) as DashboardRoutes["Clusters"]
5252
Dashboard.Clusters.Deploy = () => `${Dashboard.Clusters()}/deploy`
5353
Dashboard.MyNodes = (() => `${Dashboard()}/my-nodes`) as DashboardRoutes["MyNodes"]
5454
Dashboard.MyNodes.Explorer = () => `${Dashboard.MyNodes()}/explorer`

0 commit comments

Comments
 (0)