Skip to content

Commit 56d58be

Browse files
author
Lasim
committed
feat(credentials): implement credential management views and functionality
- Added CredentialTableColumns.vue for displaying a table of credentials with sorting and actions. - Created [id].vue for detailed view of individual credentials, including update and delete functionalities. - Developed index.vue for listing credentials with search and add capabilities. - Introduced types.ts to define credential-related types for better type safety and clarity. - Integrated event bus for managing team selection and credential updates across components. - Implemented loading states, error handling, and success messages for user feedback.
1 parent 0c23064 commit 56d58be

File tree

5 files changed

+215
-11
lines changed

5 files changed

+215
-11
lines changed

services/frontend/src/router/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,13 @@ const routes = [
8686
{
8787
path: '/credentials',
8888
name: 'Credentials',
89-
component: () => import('../views/Credentials.vue'),
89+
component: () => import('../views/credentials/index.vue'),
9090
meta: { requiresSetup: true },
9191
},
9292
{
9393
path: '/credentials/:id',
9494
name: 'CredentialDetail',
95-
component: () => import('../views/CredentialDetail.vue'),
95+
component: () => import('../views/credentials/[id].vue'),
9696
meta: { requiresSetup: true },
9797
},
9898
{
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue'
3+
import { useI18n } from 'vue-i18n'
4+
import {
5+
Table,
6+
TableBody,
7+
TableCell,
8+
TableHead,
9+
TableHeader,
10+
TableRow,
11+
} from '@/components/ui/table'
12+
import { Badge } from '@/components/ui/badge'
13+
import { Button } from '@/components/ui/button'
14+
import { Settings, Key } from 'lucide-vue-next'
15+
import type { CloudCredential, CloudCredentialBasic } from './types'
16+
17+
interface Props {
18+
credentials: (CloudCredential | CloudCredentialBasic)[]
19+
onManage: (credentialId: string) => void
20+
}
21+
22+
const props = defineProps<Props>()
23+
const { t } = useI18n()
24+
25+
// Sort credentials by name for consistency
26+
const sortedCredentials = computed(() => {
27+
return [...props.credentials].sort((a, b) => a.name.localeCompare(b.name))
28+
})
29+
30+
// Format date for display
31+
const formatDate = (dateString: string) => {
32+
const date = new Date(dateString)
33+
return date.toLocaleDateString('en-US', {
34+
year: 'numeric',
35+
month: 'short',
36+
day: 'numeric'
37+
})
38+
}
39+
40+
// Get created by display info
41+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
42+
const getCreatedByDisplay = (createdBy: any) => {
43+
if (typeof createdBy === 'object' && createdBy?.username) {
44+
return {
45+
username: createdBy.username,
46+
email: createdBy.email
47+
}
48+
}
49+
return {
50+
username: typeof createdBy === 'string' ? createdBy : t('credentials.table.values.unknown'),
51+
email: null
52+
}
53+
}
54+
55+
// Get provider badge variant
56+
const getProviderVariant = (providerId: string) => {
57+
// You can customize this based on provider types
58+
switch (providerId) {
59+
case 'aws':
60+
return 'default'
61+
case 'gcp':
62+
return 'secondary'
63+
case 'azure':
64+
return 'outline'
65+
default:
66+
return 'secondary'
67+
}
68+
}
69+
</script>
70+
71+
<template>
72+
<div class="rounded-md border">
73+
<Table>
74+
<TableHeader>
75+
<TableRow>
76+
<TableHead class="w-[200px]">{{ t('credentials.table.columns.name') }}</TableHead>
77+
<TableHead class="w-[150px]">{{ t('credentials.table.columns.provider') }}</TableHead>
78+
<TableHead>{{ t('credentials.table.columns.comment') }}</TableHead>
79+
<TableHead class="w-[150px]">{{ t('credentials.table.columns.createdBy') }}</TableHead>
80+
<TableHead class="w-[120px]">{{ t('credentials.table.columns.createdAt') }}</TableHead>
81+
<TableHead class="w-[100px] text-right">{{ t('credentials.table.columns.actions') }}</TableHead>
82+
</TableRow>
83+
</TableHeader>
84+
<TableBody>
85+
<!-- Empty State -->
86+
<TableRow v-if="sortedCredentials.length === 0">
87+
<TableCell :colspan="6" class="h-24 text-center text-muted-foreground">
88+
{{ t('credentials.table.noData') }}
89+
</TableCell>
90+
</TableRow>
91+
92+
<!-- Data Rows -->
93+
<TableRow
94+
v-for="credential in sortedCredentials"
95+
:key="credential.id"
96+
class="hover:bg-muted/50"
97+
>
98+
<!-- Name -->
99+
<TableCell>
100+
<div class="flex items-center gap-2">
101+
<Key class="h-4 w-4 text-muted-foreground" />
102+
<div>
103+
<div class="font-medium">{{ credential.name }}</div>
104+
<div class="text-xs text-muted-foreground font-mono">{{ credential.id.slice(0, 8) }}...</div>
105+
</div>
106+
</div>
107+
</TableCell>
108+
109+
<!-- Provider -->
110+
<TableCell>
111+
<div class="flex items-center gap-2">
112+
<img
113+
:src="`/images/provider/${credential.provider.id}.svg`"
114+
:alt="credential.provider.name"
115+
class="w-5 h-5"
116+
@error="($event.target as HTMLImageElement).style.display = 'none'"
117+
/>
118+
<Badge :variant="getProviderVariant(credential.provider.id)">
119+
{{ credential.provider.name }}
120+
</Badge>
121+
</div>
122+
</TableCell>
123+
124+
<!-- Comment -->
125+
<TableCell>
126+
<span v-if="credential.comment" class="text-sm">
127+
{{ credential.comment }}
128+
</span>
129+
<span v-else class="text-sm text-muted-foreground italic">
130+
{{ t('credentials.detail.values.noComment') }}
131+
</span>
132+
</TableCell>
133+
134+
<!-- Created By -->
135+
<TableCell>
136+
<div class="text-sm">
137+
<div class="font-medium">{{ getCreatedByDisplay(credential.createdBy).username }}</div>
138+
<div
139+
v-if="getCreatedByDisplay(credential.createdBy).email"
140+
class="text-xs text-muted-foreground"
141+
>
142+
{{ getCreatedByDisplay(credential.createdBy).email }}
143+
</div>
144+
</div>
145+
</TableCell>
146+
147+
<!-- Created At -->
148+
<TableCell class="text-sm text-muted-foreground">
149+
{{ formatDate(credential.createdAt) }}
150+
</TableCell>
151+
152+
<!-- Actions -->
153+
<TableCell class="text-right">
154+
<Button
155+
variant="outline"
156+
size="sm"
157+
@click="props.onManage(credential.id)"
158+
class="h-8"
159+
>
160+
<Settings class="h-4 w-4 mr-2" />
161+
{{ t('credentials.actions.view') }}
162+
</Button>
163+
</TableCell>
164+
</TableRow>
165+
</TableBody>
166+
</Table>
167+
</div>
168+
</template>

services/frontend/src/views/CredentialDetail.vue renamed to services/frontend/src/views/credentials/[id].vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import DashboardLayout from '@/components/DashboardLayout.vue'
2929
import { CredentialsService } from '@/services/credentialsService'
3030
import { TeamService, type Team } from '@/services/teamService'
3131
import { useEventBus } from '@/composables/useEventBus'
32-
import type { CloudCredential, CloudProvider, CredentialField } from '@/types/credentials'
32+
import type { CloudCredential, CloudProvider, CredentialField } from './types'
3333
3434
const { t } = useI18n()
3535
const route = useRoute()

services/frontend/src/views/Credentials.vue renamed to services/frontend/src/views/credentials/index.vue

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import { CredentialsService } from '@/services/credentialsService'
1111
import { UserService } from '@/services/userService'
1212
import { TeamService, type Team } from '@/services/teamService'
1313
import { useEventBus } from '@/composables/useEventBus'
14-
import type { CloudCredential, CloudCredentialBasic } from '@/types/credentials'
14+
import type { CloudCredential, CloudCredentialBasic } from './types'
1515
import CredentialsTable from '@/components/credentials/CredentialsTable.vue'
1616
import AddCredentialDialog from '@/components/credentials/AddCredentialDialog.vue'
1717
18-
// Simple debounce function
18+
1919
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2020
function debounce<T extends (...args: any[]) => any>(func: T, wait: number): T {
2121
let timeout: ReturnType<typeof setTimeout>
@@ -52,7 +52,7 @@ const initializeSelectedTeam = async () => {
5252
const userTeams = await TeamService.getUserTeams()
5353
if (userTeams.length > 0) {
5454
const storedTeamId = eventBus.getState<string>('selected_team_id')
55-
55+
5656
if (storedTeamId) {
5757
// Try to find the stored team in available teams
5858
const storedTeam = userTeams.find(team => team.id === storedTeamId)
@@ -219,11 +219,6 @@ const checkDeleteSuccess = () => {
219219
220220
// Clear the query parameter from URL
221221
router.replace({ path: '/credentials' })
222-
223-
// Clear the message after 5 seconds
224-
setTimeout(() => {
225-
deleteSuccessMessage.value = null
226-
}, 5000)
227222
}
228223
}
229224
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Re-export credential types for local use
2+
export type {
3+
CloudProvider,
4+
CredentialField,
5+
UserInfo,
6+
CloudCredential,
7+
CloudCredentialBasic,
8+
CreateCredentialInput,
9+
UpdateCredentialInput,
10+
CloudProvidersResponse,
11+
CloudCredentialsResponse,
12+
CloudCredentialResponse,
13+
SearchCredentialsResponse,
14+
ApiError,
15+
CredentialFormData,
16+
FieldError,
17+
ValidationResult,
18+
CredentialEvents
19+
} from '@/types/credentials'
20+
21+
// Import types for local use
22+
import type { CloudCredential, CloudCredentialBasic } from '@/types/credentials'
23+
24+
// Local types specific to credentials views
25+
export interface CredentialTableProps {
26+
credentials: CloudCredential[] | CloudCredentialBasic[]
27+
onManage: (credentialId: string) => void
28+
}
29+
30+
export interface CredentialFilters {
31+
provider?: string
32+
status?: 'active' | 'inactive'
33+
search?: string
34+
}
35+
36+
export interface CredentialTableColumn {
37+
key: string
38+
label: string
39+
sortable?: boolean
40+
width?: string
41+
}

0 commit comments

Comments
 (0)