From 8fe460a0940263102fbf99f45290ce942875150d Mon Sep 17 00:00:00 2001 From: abugraokkali Date: Wed, 27 Dec 2023 13:24:27 +0000 Subject: [PATCH] feature: Discovery operations (list,create,run,delete) --- db.json | 8 + frontend/src/components/Select/Profile.vue | 74 ++++++++ frontend/src/components/Table/AsyncStore.vue | 8 +- frontend/src/localization/en.json | 52 ++++++ frontend/src/localization/tr.json | 46 +++++ frontend/src/models/Asset.ts | 29 ++-- frontend/src/models/Column.ts | 6 +- frontend/src/models/Discovery.ts | 18 ++ frontend/src/models/Profile.ts | 6 +- frontend/src/router/index.ts | 5 + frontend/src/stores/discovery.ts | 106 ++++++++++++ frontend/src/utils/format-date.ts | 2 +- frontend/src/views/modals/Discovery.vue | 80 +++++++++ frontend/src/views/pages/Discoveries.vue | 167 +++++++++++++++++++ frontend/src/views/pages/Profiles.vue | 3 +- 15 files changed, 585 insertions(+), 25 deletions(-) create mode 100644 frontend/src/components/Select/Profile.vue create mode 100644 frontend/src/models/Discovery.ts create mode 100644 frontend/src/stores/discovery.ts create mode 100644 frontend/src/views/modals/Discovery.vue create mode 100644 frontend/src/views/pages/Discoveries.vue diff --git a/db.json b/db.json index 0562b19..2ead20c 100755 --- a/db.json +++ b/db.json @@ -32,6 +32,14 @@ "url": "#/assets", "key": "assets" }, + { + "name": { + "tr": "Keşifler", + "en": "Discoveries" + }, + "url": "#/discoveries", + "key": "discoveries" + }, { "name": { "tr": "Profiller", diff --git a/frontend/src/components/Select/Profile.vue b/frontend/src/components/Select/Profile.vue new file mode 100644 index 0000000..84095b6 --- /dev/null +++ b/frontend/src/components/Select/Profile.vue @@ -0,0 +1,74 @@ + + + diff --git a/frontend/src/components/Table/AsyncStore.vue b/frontend/src/components/Table/AsyncStore.vue index 2883a4e..07472c9 100755 --- a/frontend/src/components/Table/AsyncStore.vue +++ b/frontend/src/components/Table/AsyncStore.vue @@ -184,16 +184,16 @@ const handlePageChange = (currentPage: any) => { query(currentPage) } -const handleFilterChange = (filters: any) => { +const handleFilterChange = (filter: any) => { loading.value = true - Object.keys(filters).forEach((item) => { + Object.keys(filter).forEach((item) => { filters.value = filters.value.filter((i: IData) => { return i.key != item }) - if (filters[item] && filters[item].length > 0) { + if (filter[item] && filter[item].length > 0) { filters.value.push({ key: item, - value: filters[item], + value: filter[item], }) } }) diff --git a/frontend/src/localization/en.json b/frontend/src/localization/en.json index 41d9101..515189a 100755 --- a/frontend/src/localization/en.json +++ b/frontend/src/localization/en.json @@ -43,6 +43,9 @@ "name": "Name", "updated_at": "Updated At" }, + "select": { + "placeholder": "Type to search profile" + }, "fetch": { "messages": { "error": "An error occurred while fetching profiles." @@ -78,5 +81,54 @@ "error": "An error occurred while deleting profile." } } + }, + "discovery": { + "title": "Discoveries", + "description": "You can start a new discovery or view your previous discoveries here.", + "table": { + "ip_range": "IP Range", + "profile": "Profile", + "discovery_status": "Discovery Status", + "message": "Message", + "updated_at": "Last Scan" + }, + "status": { + "pending": "Pending", + "in_progress": "In Progress", + "done": "Done", + "error": "Error" + }, + "fetch": { + "messages": { + "error": "An error occurred while fetching discoveries." + } + }, + "create": { + "title": "Create Discovery", + "inputs": { + "ip_range": "IP Range", + "profile": "Profile" + }, + "rules": { + "ip_range": "IP Range is required.", + "profile": "Profile is required." + }, + "messages": { + "success": "Discovery created successfully.", + "error": "An error occurred while creating discovery." + } + }, + "run": { + "messages": { + "success": "Scan started successfully.", + "error": "An error occurred while starting scan." + } + }, + "delete": { + "messages": { + "success": "Discovery deleted successfully.", + "error": "An error occurred while deleting discovery." + } + } } } diff --git a/frontend/src/localization/tr.json b/frontend/src/localization/tr.json index 8541f0d..ec9bd57 100755 --- a/frontend/src/localization/tr.json +++ b/frontend/src/localization/tr.json @@ -43,6 +43,9 @@ "name": "Ad", "updated_at": "Güncellenme Tarihi" }, + "select": { + "placeholder": "Profil aramak için yazın" + }, "fetch": { "messages": { "error": "Profiller getirilirken bir hata oluştu." @@ -78,5 +81,48 @@ "error": "Profil silinirken bir hata oluştu." } } + }, + "discovery": { + "title": "Keşifler", + "description": "Burada yeni bir keşif başlatabilir veya önceki keşiflerinizi görüntüleyebilirsiniz.", + "table": { + "ip_range": "IP Aralığı", + "profile": "Profil", + "discovery_status": "Keşif Durumu", + "message": "Mesaj", + "updated_at": "Son Tarama" + }, + "fetch": { + "messages": { + "error": "Keşifler getirilirken bir hata oluştu." + } + }, + "create": { + "title": "Keşif Oluştur", + "inputs": { + "ip_range": "IP Aralığı", + "profile": "Profil" + }, + "rules": { + "ip_range": "IP Aralığı zorunludur.", + "profile": "Profil zorunludur." + }, + "messages": { + "success": "Keşif başarıyla oluşturuldu.", + "error": "Keşif oluşturulurken bir hata oluştu." + } + }, + "run": { + "messages": { + "success": "Tarama başarıyla başlatıldı.", + "error": "Tarama başlatılırken bir hata oluştu." + } + }, + "delete": { + "messages": { + "success": "Keşif başarıyla silindi.", + "error": "Keşif silinirken bir hata oluştu." + } + } } } diff --git a/frontend/src/models/Asset.ts b/frontend/src/models/Asset.ts index 59ed535..0c96962 100644 --- a/frontend/src/models/Asset.ts +++ b/frontend/src/models/Asset.ts @@ -1,15 +1,16 @@ +import type { IDiscovery } from "./Discovery" + export interface IAsset { - id: string - created_at: string - updated_at: string - deleted_at: string - hostname: string - address: string - serial_number: string - vendor: string - model: string - discovery_id: string - discovery: any - packages: any - } - \ No newline at end of file + id: string + created_at: string + updated_at: string + deleted_at: string + hostname: string + address: string + serial_number: string + vendor: string + model: string + discovery_id: string + discovery: IDiscovery + packages: any +} diff --git a/frontend/src/models/Column.ts b/frontend/src/models/Column.ts index c7e0740..876e987 100755 --- a/frontend/src/models/Column.ts +++ b/frontend/src/models/Column.ts @@ -7,13 +7,13 @@ export interface IColumn { ellipsis?: { tooltip: boolean } - render?: (record: any) => JSX.Element + render?: (record: any) => any type?: string options?: string[] - filter?: (value: any) => boolean + filter?: (value: any, row: any) => boolean filterOptions?: any customFilter?: boolean defaultFilterOptionValues?: any - renderExpand?: (record: any) => JSX.Element + renderExpand?: (record: any) => any resizable?: boolean } diff --git a/frontend/src/models/Discovery.ts b/frontend/src/models/Discovery.ts new file mode 100644 index 0000000..325e5e7 --- /dev/null +++ b/frontend/src/models/Discovery.ts @@ -0,0 +1,18 @@ +import type { IProfile } from "./Profile" + +export interface IDiscovery { + id: string + created_at: string + updated_at: string + deleted_at: string + ip_range: string + profile_id: string + profile: IProfile + discovery_status: string + message: string +} + +export interface IDiscoveryCreate { + ip_range: string + profile_id: string | null +} diff --git a/frontend/src/models/Profile.ts b/frontend/src/models/Profile.ts index dc9cd04..724f863 100644 --- a/frontend/src/models/Profile.ts +++ b/frontend/src/models/Profile.ts @@ -1,12 +1,14 @@ +import type { IDiscovery } from "./Discovery" + export interface IProfile { id: string created_at: string updated_at: string - deleted_at: any + deleted_at: string name: string username: string password: string - discoveries: any[] + discoveries: IDiscovery[] } export interface IProfileCreate { diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 0513f30..1dfd648 100755 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -8,6 +8,11 @@ const router = createRouter({ name: "assets", component: () => import("@/views/pages/Assets.vue"), }, + { + path: "/discoveries", + name: "discoveries", + component: () => import("@/views/pages/Discoveries.vue"), + }, { path: "/profiles", name: "profiles", diff --git a/frontend/src/stores/discovery.ts b/frontend/src/stores/discovery.ts new file mode 100644 index 0000000..503cfe6 --- /dev/null +++ b/frontend/src/stores/discovery.ts @@ -0,0 +1,106 @@ +import { defineStore } from "pinia" +import http from "@/utils/http-common" +import { i18n } from "@/utils/i18n" +import type { IFilter } from "@/models/Filter" +import type { IPaginator } from "@/models/Paginator" +import type { IDiscovery, IDiscoveryCreate } from "@/models/Discovery" + +export const useDiscoveryStore = defineStore({ + id: "discovery", + state: () => ({ + filter: {} as IFilter, + discoveries: {} as IPaginator, + }), + getters: { + get: (state) => state.discoveries, + }, + actions: { + async fetch(payload: IFilter = {} as IFilter) { + let q = payload + if (Object.keys(payload).length < 1) { + q = this.filter + } else { + this.filter = q + } + const query = new URLSearchParams(q as Record).toString() + + return http.get(`discoveries/?${query}`).then((res) => { + if (res.status == 200) { + this.discoveries = res.data + } else { + window.$notification.error({ + title: i18n.t("common.error"), + content: i18n.t("discovery.fetch.messages.error"), + duration: 5000, + }) + } + }) + }, + async create(payload: IDiscoveryCreate) { + return http + .post(`discoveries`, { + data: JSON.stringify(payload), + }) + .then((res) => { + if (res.status == 200) { + window.$notification.success({ + title: i18n.t("common.success"), + content: i18n.t("discovery.create.messages.success"), + duration: 3000, + }) + this.fetch() + } else { + window.$notification.error({ + title: i18n.t("common.error"), + content: i18n.t("discovery.create.messages.error"), + duration: 5000, + }) + } + }) + }, + async run(id: string) { + return http.post(`discoveries/${id}`).then((res) => { + if (res.status == 200) { + window.$notification.success({ + title: i18n.t("common.success"), + content: i18n.t("discovery.run.messages.success"), + duration: 3000, + }) + this.fetch() + } else { + window.$notification.error({ + title: i18n.t("common.error"), + content: i18n.t("discovery.run.messages.error"), + duration: 5000, + }) + } + }) + }, + async delete(id: string) { + window.$dialog.warning({ + title: i18n.t("common.warning"), + content: i18n.t("common.are_you_sure"), + positiveText: i18n.t("common.yes"), + negativeText: i18n.t("common.no"), + onPositiveClick: () => { + return http.delete(`discoveries/${id}`).then((res) => { + if (res.status == 200) { + window.$notification.success({ + title: i18n.t("common.success"), + content: i18n.t("discovery.delete.messages.success"), + duration: 3000, + }) + this.fetch() + } else { + window.$notification.error({ + title: i18n.t("common.error"), + content: i18n.t("discovery.delete.messages.error"), + duration: 5000, + }) + } + }) + }, + }) + }, + }, +}) diff --git a/frontend/src/utils/format-date.ts b/frontend/src/utils/format-date.ts index a492b27..ebcb0fd 100755 --- a/frontend/src/utils/format-date.ts +++ b/frontend/src/utils/format-date.ts @@ -1,7 +1,7 @@ import { format } from "date-fns" import { tr, enUS } from "date-fns/locale" -const formatDate = (time: any) => { +const formatDate = (time: string) => { return format(new Date(time), "dd MMMM yyyy HH:mm", { locale: document.documentElement.lang == "tr" ? tr : enUS, }) diff --git a/frontend/src/views/modals/Discovery.vue b/frontend/src/views/modals/Discovery.vue new file mode 100644 index 0000000..9673847 --- /dev/null +++ b/frontend/src/views/modals/Discovery.vue @@ -0,0 +1,80 @@ + + + diff --git a/frontend/src/views/pages/Discoveries.vue b/frontend/src/views/pages/Discoveries.vue new file mode 100644 index 0000000..60eb66c --- /dev/null +++ b/frontend/src/views/pages/Discoveries.vue @@ -0,0 +1,167 @@ + + + diff --git a/frontend/src/views/pages/Profiles.vue b/frontend/src/views/pages/Profiles.vue index f73edf4..0151b40 100644 --- a/frontend/src/views/pages/Profiles.vue +++ b/frontend/src/views/pages/Profiles.vue @@ -7,6 +7,7 @@ import ProfileModal from "@/views/modals/Profile.vue" import { useProfileStore } from "@/stores/profile" import useEmitter from "@/utils/emitter" import type { IColumn } from "@/models/Column" +import type { IProfile } from "@/models/Profile" import Header from "@/components/UIElements/Header.vue" const { t } = useI18n() @@ -37,7 +38,7 @@ const columns: IColumn[] = reactive([ { key: "actions", width: 40, - render: (row: any) => { + render: (row: IProfile) => { return h(DropdownMenu, { options: [ {