@cryptotaxi247 / CoPilot / commits / 05e936f1

add agents (#91)

* add agents * precommit fixes

taylor_socfortress committed Aug 31, 2023 at 11:50 UTC 05e936f1ee59f4bdc7dd13d7245243c6e3e57e44
8 files changed +597 -1
public/static/images/gallery/computer.png
Binary files /dev/null and b/public/static/images/gallery/computer.png differ
src/api/agent.ts new
+7
@@ -0,0 +1,7 @@
1 +import connectors from "./connectors"
2 +import agents from "./agents"
3 +
4 +export default {
5 + connectors,
6 + agents
7 +}
src/api/agents.ts new
+9
@@ -0,0 +1,9 @@
1 +import { FlaskBaseResponse } from "@/types/flask"
2 +import { HttpClient } from "./httpClient"
3 +import { Agents } from "@/types/agents"
4 +
5 +export default {
6 + getAgents() {
7 + return HttpClient.get<FlaskBaseResponse & { agents: Agents[] }>("/agents")
8 + }
9 +}
src/api/index.ts
+3 -1
@@ -1,7 +1,9 @@
1 import connectors from "./connectors"
2 import indices from "./indices"
3 +import agents from "./agents"
4
5 export default {
6 connectors,
6 - indices
7 + indices,
8 + agents
9 }
src/core/nav.vue
+3
@@ -15,6 +15,9 @@
15 <el-menu-item index="/indices">
16 <span slot="title">Indicies</span>
17 </el-menu-item>
18 + <el-menu-item index="/agents">
19 + <span slot="title">Agents</span>
20 + </el-menu-item>
21 <el-menu-item index="/indices-bkp">
22 <span slot="title">Indicies-bkp</span>
23 </el-menu-item>
src/router/index.ts
+12
@@ -13,6 +13,7 @@ import Ecommerce from "./ecommerce"
13 import Connectors from "../views/apps/Connectors.vue"
14 import Indices from "../views/apps/Dashboards/Indices.vue"
15 import IndicesBKP from "../views/apps/Dashboards/_bkp_Indices.vue"
16 +import Agents from "../views/apps/Dashboards/Agents.vue"
17 /*
18
19 //pages
@@ -105,6 +106,17 @@ const router = createRouter({
106 tags: ["app"]
107 }
108 },
109 + {
110 + path: "/agents",
111 + name: "agents",
112 + component: Agents,
113 + meta: {
114 + auth: true,
115 + layout: layouts.navLeft,
116 + searchable: true,
117 + tags: ["app"]
118 + }
119 + },
120 {
121 path: "/indices-bkp",
122 name: "indices-bkp",
src/types/agents.d.ts new
+14
@@ -0,0 +1,14 @@
1 +export interface Agents {
2 + agent_id: string
3 + client_id: string
4 + client_last_seen: string
5 + critical_asset: boolean
6 + hostname: string
7 + id?: number
8 + ip_address: string
9 + label: string
10 + last_seen: string
11 + os: string
12 + velociraptor_client_version: string
13 + wazuh_agent_version: string
14 +}
src/views/apps/Dashboards/Agents.vue new
+549
@@ -0,0 +1,549 @@
1 +<template>
2 + <div class="page-contacts flex column" id="page-contacts">
3 + <resize-observer @notify="__resizeHanlder" />
4 +
5 + <div class="contacts-root box grow flex gaps justify-center" :class="contactsClass">
6 + <div class="card-base card-shadow--small search-card scrollable only-y">
7 + <h1 class="mt-0">Agents</h1>
8 +
9 + <el-input prefix-icon="el-icon-search" placeholder="Search a contact" clearable v-model="search"> </el-input>
10 +
11 + <div class="o-050 text-right mt-10 mb-30">
12 + <strong>{{ agentsFiltered.length }}</strong> Agents
13 + </div>
14 +
15 + <el-button @click="sync_agents({})">
16 + <i class="mdi mdi-account-plus mr-10"></i>
17 + Sync Agents</el-button
18 + >
19 +
20 + <div class="p-20">
21 + <p>Critical Assets</p>
22 + <ul class="contacts-favourites">
23 + <li v-for="agent in agentsFavorite" :key="agent.agent_id" @click="openDialog(c)">
24 + <img :src="'/static/images/gallery/computer.png'" alt="user favourite avatar" />
25 + <span>{{ agent.hostname }}</span>
26 + </li>
27 + </ul>
28 + </div>
29 + </div>
30 + <div class="contacts-list box grow scrollable only-y">
31 + <div v-for="agent in agentsFiltered" :key="agent.agent_id" class="flex contact" @click="openEditAgentsModal(agent)">
32 + <div class="star align-vertical p-10 fs-22">
33 + <i class="mdi mdi-star align-vertical-middle" v-if="agent.critical_asset"></i>
34 + <i class="mdi mdi-star-outline align-vertical-middle" v-if="!agent.critical_asset"></i>
35 + </div>
36 + <div class="avatar align-vertical">
37 + <img :src="'/static/images/gallery/computer.png'" class="align-vertical-middle" alt="user avatar" />
38 + </div>
39 + <div class="info box grow flex">
40 + <div class="name box grow flex column justify-center p-10">
41 + <div class="fullname fs-18">
42 + <strong>{{ agent.hostname }}</strong>
43 + </div>
44 + <div class="ip fs-14 secondary-text">{{ agent.ip_address }}</div>
45 + <div class="os fs-14 secondary-text">{{ agent.os }}</div>
46 + </div>
47 + <div class="phone align-vertical p-10">
48 + <span class="align-vertical-middle">{{ agent.last_seen }}</span>
49 + </div>
50 + <!--Add a el-button to make the agent critical-->
51 + <div class="phone align-vertical p-10">
52 + <el-button type="primary" @click="makeAgentCritical(agent.agent_id)" v-if="!agent.critical_asset">
53 + <i class="mdi mdi-star-outline align-vertical-middle"></i>
54 + </el-button>
55 + <el-button type="primary" @click="makeAgentUncritical(agent.agent_id)" v-if="agent.critical_asset">
56 + <i class="mdi mdi-star align-vertical-middle"></i>
57 + </el-button>
58 + </div>
59 + </div>
60 + </div>
61 + </div>
62 + </div>
63 +
64 + <user-dialog v-model="dialogvisible" :userdata="userdata"></user-dialog>
65 + </div>
66 +</template>
67 +
68 +<script>
69 +import UserDialog from "@/components/UserDialog.vue"
70 +import Contacts from "@/assets/data/CONTACTS_MOCK_DATA.json"
71 +import { defineComponent } from "@vue/runtime-core"
72 +import _ from "lodash"
73 +import ResizeObserver from "@/components/vue-resize/ResizeObserver.vue"
74 +import axios from "axios"
75 +
76 +export default defineComponent({
77 + name: "Contacts",
78 + data() {
79 + return {
80 + loading: false,
81 + agents: [],
82 + currentAgent: null,
83 +
84 + // Configure Modal
85 + isEditAgentsModalActive: false,
86 +
87 + search: "",
88 + dialogvisible: false,
89 + pageWidth: 0,
90 + userdata: {},
91 + contacts: Contacts.slice(0, 30)
92 + }
93 + },
94 + computed: {
95 + contactsFiltered() {
96 + return this.contacts.filter(
97 + ({ full_name, email, phone }) =>
98 + (full_name + email + phone).toString().toLowerCase().indexOf(this.search.toString().toLowerCase()) !== -1
99 + )
100 + },
101 + contactsClass() {
102 + return this.pageWidth >= 870 ? "large" : this.pageWidth >= 760 ? "medium" : "small"
103 + },
104 + contactsFavourite() {
105 + return this.contacts.filter(({ starred }) => starred)
106 + },
107 + agentsFiltered() {
108 + return this.agents.filter(
109 + ({ agent_name, agent_ip, agent_id }) =>
110 + (agent_name + agent_ip + agent_id).toString().toLowerCase().indexOf(this.search.toString().toLowerCase()) !== -1
111 + )
112 + },
113 + agentsFavorite() {
114 + return this.agents.filter(({ critical_asset }) => critical_asset)
115 + }
116 + },
117 + methods: {
118 + openEditAgentsModal(agent) {
119 + this.currentAgent = agent
120 + this.isEditAgentsModalActive = true
121 + },
122 + closeEditAgentsModal() {
123 + this.isEditAgentsModalActive = false
124 + },
125 +
126 + openDialog(data) {
127 + this.userdata = data
128 + this.dialogvisible = true
129 + },
130 + setPageWidth() {
131 + this.pageWidth = document.getElementById("page-contacts").offsetWidth
132 + },
133 + __resizeHanlder: _.throttle(function (e) {
134 + this.setPageWidth()
135 + }, 700),
136 + get_agents() {
137 + const path = "http://localhost:5000/agents"
138 + this.loading = true
139 + resizing: true,
140 + axios
141 + .get(path)
142 + .then(response => {
143 + if (response.data.success && Array.isArray(response.data.agents)) {
144 + this.agents = response.data.agents
145 + } else {
146 + console.error("Received non-array agents: ", response.data)
147 + this.agents = [] // Reset to empty array
148 + }
149 + })
150 + .catch(error => {
151 + console.log(error)
152 + this.loading = false
153 + })
154 + },
155 + sync_agents() {
156 + const path = "http://localhost:5000/sync"
157 + this.loading = true
158 + resizing: true,
159 + axios
160 + .get(path)
161 + .then(response => {
162 + this.sync = response.data
163 + this.loading = false
164 + this.succssMesaage = "Agents Synced Successfully"
165 + this.$message({
166 + message: this.succssMesaage,
167 + type: "success"
168 + })
169 + this.get_agents()
170 + })
171 + .catch(error => {
172 + if (error.response.status === 401) {
173 + this.errorMessage = "Unauthorized"
174 + this.$message({
175 + message: this.errorMessage,
176 + type: "error"
177 + })
178 + } else {
179 + this.$message({
180 + message: "Failed to Sync Agents",
181 + type: "error"
182 + })
183 + }
184 + })
185 + },
186 + makeAgentCritical(agent_id) {
187 + const path = "http://localhost:5000/agents/" + agent_id + "/critical"
188 + axios
189 + .put(path)
190 + .then(response => {
191 + this.loading = false
192 + this.succssMesaage = "Agent Criticality Updated Successfully"
193 + this.$message({
194 + message: this.succssMesaage,
195 + type: "success"
196 + })
197 + this.get_agents()
198 + })
199 + .catch(error => {
200 + if (error.response.status === 401) {
201 + this.errorMessage = "Unauthorized"
202 + this.$message({
203 + message: this.errorMessage,
204 + type: "error"
205 + })
206 + } else {
207 + this.$message({
208 + message: "Failed to Update Agent Criticality",
209 + type: "error"
210 + })
211 + }
212 + })
213 + },
214 + makeAgentUncritical(agent_id) {
215 + const path = "http://localhost:5000/agents/" + agent_id + "/uncritical"
216 + axios
217 + .put(path)
218 + .then(response => {
219 + this.loading = false
220 + this.succssMesaage = "Agent Criticality Updated Successfully"
221 + this.$message({
222 + message: this.succssMesaage,
223 + type: "success"
224 + })
225 + this.get_agents()
226 + })
227 + .catch(error => {
228 + if (error.response.status === 401) {
229 + this.errorMessage = "Unauthorized"
230 + this.$message({
231 + message: this.errorMessage,
232 + type: "error"
233 + })
234 + } else {
235 + this.$message({
236 + message: "Failed to Update Agent Criticality",
237 + type: "error"
238 + })
239 + }
240 + })
241 + }
242 + },
243 + mounted() {
244 + this.setPageWidth()
245 + this.get_agents()
246 + },
247 + watch: {
248 + agentsFiltered(newValue) {
249 + console.log("agentsFiltered:", newValue)
250 + }
251 + },
252 + components: {
253 + ResizeObserver,
254 + UserDialog
255 + }
256 +})
257 +</script>
258 +
259 +<style lang="scss" scoped>
260 +@import "../../../assets/scss/_variables";
261 +
262 +.page-contacts {
263 + height: 100%;
264 + margin: 0 !important;
265 + padding: 20px;
266 + padding-bottom: 10px;
267 + box-sizing: border-box;
268 +
269 + .search-card {
270 + padding: 50px;
271 + max-width: 350px;
272 + //max-height: 320px;
273 + box-sizing: border-box;
274 + margin-bottom: 15px;
275 +
276 + .el-input,
277 + .el-button {
278 + width: 100%;
279 + }
280 +
281 + .contacts-favourites {
282 + margin: 0;
283 + padding: 0;
284 + list-style: none;
285 + overflow: auto;
286 +
287 + li {
288 + list-style: none;
289 + padding: 0;
290 + margin: 0;
291 + margin-right: 10px;
292 + margin-bottom: 10px;
293 + float: left;
294 + cursor: pointer;
295 + background: $background-color;
296 + color: $text-color-primary;
297 + border-radius: 4px;
298 + overflow: hidden;
299 +
300 + &:hover {
301 + color: $text-color-accent;
302 + }
303 +
304 + img {
305 + width: 30px;
306 + height: 30px;
307 + float: left;
308 + }
309 +
310 + span {
311 + line-height: 30px;
312 + padding: 0 10px;
313 + }
314 + }
315 + }
316 + }
317 +
318 + .search-wrap {
319 + margin: 0 auto;
320 + margin-bottom: 10px;
321 + padding: 0px 30px;
322 + box-sizing: border-box;
323 + width: 100%;
324 + max-width: 600px;
325 +
326 + i {
327 + display: inline-block;
328 + width: 22px;
329 + }
330 +
331 + input {
332 + outline: none;
333 + background: transparent;
334 + border: none;
335 + font-size: 15px;
336 + position: relative;
337 + top: -2px;
338 + width: 100%;
339 + padding: 0;
340 + color: $text-color-primary;
341 + }
342 +
343 + .contacts-tot {
344 + margin-right: 20px;
345 + margin-left: 10px;
346 + }
347 +
348 + a {
349 + border-bottom: 1px solid;
350 + text-decoration: none;
351 + color: $text-color-primary;
352 +
353 + &:hover {
354 + opacity: 0.6;
355 + }
356 + }
357 + }
358 +
359 + .contacts-root {
360 + max-height: 100%;
361 + }
362 +
363 + .contacts-list {
364 + //margin: 0 auto;
365 + width: 100%;
366 + max-width: 965px;
367 + padding: 0px 30px;
368 + box-sizing: border-box;
369 +
370 + .contact {
371 + margin: 10px 0;
372 + padding: 5px;
373 + box-sizing: border-box;
374 + cursor: pointer;
375 + transition: all 0.5s 0.25s;
376 +
377 + .star {
378 + .mdi-star {
379 + color: #ffd730;
380 + }
381 + .mdi-star-outline {
382 + opacity: 0.5;
383 + }
384 + }
385 +
386 + .avatar {
387 + width: 60px;
388 + transition: all 0.5s 0.25s;
389 +
390 + img {
391 + border: 1px solid transparentize($text-color-primary, 0.9);
392 + box-sizing: border-box;
393 + width: 50px;
394 + height: 50px;
395 + border-radius: 50%;
396 + transition: all 0.5s 0.25s;
397 + }
398 + }
399 +
400 + .info {
401 + word-break: break-word;
402 +
403 + .name {
404 + //.fullname {}
405 +
406 + .email {
407 + opacity: 0;
408 + line-height: 0;
409 + transition: all 0.5s 0.25s;
410 + }
411 +
412 + .phone {
413 + display: none;
414 + }
415 + }
416 +
417 + //.phone {}
418 + }
419 +
420 + &:hover {
421 + margin: 15px -20px;
422 + padding: 10px;
423 + background-color: lighten($background-color, 20%);
424 + border-radius: 5px;
425 + box-shadow:
426 + 0 8px 16px 0 rgba(40, 40, 90, 0.09),
427 + 0 3px 6px 0 rgba(0, 0, 0, 0.065);
428 +
429 + .avatar {
430 + width: 90px;
431 +
432 + img {
433 + width: 90px;
434 + height: 90px;
435 + }
436 + }
437 +
438 + .info {
439 + .name {
440 + .email {
441 + opacity: 1;
442 + line-height: 1.4;
443 + }
444 + }
445 + }
446 + }
447 + }
448 + }
449 +
450 + .contacts-root {
451 + &.medium {
452 + .search-card {
453 + padding: 20px;
454 + max-width: 260px;
455 + //max-height: 260px;
456 + }
457 + }
458 + &.small {
459 + overflow-y: auto;
460 + display: block;
461 + -webkit-box-orient: vertical;
462 + -webkit-box-direction: normal;
463 + -ms-flex-direction: column;
464 + flex-direction: column;
465 + padding: 5px;
466 +
467 + .search-card {
468 + padding: 20px;
469 + max-width: 100%;
470 + width: 100%;
471 + //max-height: 240px;
472 + flex: none;
473 + -webkit-box-flex: none;
474 + -ms-flex: none;
475 + display: block;
476 + overflow: hidden !important;
477 + }
478 +
479 + .contacts-list {
480 + flex: none;
481 + -webkit-box-flex: none;
482 + -ms-flex: none;
483 + display: block;
484 + overflow: hidden !important;
485 + }
486 + }
487 + }
488 +}
489 +
490 +@media (max-width: 768px) {
491 + .page-contacts {
492 + .search-wrap {
493 + padding: 0;
494 + }
495 + .contacts-list {
496 + padding: 0px;
497 +
498 + .contact {
499 + .avatar {
500 + width: 40px;
501 +
502 + img {
503 + width: 40px;
504 + height: 40px;
505 + }
506 + }
507 +
508 + .info {
509 + .phone {
510 + display: none;
511 + }
512 +
513 + .name {
514 + .phone {
515 + display: block;
516 + }
517 + }
518 + }
519 +
520 + &:hover {
521 + margin: 15px 0px;
522 +
523 + .avatar {
524 + width: 60px;
525 +
526 + img {
527 + width: 60px;
528 + height: 60px;
529 + }
530 + }
531 + }
532 + }
533 + }
534 +
535 + .contacts-root {
536 + &.medium {
537 + .contacts-list {
538 + padding: 0 30px;
539 + }
540 + }
541 + &.small {
542 + .contacts-list {
543 + padding: 8px;
544 + }
545 + }
546 + }
547 + }
548 +}
549 +</style>