1
<template>
2
- <div class="min-h-screen bg-gray-50">
3
- <!-- Header -->
4
- <header class="bg-white shadow">
5
- <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
6
- <div class="flex justify-between h-16">
7
- <div class="flex items-center">
8
- <router-link
9
- to="/"
10
- class="text-indigo-600 hover:text-indigo-500 mr-4"
11
- >
12
- ← Back to Dashboard
13
- </router-link>
14
- <h1 class="text-xl font-semibold">Security Cases</h1>
15
- </div>
16
- <div class="flex items-center space-x-4">
17
- <span class="text-sm text-gray-700">{{ user?.username }}</span>
18
- <button
19
- @click="logout"
20
- class="bg-red-600 hover:bg-red-700 text-white px-3 py-2 rounded-md text-sm font-medium"
21
- >
22
- Logout
23
- </button>
24
- </div>
25
- </div>
26
- </div>
27
- </header>
2
+ <div class="min-h-screen bg-gray-50">
3
+ <!-- Header -->
4
+ <header class="bg-white shadow">
5
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
6
+ <div class="flex h-16 justify-between">
7
+ <div class="flex items-center">
8
+ <router-link to="/" class="mr-4 text-indigo-600 hover:text-indigo-500">
9
+ ← Back to Dashboard
10
+ </router-link>
11
+ <h1 class="text-xl font-semibold">Security Cases</h1>
12
+ </div>
13
+ <div class="flex items-center space-x-4">
14
+ <span class="text-sm text-gray-700">{{ user?.username }}</span>
15
+ <button
16
+ @click="logout"
17
+ class="rounded-md bg-red-600 px-3 py-2 text-sm font-medium text-white hover:bg-red-700"
18
+ >
19
+ Logout
20
+ </button>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </header>
25
29
- <!-- Main Content -->
30
- <main class="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
31
- <div class="px-4 py-6 sm:px-0">
32
- <!-- Loading State -->
33
- <div v-if="loading" class="text-center py-8">
34
- <div class="inline-flex items-center px-4 py-2 font-semibold leading-6 text-sm shadow rounded-md text-white bg-indigo-500">
35
- Loading cases...
36
- </div>
37
- </div>
26
+ <!-- Main Content -->
27
+ <main class="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
28
+ <div class="px-4 py-6 sm:px-0">
29
+ <!-- Loading State -->
30
+ <div v-if="loading" class="py-8 text-center">
31
+ <div
32
+ class="inline-flex items-center rounded-md bg-indigo-500 px-4 py-2 text-sm leading-6 font-semibold text-white shadow"
33
+ >
34
+ Loading cases...
35
+ </div>
36
+ </div>
37
39
- <!-- Error State -->
40
- <div v-else-if="error" class="bg-red-50 border border-red-200 rounded-md p-4">
41
- <div class="flex">
42
- <div class="ml-3">
43
- <h3 class="text-sm font-medium text-red-800">
44
- Error loading cases
45
- </h3>
46
- <div class="mt-2 text-sm text-red-700">
47
- {{ error }}
48
- </div>
49
- </div>
50
- </div>
51
- </div>
38
+ <!-- Error State -->
39
+ <div v-else-if="error" class="rounded-md border border-red-200 bg-red-50 p-4">
40
+ <div class="flex">
41
+ <div class="ml-3">
42
+ <h3 class="text-sm font-medium text-red-800">Error loading cases</h3>
43
+ <div class="mt-2 text-sm text-red-700">
44
+ {{ error }}
45
+ </div>
46
+ </div>
47
+ </div>
48
+ </div>
49
53
- <!-- Cases List -->
54
- <div v-else>
55
- <!-- Stats Cards -->
56
- <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
57
- <div class="bg-white overflow-hidden shadow rounded-lg">
58
- <div class="p-5">
59
- <div class="flex items-center">
60
- <div class="w-8 h-8 bg-red-500 rounded-md flex items-center justify-center">
61
- <span class="text-white text-sm font-medium">O</span>
62
- </div>
63
- <div class="ml-3">
64
- <p class="text-sm font-medium text-gray-500">Open</p>
65
- <p class="text-lg font-semibold text-gray-900">{{ getCaseCount('open') }}</p>
66
- </div>
67
- </div>
68
- </div>
69
- </div>
70
- <div class="bg-white overflow-hidden shadow rounded-lg">
71
- <div class="p-5">
72
- <div class="flex items-center">
73
- <div class="w-8 h-8 bg-yellow-500 rounded-md flex items-center justify-center">
74
- <span class="text-white text-sm font-medium">P</span>
75
- </div>
76
- <div class="ml-3">
77
- <p class="text-sm font-medium text-gray-500">In Progress</p>
78
- <p class="text-lg font-semibold text-gray-900">{{ getCaseCount('in_progress') }}</p>
79
- </div>
80
- </div>
81
- </div>
82
- </div>
83
- <div class="bg-white overflow-hidden shadow rounded-lg">
84
- <div class="p-5">
85
- <div class="flex items-center">
86
- <div class="w-8 h-8 bg-green-500 rounded-md flex items-center justify-center">
87
- <span class="text-white text-sm font-medium">C</span>
88
- </div>
89
- <div class="ml-3">
90
- <p class="text-sm font-medium text-gray-500">Closed</p>
91
- <p class="text-lg font-semibold text-gray-900">{{ getCaseCount('closed') }}</p>
92
- </div>
93
- </div>
94
- </div>
95
- </div>
96
- <div class="bg-white overflow-hidden shadow rounded-lg">
97
- <div class="p-5">
98
- <div class="flex items-center">
99
- <div class="w-8 h-8 bg-gray-500 rounded-md flex items-center justify-center">
100
- <span class="text-white text-sm font-medium">T</span>
101
- </div>
102
- <div class="ml-3">
103
- <p class="text-sm font-medium text-gray-500">Total</p>
104
- <p class="text-lg font-semibold text-gray-900">{{ cases.length }}</p>
105
- </div>
106
- </div>
107
- </div>
108
- </div>
109
- </div>
50
+ <!-- Cases List -->
51
+ <div v-else>
52
+ <!-- Stats Cards -->
53
+ <div class="mb-6 grid grid-cols-1 gap-4 md:grid-cols-4">
54
+ <div class="overflow-hidden rounded-lg bg-white shadow">
55
+ <div class="p-5">
56
+ <div class="flex items-center">
57
+ <div class="flex h-8 w-8 items-center justify-center rounded-md bg-red-500">
58
+ <span class="text-sm font-medium text-white">O</span>
59
+ </div>
60
+ <div class="ml-3">
61
+ <p class="text-sm font-medium text-gray-500">Open</p>
62
+ <p class="text-lg font-semibold text-gray-900">{{ getCaseCount("open") }}</p>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ <div class="overflow-hidden rounded-lg bg-white shadow">
68
+ <div class="p-5">
69
+ <div class="flex items-center">
70
+ <div class="flex h-8 w-8 items-center justify-center rounded-md bg-yellow-500">
71
+ <span class="text-sm font-medium text-white">P</span>
72
+ </div>
73
+ <div class="ml-3">
74
+ <p class="text-sm font-medium text-gray-500">In Progress</p>
75
+ <p class="text-lg font-semibold text-gray-900">
76
+ {{ getCaseCount("in_progress") }}
77
+ </p>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ <div class="overflow-hidden rounded-lg bg-white shadow">
83
+ <div class="p-5">
84
+ <div class="flex items-center">
85
+ <div class="flex h-8 w-8 items-center justify-center rounded-md bg-green-500">
86
+ <span class="text-sm font-medium text-white">C</span>
87
+ </div>
88
+ <div class="ml-3">
89
+ <p class="text-sm font-medium text-gray-500">Closed</p>
90
+ <p class="text-lg font-semibold text-gray-900">{{ getCaseCount("closed") }}</p>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ <div class="overflow-hidden rounded-lg bg-white shadow">
96
+ <div class="p-5">
97
+ <div class="flex items-center">
98
+ <div class="flex h-8 w-8 items-center justify-center rounded-md bg-gray-500">
99
+ <span class="text-sm font-medium text-white">T</span>
100
+ </div>
101
+ <div class="ml-3">
102
+ <p class="text-sm font-medium text-gray-500">Total</p>
103
+ <p class="text-lg font-semibold text-gray-900">{{ cases.length }}</p>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </div>
109
111
- <!-- Cases Table -->
112
- <div class="bg-white shadow overflow-hidden sm:rounded-md">
113
- <div class="px-4 py-5 sm:px-6">
114
- <h3 class="text-lg leading-6 font-medium text-gray-900">
115
- Security Cases
116
- </h3>
117
- <p class="mt-1 max-w-2xl text-sm text-gray-500">
118
- Security incident cases for your organization
119
- </p>
120
- </div>
110
+ <!-- Cases Table -->
111
+ <div class="overflow-hidden bg-white shadow sm:rounded-md">
112
+ <div class="px-4 py-5 sm:px-6">
113
+ <h3 class="text-lg leading-6 font-medium text-gray-900">Security Cases</h3>
114
+ <p class="mt-1 max-w-2xl text-sm text-gray-500">
115
+ Security incident cases for your organization
116
+ </p>
117
+ </div>
118
122
- <div v-if="cases.length === 0" class="px-4 py-5 sm:px-6 text-center text-gray-500">
123
- No cases found
124
- </div>
119
+ <div v-if="cases.length === 0" class="px-4 py-5 text-center text-gray-500 sm:px-6">
120
+ No cases found
121
+ </div>
122
126
- <ul v-else class="divide-y divide-gray-200">
127
- <li v-for="case_ in cases" :key="case_.id" class="px-4 py-4 sm:px-6 hover:bg-gray-50 cursor-pointer" @click="viewCaseDetails(case_.id)">
128
- <div class="flex items-center justify-between">
129
- <div class="flex items-center">
130
- <div
131
- class="w-3 h-3 rounded-full mr-3"
132
- :class="{
133
- 'bg-red-500': case_.case_status === 'open',
134
- 'bg-yellow-500': case_.case_status === 'in_progress',
135
- 'bg-green-500': case_.case_status === 'closed',
136
- 'bg-gray-500': !case_.case_status
137
- }"
138
- ></div>
139
- <div>
140
- <p class="text-sm font-medium text-gray-900 hover:text-indigo-600">
141
- {{ case_.case_name || 'Unnamed Case' }}
142
- </p>
143
- <p class="text-sm text-gray-500">
144
- {{ case_.case_description || 'No description available' }}
145
- </p>
146
- <p class="text-xs text-gray-400 mt-1">
147
- Created: {{ formatDate(case_.case_creation_time) }}
148
- <span v-if="case_.assigned_to"> • Assigned to: {{ case_.assigned_to }}</span>
149
- <span v-if="case_.comments && case_.comments.length > 0"> • {{ case_.comments.length }} {{ case_.comments.length === 1 ? 'comment' : 'comments' }}</span>
150
- </p>
151
- </div>
152
- </div>
153
- <div class="flex items-center space-x-2">
154
- <span
155
- class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
156
- :class="{
157
- 'bg-red-100 text-red-800': case_.case_status === 'open',
158
- 'bg-yellow-100 text-yellow-800': case_.case_status === 'in_progress',
159
- 'bg-green-100 text-green-800': case_.case_status === 'closed',
160
- 'bg-gray-100 text-gray-800': !case_.case_status
161
- }"
162
- >
163
- {{ case_.case_status || 'Unknown' }}
164
- </span>
165
- <span
166
- v-if="case_.escalation_level"
167
- class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium"
168
- :class="{
169
- 'bg-red-100 text-red-800': case_.escalation_level === 'high',
170
- 'bg-yellow-100 text-yellow-800': case_.escalation_level === 'medium',
171
- 'bg-blue-100 text-blue-800': case_.escalation_level === 'low'
172
- }"
173
- >
174
- {{ case_.escalation_level }}
175
- </span>
176
- </div>
177
- </div>
178
- </li>
179
- </ul>
180
- </div>
123
+ <ul v-else class="divide-y divide-gray-200">
124
+ <li
125
+ v-for="case_ in cases"
126
+ :key="case_.id"
127
+ class="cursor-pointer px-4 py-4 hover:bg-gray-50 sm:px-6"
128
+ @click="viewCaseDetails(case_.id)"
129
+ >
130
+ <div class="flex items-center justify-between">
131
+ <div class="flex items-center">
132
+ <div
133
+ class="mr-3 h-3 w-3 rounded-full"
134
+ :class="{
135
+ 'bg-red-500': case_.case_status === 'open',
136
+ 'bg-yellow-500': case_.case_status === 'in_progress',
137
+ 'bg-green-500': case_.case_status === 'closed',
138
+ 'bg-gray-500': !case_.case_status
139
+ }"
140
+ ></div>
141
+ <div>
142
+ <p class="text-sm font-medium text-gray-900 hover:text-indigo-600">
143
+ {{ case_.case_name || "Unnamed Case" }}
144
+ </p>
145
+ <p class="text-sm text-gray-500">
146
+ {{ case_.case_description || "No description available" }}
147
+ </p>
148
+ <p class="mt-1 text-xs text-gray-400">
149
+ Created: {{ formatDate(case_.case_creation_time) }}
150
+ <span v-if="case_.assigned_to">
151
+ • Assigned to: {{ case_.assigned_to }}
152
+ </span>
153
+ <span v-if="case_.comments && case_.comments.length > 0">
154
+ • {{ case_.comments.length }}
155
+ {{ case_.comments.length === 1 ? "comment" : "comments" }}
156
+ </span>
157
+ </p>
158
+ </div>
159
+ </div>
160
+ <div class="flex items-center space-x-2">
161
+ <span
162
+ class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium"
163
+ :class="{
164
+ 'bg-red-100 text-red-800': case_.case_status === 'open',
165
+ 'bg-yellow-100 text-yellow-800': case_.case_status === 'in_progress',
166
+ 'bg-green-100 text-green-800': case_.case_status === 'closed',
167
+ 'bg-gray-100 text-gray-800': !case_.case_status
168
+ }"
169
+ >
170
+ {{ case_.case_status || "Unknown" }}
171
+ </span>
172
+ <span
173
+ v-if="case_.escalation_level"
174
+ class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium"
175
+ :class="{
176
+ 'bg-red-100 text-red-800': case_.escalation_level === 'high',
177
+ 'bg-yellow-100 text-yellow-800': case_.escalation_level === 'medium',
178
+ 'bg-blue-100 text-blue-800': case_.escalation_level === 'low'
179
+ }"
180
+ >
181
+ {{ case_.escalation_level }}
182
+ </span>
183
+ </div>
184
+ </div>
185
+ </li>
186
+ </ul>
187
+ </div>
188
182
- <!-- Pagination (if needed) -->
183
- <div v-if="cases.length > 0" class="mt-6 flex justify-center">
184
- <button
185
- @click="refreshCases"
186
- class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-md text-sm font-medium"
187
- >
188
- Refresh
189
- </button>
190
- </div>
191
- </div>
192
- </div>
193
- </main>
194
- </div>
189
+ <!-- Pagination (if needed) -->
190
+ <div v-if="cases.length > 0" class="mt-6 flex justify-center">
191
+ <button
192
+ @click="refreshCases"
193
+ class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700"
194
+ >
195
+ Refresh
196
+ </button>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ </main>
201
+ </div>
202
</template>
203
204
<script setup lang="ts">
198
-import { ref, onMounted, computed } from 'vue'
199
-import { useRouter } from 'vue-router'
200
-import { useAuthStore } from '@/stores/auth'
201
-import { httpClient } from '@/utils/httpClient'
205
+import { ref, onMounted, computed } from "vue"
206
+import { useRouter } from "vue-router"
207
+import { useAuthStore } from "@/stores/auth"
208
+import { httpClient } from "@/utils/httpClient"
209
210
interface Case {
204
- id: number
205
- case_name: string
206
- case_description: string
207
- case_status: string
208
- case_creation_time: string
209
- assigned_to?: string
210
- escalation_level?: string
211
- customer_code?: string
212
- comments?: Array<{
213
- id: number
214
- comment: string
215
- user_name?: string
216
- created_at: string
217
- }>
211
+ id: number
212
+ case_name: string
213
+ case_description: string
214
+ case_status: string
215
+ case_creation_time: string
216
+ assigned_to?: string
217
+ escalation_level?: string
218
+ customer_code?: string
219
+ comments?: Array<{
220
+ id: number
221
+ comment: string
222
+ user_name?: string
223
+ created_at: string
224
+ }>
225
}
226
227
const router = useRouter()
229
230
const cases = ref<Case[]>([])
231
const loading = ref(false)
225
-const error = ref('')
232
+const error = ref("")
233
234
const user = computed(() => authStore.user)
235
236
const getCaseCount = (status: string) => {
230
- return cases.value.filter(case_ => case_.case_status === status).length
237
+ return cases.value.filter(case_ => case_.case_status === status).length
238
}
239
240
const formatDate = (dateString: string) => {
234
- if (!dateString) return 'Unknown'
235
- try {
236
- return new Date(dateString).toLocaleDateString()
237
- } catch {
238
- return 'Invalid date'
239
- }
241
+ if (!dateString) return "Unknown"
242
+ try {
243
+ return new Date(dateString).toLocaleDateString()
244
+ } catch {
245
+ return "Invalid date"
246
+ }
247
}
248
249
const fetchCases = async () => {
243
- loading.value = true
244
- error.value = ''
250
+ loading.value = true
251
+ error.value = ""
252
246
- try {
247
- const response = await httpClient.get('/incidents/db_operations/cases')
248
- cases.value = response.data.cases || []
249
- } catch (err: any) {
250
- error.value = err.response?.data?.detail || 'Failed to fetch cases'
251
- console.error('Failed to fetch cases:', err)
252
- } finally {
253
- loading.value = false
254
- }
253
+ try {
254
+ const response = await httpClient.get("/incidents/db_operations/cases")
255
+ cases.value = response.data.cases || []
256
+ } catch (err: any) {
257
+ error.value = err.response?.data?.detail || "Failed to fetch cases"
258
+ console.error("Failed to fetch cases:", err)
259
+ } finally {
260
+ loading.value = false
261
+ }
262
}
263
264
const refreshCases = () => {
258
- fetchCases()
265
+ fetchCases()
266
}
267
268
const viewCaseDetails = (caseId: number) => {
262
- router.push(`/cases/${caseId}`)
269
+ router.push(`/cases/${caseId}`)
270
}
271
272
const logout = () => {
266
- authStore.logout()
267
- router.push('/login')
273
+ authStore.logout()
274
+ router.push("/login")
275
}
276
277
onMounted(() => {
271
- fetchCases()
278
+ fetchCases()
279
})
280
</script>