|
5 | 5 | <RecentUpdatedRegistries :registryStatusList="registryStatusList"/>
|
6 | 6 | <StatusDistribution :chartData="statusSummary"/>
|
7 | 7 | </div>
|
8 |
| - <ResourceTable |
| 8 | + <SortableTable |
9 | 9 | ref="table"
|
10 |
| - :schema="schema" |
11 | 10 | :has-advanced-filtering="true"
|
12 | 11 | :rows="rows"
|
13 | 12 | :headers="headers"
|
|
33 | 32 | </button>
|
34 | 33 | </div>
|
35 | 34 | </template>
|
36 |
| - </ResourceTable> |
| 35 | + </SortableTable> |
37 | 36 | </div>
|
38 | 37 | </template>
|
39 | 38 |
|
40 | 39 | <script>
|
41 | 40 |
|
42 | 41 | import { ref } from "vue";
|
43 | 42 | import { RESOURCE } from "@sbombastic-image-vulnerability-scanner/types";
|
44 |
| - import ResourceTable from "@shell/components/ResourceTable"; |
| 43 | + import SortableTable from "@shell/components/SortableTable"; |
45 | 44 | import RecentUpdatedRegistries from "@sbombastic-image-vulnerability-scanner/components/RecentUpdatedRegistries";
|
46 | 45 | import StatusDistribution from "@sbombastic-image-vulnerability-scanner/components/StatusDistribution";
|
47 | 46 | import { REGISTRY_SCAN_TABLE } from "@sbombastic-image-vulnerability-scanner/config/table-headers";
|
|
56 | 55 | export default {
|
57 | 56 | name: 'registries',
|
58 | 57 | components: {
|
59 |
| - ResourceTable, |
| 58 | + SortableTable, |
60 | 59 | RecentUpdatedRegistries,
|
61 | 60 | StatusDistribution
|
62 | 61 | },
|
|
88 | 87 | methods: {
|
89 | 88 | loadData() {
|
90 | 89 | this.registryStatusList = [];
|
91 |
| - this.statusSummary = { |
92 |
| - scheduled: 0, |
93 |
| - pending: 0, |
94 |
| - inprogress: 0, |
95 |
| - complete: 0, |
96 |
| - failed: 0 |
97 |
| - }; |
| 90 | + this.statusSummary = {}; |
98 | 91 |
|
99 | 92 | let registriesCRD = this.$store.getters['cluster/all'](RESOURCE.REGISTRY);
|
100 | 93 |
|
|
109 | 102 | return new Date(b.status.startTime) - new Date(a.status.startTime);
|
110 | 103 | });
|
111 | 104 |
|
| 105 | + const scanJobMap = this.getScanJobMap(scanJobCRD); |
| 106 | + const registryDataset = this.getRegistryDataset(registriesCRD, scanJobMap); |
| 107 | + this.rows = registryDataset.rows; |
| 108 | + this.registryStatusList = registryDataset.registryStatusList; |
| 109 | + this.statusSummary = registryDataset.statusSummary; |
| 110 | + console.log("this.rows",this.rows) |
| 111 | + }, |
| 112 | + refresh() { |
| 113 | + window.location.reload(); |
| 114 | + }, |
| 115 | + async startScan(selected) { |
| 116 | + const scanjobObj = await this.$store.dispatch('cluster/create', { |
| 117 | + type: RESOURCE.SCAN_JOB, |
| 118 | + metadata: { |
| 119 | + generateName: selected?.[0]?.metadata.name || this.selectedRows[0].metadata.name, |
| 120 | + namespace: selected?.[0]?.metadata.namespace || this.selectedRows[0].metadata.namespace, |
| 121 | + }, |
| 122 | + spec: { |
| 123 | + registry: this.selectedRows[0].metadata.name, |
| 124 | + } |
| 125 | + }); |
| 126 | + try { |
| 127 | + await scanjobObj.save(); |
| 128 | + // this.refresh(); |
| 129 | + this.$store.dispatch('growl/success', { |
| 130 | + title: this.t('imageScanner.registries.messages.registryScanComplete'), |
| 131 | + message: this.t('imageScanner.registries.messages.registryScanComplete', { name: this.selectedRows[0].metadata.name }), |
| 132 | + }); |
| 133 | + } catch (e) { |
| 134 | + this.$store.dispatch('growl/error', { |
| 135 | + title: this.t('imageScanner.registries.messages.registryScanFailed'), |
| 136 | + message: e.message, |
| 137 | + }); |
| 138 | + } |
| 139 | + }, |
| 140 | + openAddEditRegistry() {}, |
| 141 | + onSelectionChange(selected) { |
| 142 | + this.selectedRows = selected || []; |
| 143 | + }, |
| 144 | + async promptRemoveRegistry() { |
| 145 | + const table = this.$refs.table; |
| 146 | + const act = findBy(table.availableActions, 'action', 'promptRemove'); |
| 147 | + if ( act ) { |
| 148 | + table.setBulkActionOfInterest(act); |
| 149 | + table.applyTableAction(act); |
| 150 | + } |
| 151 | + return; |
| 152 | + }, |
| 153 | + getScanJobMap(scanJobCRD) { |
112 | 154 | let scanJobMap = {};
|
113 | 155 | scanJobCRD.forEach((rec) => {
|
114 |
| - rec.status.statusResult = rec.status?.conditions?.filter(condition => { |
| 156 | + let statusIndex = rec.status?.conditions?.findIndex((condition) => { |
115 | 157 | return condition.status === "True";
|
116 |
| - })[0] || { |
| 158 | + }); |
| 159 | + console.log("statusIndex", statusIndex, rec.status?.conditions); |
| 160 | + rec.status.statusResult = statusIndex > -1 ? { |
| 161 | + type: rec.status.conditions[statusIndex].type, |
| 162 | + lastTransitionTime: rec.status.conditions[statusIndex].lastTransitionTime, |
| 163 | + statusIndex: statusIndex |
| 164 | + } : { |
117 | 165 | type: "Pending",
|
118 |
| - lastUpdateTime: null, |
| 166 | + lastTransitionTime: null, |
| 167 | + statusIndex: -1 |
119 | 168 | };
|
120 | 169 |
|
121 | 170 | // Set ScanJobe map with namespace/registry as key and up to 2 scan jobs as value
|
|
129 | 178 | scanJobMap[`${rec.metadata.namespace}/${rec.spec.registry}`] = [rec];
|
130 | 179 | }
|
131 | 180 | });
|
132 |
| -
|
133 |
| - this.rows = registriesCRD.map((rec) => { |
| 181 | + return scanJobMap; |
| 182 | + }, |
| 183 | + getRegistryDataset(registriesCRD, scanJobMap) { |
| 184 | + let registryStatusList = []; |
| 185 | + const statusSummary = { |
| 186 | + scheduled: 0, |
| 187 | + pending: 0, |
| 188 | + inprogress: 0, |
| 189 | + complete: 0, |
| 190 | + failed: 0 |
| 191 | + }; |
| 192 | + const rows = registriesCRD.map((rec) => { |
134 | 193 | this.latestUpdateTime = new Date();
|
135 | 194 | let scanjobs = scanJobMap[`${rec.metadata.namespace}/${rec.metadata.name}`] || [];
|
136 | 195 | const status = scanjobs[0] ? scanjobs[0].status.statusResult.type.toLowerCase() || "pending" : "none";
|
137 |
| - const prevStatus = scanjobs[1] ? scanjobs[1].status.statusResult.type.toLowerCase() || "pending" : "none"; |
| 196 | + const prevStatus = this.getPreviousStatus(scanjobs); |
| 197 | + const prevScanStatus = scanjobs[1] ? scanjobs[1].status.statusResult.type.toLowerCase() || "pending" : "none"; |
138 | 198 |
|
139 | 199 | // Reform the record for the table
|
140 | 200 | rec.id = `${rec.metadata.namespace}/${rec.metadata.name}`;
|
141 | 201 | rec.currStatus = status;
|
142 |
| - rec.prevStatus = prevStatus; |
| 202 | + rec.prevScanStatus = prevScanStatus; |
143 | 203 | rec.progress = scanjobs[0] && scanjobs[0].status.imageCount && scanjobs[0].status.scannedImageCount?
|
144 | 204 | Math.ceil(scanjobs[0].status.scannedImageCount / scanjobs[0].status.imageCount * 100) : 0;
|
145 | 205 | rec.prevProgress = scanjobs[1] && scanjobs[1].status.imageCount && scanjobs[1].status.scannedImageCount?
|
146 | 206 | Math.ceil(scanjobs[1].status.scannedImageCount / scanjobs[1].status.imageCount * 100) : 0;
|
147 | 207 | rec.error = scanjobs[0] && scanjobs[0].status.statusResult.type === "Failed" ? scanjobs[0].status.statusResult.message : "";
|
148 | 208 |
|
149 | 209 | // Summarize the data for Latest status updates panel
|
150 |
| - this.registryStatusList.push({ |
| 210 | + registryStatusList.push({ |
151 | 211 | registryName: rec.metadata.name,
|
152 | 212 | uri: rec.spec.uri,
|
153 | 213 | namespace: rec.metadata.namespace,
|
154 |
| - prevStatus: prevStatus, |
| 214 | + prevScanStatus: prevStatus, |
155 | 215 | currStatus: status,
|
156 |
| - lastUpdateTime: scanjobs[0]?.status?.statusResult?.lastUpdateTime || rec.metadata.creationTimestamp, |
| 216 | + lastTransitionTime: scanjobs[0]?.status?.statusResult?.lastTransitionTime || rec.metadata.creationTimestamp, |
157 | 217 | });
|
158 | 218 |
|
159 | 219 | //Summarize the data for Status distribution panel
|
160 |
| - if (status && this.statusSummary.hasOwnProperty(status)) { |
161 |
| - this.statusSummary[status]++; |
| 220 | + if (status && statusSummary.hasOwnProperty(status)) { |
| 221 | + statusSummary[status]++; |
162 | 222 | }
|
163 | 223 |
|
164 | 224 | rec.availableActions.push({
|
|
171 | 231 |
|
172 | 232 | console.log("rec.availableActions", rec.availableActions)
|
173 | 233 | return rec;
|
174 |
| - }); |
175 |
| -
|
| 234 | + }); |
176 | 235 | // Sort and limit the registryStatusList to 5 most recent updates
|
177 |
| - this.registryStatusList = this.registryStatusList.sort((a, b) => new Date(b.lastUpdateTime) - new Date(a.lastUpdateTime)).slice(0, 5); |
178 |
| - while (this.registryStatusList.length < 5) { |
179 |
| - this.registryStatusList.push({ |
| 236 | + registryStatusList = registryStatusList.sort((a, b) => new Date(b.lastTransitionTime) - new Date(a.lastTransitionTime)).slice(0, 5); |
| 237 | + while (registryStatusList.length < 5) { |
| 238 | + registryStatusList.push({ |
180 | 239 | registryName: "",
|
181 | 240 | uri: "",
|
182 |
| - prevStatus: "", |
| 241 | + prevScanStatus: "", |
183 | 242 | currStatus: "",
|
184 |
| - lastUpdateTime: new Date().toISOString() |
| 243 | + lastTransitionTime: new Date().toISOString() |
185 | 244 | });
|
186 | 245 | }
|
187 |
| - }, |
188 |
| - refresh() { |
189 |
| - window.location.reload(); |
190 |
| - }, |
191 |
| - async startScan(selected) { |
192 |
| - const scanjobObj = await this.$store.dispatch('cluster/create', { |
193 |
| - type: RESOURCE.SCAN_JOB, |
194 |
| - metadata: { |
195 |
| - generateName: selected?.[0]?.metadata.name || this.selectedRows[0].metadata.name, |
196 |
| - namespace: selected?.[0]?.metadata.namespace || this.selectedRows[0].metadata.namespace, |
197 |
| - }, |
198 |
| - spec: { |
199 |
| - registry: this.selectedRows[0].metadata.name, |
200 |
| - } |
201 |
| - }); |
202 |
| - try { |
203 |
| - await scanjobObj.save(); |
204 |
| - // this.refresh(); |
205 |
| - this.$store.dispatch('growl/success', { |
206 |
| - title: this.t('imageScanner.registries.messages.registryScanComplete'), |
207 |
| - message: this.t('imageScanner.registries.messages.registryScanComplete', { name: this.selectedRows[0].metadata.name }), |
208 |
| - }); |
209 |
| - } catch (e) { |
210 |
| - this.$store.dispatch('growl/error', { |
211 |
| - title: this.t('imageScanner.registries.messages.registryScanFailed'), |
212 |
| - message: e.message, |
213 |
| - }); |
| 246 | + return { |
| 247 | + rows, |
| 248 | + registryStatusList, |
| 249 | + statusSummary |
214 | 250 | }
|
215 | 251 | },
|
216 |
| - openAddEditRegistry() {}, |
217 |
| - onSelectionChange(selected) { |
218 |
| - this.selectedRows = selected || []; |
219 |
| - }, |
220 |
| - async promptRemoveRegistry() { |
221 |
| - const table = this.$refs.table.$refs.table; |
222 |
| - const act = findBy(table.availableActions, 'action', 'promptRemove'); |
223 |
| - if ( act ) { |
224 |
| - table.setBulkActionOfInterest(act); |
225 |
| - table.applyTableAction(act); |
| 252 | + getPreviousStatus(scanjobs) { |
| 253 | + if (scanjobs && scanjobs[0] && scanjobs[0].status && scanjobs[0].status.statusResult && scanjobs[0].status.statusResult.statusIndex > 0) { |
| 254 | + let index = scanjobs[0].status.statusResult.statusIndex; |
| 255 | + if (index < 3) { |
| 256 | + return scanjobs[0].status.conditions[index - 1].type.toLowerCase(); |
| 257 | + } else { |
| 258 | + return scanjobs[0].status.conditions[index - 2].type.toLowerCase(); |
| 259 | + } |
| 260 | + } else if (scanjobs && scanjobs[1]) { |
| 261 | + return scanjobs[1].status.statusResult.type.toLowerCase(); |
| 262 | + } else { |
| 263 | + return "none"; |
226 | 264 | }
|
227 |
| - return; |
228 |
| - }, |
| 265 | + } |
229 | 266 | },
|
230 | 267 | computed: {
|
231 | 268 | schema() {
|
232 |
| - return this.$store.getters['cluster/schemaFor'](RESOURCE.SCAN_JOB); |
| 269 | + return this.$store.getters['cluster/schemaFor'](RESOURCE.REGISTRY); |
233 | 270 | },
|
234 | 271 | latestUpdateTimeText() {
|
235 | 272 | const dateFormat = escapeHtml( this.$store.getters['prefs/get'](DATE_FORMAT));
|
236 | 273 | const timeFormat = escapeHtml( this.$store.getters['prefs/get'](TIME_FORMAT));
|
237 | 274 | return `${day(new Date(this.latestUpdateTime).getTime()).format(dateFormat)} ${day(new Date(this.latestUpdateTime).getTime()).format(timeFormat)}`;
|
238 | 275 | },
|
239 |
| - } |
| 276 | + }, |
240 | 277 | }
|
241 | 278 |
|
242 | 279 | </script>
|
|
0 commit comments