mihari 5.2.3 → 5.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/build_frontend.sh +1 -9
- data/frontend/.browserslistrc +3 -0
- data/frontend/.eslintrc.js +33 -0
- data/frontend/.gitignore +25 -0
- data/frontend/README.md +3 -0
- data/frontend/babel.config.js +3 -0
- data/frontend/index.html +21 -0
- data/frontend/jest.config.js +9 -0
- data/frontend/package-lock.json +13216 -0
- data/frontend/package.json +54 -0
- data/frontend/public/favicon.ico +0 -0
- data/frontend/scripts/swagger_doc_to_yaml.rb +23 -0
- data/frontend/src/App.vue +27 -0
- data/frontend/src/api-helper.ts +113 -0
- data/frontend/src/api.ts +105 -0
- data/frontend/src/components/ErrorMessage.vue +32 -0
- data/frontend/src/components/Loading.vue +15 -0
- data/frontend/src/components/Navbar.vue +59 -0
- data/frontend/src/components/Pagination.vue +126 -0
- data/frontend/src/components/alert/Alert.vue +92 -0
- data/frontend/src/components/alert/Alerts.vue +66 -0
- data/frontend/src/components/alert/AlertsWithPagination.vue +91 -0
- data/frontend/src/components/alert/AlertsWrapper.vue +141 -0
- data/frontend/src/components/alert/Form.vue +185 -0
- data/frontend/src/components/artifact/AS.vue +29 -0
- data/frontend/src/components/artifact/Artifact.vue +321 -0
- data/frontend/src/components/artifact/ArtifactTag.vue +70 -0
- data/frontend/src/components/artifact/ArtifactTags.vue +29 -0
- data/frontend/src/components/artifact/ArtifactWrapper.vue +62 -0
- data/frontend/src/components/artifact/CPEs.vue +23 -0
- data/frontend/src/components/artifact/DnsRecords.vue +38 -0
- data/frontend/src/components/artifact/Ports.vue +23 -0
- data/frontend/src/components/artifact/ReverseDnsNames.vue +31 -0
- data/frontend/src/components/artifact/Tags.vue +29 -0
- data/frontend/src/components/artifact/WhoisRecord.vue +49 -0
- data/frontend/src/components/config/Configs.vue +68 -0
- data/frontend/src/components/config/ConfigsWrapper.vue +40 -0
- data/frontend/src/components/link/Link.vue +32 -0
- data/frontend/src/components/link/Links.vue +47 -0
- data/frontend/src/components/rule/EditRule.vue +74 -0
- data/frontend/src/components/rule/EditRuleWrapper.vue +56 -0
- data/frontend/src/components/rule/Form.vue +160 -0
- data/frontend/src/components/rule/InputForm.vue +80 -0
- data/frontend/src/components/rule/NewRule.vue +60 -0
- data/frontend/src/components/rule/Rule.vue +108 -0
- data/frontend/src/components/rule/RuleWrapper.vue +62 -0
- data/frontend/src/components/rule/Rules.vue +88 -0
- data/frontend/src/components/rule/RulesWrapper.vue +130 -0
- data/frontend/src/components/rule/YAML.vue +47 -0
- data/frontend/src/components/tag/Tag.vue +73 -0
- data/frontend/src/components/tag/Tags.vue +37 -0
- data/frontend/src/countries.ts +350 -0
- data/frontend/src/index.ts +23 -0
- data/frontend/src/links/anyrun.ts +19 -0
- data/frontend/src/links/base.ts +14 -0
- data/frontend/src/links/censys.ts +20 -0
- data/frontend/src/links/crtsh.ts +20 -0
- data/frontend/src/links/dnslytics.ts +38 -0
- data/frontend/src/links/greynoise.ts +20 -0
- data/frontend/src/links/index.ts +40 -0
- data/frontend/src/links/intezer.ts +20 -0
- data/frontend/src/links/otx.ts +33 -0
- data/frontend/src/links/securitytrails.ts +38 -0
- data/frontend/src/links/shodan.ts +20 -0
- data/frontend/src/links/urlscan.ts +50 -0
- data/frontend/src/links/virustotal.ts +72 -0
- data/frontend/src/main.ts +11 -0
- data/frontend/src/router/index.ts +57 -0
- data/frontend/src/rule.ts +14 -0
- data/frontend/src/shims-vue.d.ts +6 -0
- data/frontend/src/swagger.yaml +737 -0
- data/frontend/src/types.ts +188 -0
- data/frontend/src/utils.ts +60 -0
- data/frontend/src/views/Alerts.vue +20 -0
- data/frontend/src/views/Artifact.vue +44 -0
- data/frontend/src/views/Configs.vue +20 -0
- data/frontend/src/views/EditRule.vue +44 -0
- data/frontend/src/views/NewRule.vue +26 -0
- data/frontend/src/views/Rule.vue +44 -0
- data/frontend/src/views/Rules.vue +20 -0
- data/frontend/tests/unit/utils.spec.ts +7 -0
- data/frontend/tsconfig.json +40 -0
- data/frontend/vite.config.js +24 -0
- data/lefthook.yml +10 -0
- data/lib/mihari/analyzers/base.rb +22 -5
- data/lib/mihari/analyzers/rule.rb +8 -29
- data/lib/mihari/commands/search.rb +16 -7
- data/lib/mihari/entities/rule.rb +1 -1
- data/lib/mihari/entities/tag.rb +1 -1
- data/lib/mihari/schemas/analyzer.rb +2 -7
- data/lib/mihari/schemas/rule.rb +1 -1
- data/lib/mihari/structs/config.rb +39 -16
- data/lib/mihari/structs/rule.rb +1 -1
- data/lib/mihari/version.rb +1 -1
- data/lib/mihari/web/public/assets/index-ac4e5ffa.js +50 -0
- data/lib/mihari/web/public/index.html +1 -1
- data/mihari.gemspec +5 -5
- metadata +97 -16
- data/.gitmodules +0 -0
- data/.overcommit.yml +0 -12
- data/lib/mihari/web/public/assets/index-cbe1734c.js +0 -50
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mihari-frontend",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"serve": "vite",
|
|
7
|
+
"build": "npx redoc-cli build src/swagger.yaml -o public/redoc-static.html && vite build",
|
|
8
|
+
"test:unit": "jest",
|
|
9
|
+
"lint": "eslint --ext .js,.vue,.ts --ignore-path .gitignore --fix src --fix tests"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@fortawesome/fontawesome-free": "^6.4.0",
|
|
13
|
+
"@vueuse/core": "^10.2.0",
|
|
14
|
+
"@vueuse/router": "^10.2.0",
|
|
15
|
+
"axios": "^1.4.0",
|
|
16
|
+
"bulma": "^0.9.4",
|
|
17
|
+
"bulma-helpers": "^0.4.3",
|
|
18
|
+
"dayjs": "^1.11.8",
|
|
19
|
+
"js-sha256": "^0.9.0",
|
|
20
|
+
"truncate": "^3.0.0",
|
|
21
|
+
"ts-dedent": "^2.2.0",
|
|
22
|
+
"url-parse": "^1.5.10",
|
|
23
|
+
"uuidv4": "^6.2.13",
|
|
24
|
+
"vue": "^3.3.4",
|
|
25
|
+
"vue-concurrency": "4.0.1",
|
|
26
|
+
"vue-json-pretty": "^2.2.4",
|
|
27
|
+
"vue-prism-editor": "^2.0.0-alpha.2",
|
|
28
|
+
"vue-router": "^4.2.2"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/jest": "29.5.2",
|
|
32
|
+
"@types/prismjs": "^1.26.0",
|
|
33
|
+
"@types/url-parse": "^1.4.8",
|
|
34
|
+
"@typescript-eslint/eslint-plugin": "^5.59.11",
|
|
35
|
+
"@typescript-eslint/parser": "^5.59.11",
|
|
36
|
+
"@vitejs/plugin-vue": "^4.2.3",
|
|
37
|
+
"@vue/eslint-config-typescript": "^11.0.3",
|
|
38
|
+
"@vue/test-utils": "2.3.2",
|
|
39
|
+
"@vue/vue3-jest": "^29.2.4",
|
|
40
|
+
"eslint": "^8.43.0",
|
|
41
|
+
"eslint-config-prettier": "^8.8.0",
|
|
42
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
43
|
+
"eslint-plugin-simple-import-sort": "^10.0.0",
|
|
44
|
+
"eslint-plugin-vue": "^9.14.1",
|
|
45
|
+
"handlebars": "^4.7.7",
|
|
46
|
+
"husky": "^8.0.3",
|
|
47
|
+
"prettier": "^2.8.8",
|
|
48
|
+
"redoc": "2.0.0",
|
|
49
|
+
"redoc-cli": "^0.13.21",
|
|
50
|
+
"ts-jest": "^29.1.0",
|
|
51
|
+
"typescript": "~5.1.3",
|
|
52
|
+
"vite": "^4.3.9"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require "http"
|
|
2
|
+
require "json"
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
def recursive_delete(hash, to_remove)
|
|
6
|
+
hash.delete(to_remove)
|
|
7
|
+
hash.each_value do |value|
|
|
8
|
+
recursive_delete(value, to_remove) if value.is_a? Hash
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
res = HTTP.get("http://localhost:9292/api/swagger_doc")
|
|
13
|
+
json = JSON.parse(res.body.to_s)
|
|
14
|
+
|
|
15
|
+
# remove host and operationId because
|
|
16
|
+
# - host: can be varied
|
|
17
|
+
# - operationId: is useless (to me)
|
|
18
|
+
keys_to_remove = ["host", "operationId"]
|
|
19
|
+
keys_to_remove.each do |key|
|
|
20
|
+
recursive_delete json, key
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
puts json.to_yaml
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Navbar></Navbar>
|
|
3
|
+
<section class="section is-medium">
|
|
4
|
+
<div class="container">
|
|
5
|
+
<router-view />
|
|
6
|
+
</div>
|
|
7
|
+
</section>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script lang="ts">
|
|
11
|
+
import { defineComponent } from "vue";
|
|
12
|
+
|
|
13
|
+
import Navbar from "@/components/Navbar.vue";
|
|
14
|
+
|
|
15
|
+
export default defineComponent({
|
|
16
|
+
name: "App",
|
|
17
|
+
components: {
|
|
18
|
+
Navbar,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<style>
|
|
24
|
+
table.is-fullwidth th {
|
|
25
|
+
width: 120px;
|
|
26
|
+
}
|
|
27
|
+
</style>
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Task, useAsyncTask } from "vue-concurrency";
|
|
2
|
+
|
|
3
|
+
import { API } from "@/api";
|
|
4
|
+
import {
|
|
5
|
+
Alerts,
|
|
6
|
+
AlertSearchParams,
|
|
7
|
+
ArtifactWithTags,
|
|
8
|
+
Config,
|
|
9
|
+
CreateRule,
|
|
10
|
+
IPInfo,
|
|
11
|
+
Rule,
|
|
12
|
+
Rules,
|
|
13
|
+
RuleSearchParams,
|
|
14
|
+
UpdateRule,
|
|
15
|
+
} from "@/types";
|
|
16
|
+
|
|
17
|
+
export function generateGetAlertsTask(): Task<Alerts, [AlertSearchParams]> {
|
|
18
|
+
return useAsyncTask<Alerts, [AlertSearchParams]>(async (_signal, params) => {
|
|
19
|
+
return await API.getAlerts(params);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function generateDeleteAlertTask(): Task<void, [string]> {
|
|
24
|
+
return useAsyncTask<void, [string]>(async (_signal, id) => {
|
|
25
|
+
return await API.deleteAlert(id);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function generateGetTagsTask(): Task<string[], []> {
|
|
30
|
+
return useAsyncTask<string[], []>(async () => {
|
|
31
|
+
return await API.getTags();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function generateDeleteTagTask(): Task<void, [string]> {
|
|
36
|
+
return useAsyncTask<void, [string]>(async (_signal, tag) => {
|
|
37
|
+
return await API.deleteTag(tag);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function generateGetRuleSetTask(): Task<string[], []> {
|
|
42
|
+
return useAsyncTask<string[], []>(async () => {
|
|
43
|
+
return await API.getRuleSet();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function generateGetArtifactTask(): Task<ArtifactWithTags, [string]> {
|
|
48
|
+
return useAsyncTask<ArtifactWithTags, [string]>(async (_signal, id) => {
|
|
49
|
+
return await API.getArtifact(id);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function generateDeleteArtifactTask(): Task<void, [string]> {
|
|
54
|
+
return useAsyncTask<void, [string]>(async (_signal, id) => {
|
|
55
|
+
return await API.deleteArtifact(id);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function generateEnrichArtifactTask(): Task<void, [string]> {
|
|
60
|
+
return useAsyncTask<void, [string]>(async (_signal, id) => {
|
|
61
|
+
return await API.enrichArtifact(id);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function generateGetConfigsTask(): Task<Config[], []> {
|
|
66
|
+
return useAsyncTask<Config[], []>(async () => {
|
|
67
|
+
return await API.getConfigs();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function generateGetIPTask(): Task<IPInfo, [string]> {
|
|
72
|
+
return useAsyncTask<IPInfo, [string]>(async (_signal, ipAddress: string) => {
|
|
73
|
+
return await API.getIPInfo(ipAddress);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function generateGetRulesTask(): Task<Rules, [RuleSearchParams]> {
|
|
78
|
+
return useAsyncTask<Rules, [RuleSearchParams]>(
|
|
79
|
+
async (_signal, params: RuleSearchParams) => {
|
|
80
|
+
return await API.getRules(params);
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function generateGetRuleTask(): Task<Rule, [string]> {
|
|
86
|
+
return useAsyncTask<Rule, [string]>(async (_signal, id: string) => {
|
|
87
|
+
return await API.getRule(id);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function generateDeleteRuleTask(): Task<void, [string]> {
|
|
92
|
+
return useAsyncTask<void, [string]>(async (_signal, id: string) => {
|
|
93
|
+
return await API.deleteRule(id);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function generateRunRuleTask(): Task<void, [string]> {
|
|
98
|
+
return useAsyncTask<void, [string]>(async (_signal, id) => {
|
|
99
|
+
return await API.runRule(id);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function generateCreateRuleTask(): Task<Rule, [CreateRule]> {
|
|
104
|
+
return useAsyncTask<Rule, [CreateRule]>(async (_signal, payload) => {
|
|
105
|
+
return await API.createRule(payload);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function generateUpdateRuleTask(): Task<Rule, [UpdateRule]> {
|
|
110
|
+
return useAsyncTask<Rule, [UpdateRule]>(async (_signal, payload) => {
|
|
111
|
+
return await API.updateRule(payload);
|
|
112
|
+
});
|
|
113
|
+
}
|
data/frontend/src/api.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Alerts,
|
|
5
|
+
AlertSearchParams,
|
|
6
|
+
ArtifactWithTags,
|
|
7
|
+
Config,
|
|
8
|
+
CreateRule,
|
|
9
|
+
IPInfo,
|
|
10
|
+
Rule,
|
|
11
|
+
Rules,
|
|
12
|
+
RuleSearchParams,
|
|
13
|
+
RuleSet,
|
|
14
|
+
Tags,
|
|
15
|
+
UpdateRule,
|
|
16
|
+
} from "@/types";
|
|
17
|
+
|
|
18
|
+
const client = axios.create({
|
|
19
|
+
headers: {
|
|
20
|
+
Accept: "application/json",
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const API = {
|
|
25
|
+
async getConfigs(): Promise<Config[]> {
|
|
26
|
+
const res = await client.get<Config[]>("/api/configs");
|
|
27
|
+
return res.data;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
async getAlerts(params: AlertSearchParams): Promise<Alerts> {
|
|
31
|
+
params.page = params.page || 1;
|
|
32
|
+
const res = await client.get<Alerts>("/api/alerts", {
|
|
33
|
+
params: params,
|
|
34
|
+
});
|
|
35
|
+
return res.data;
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
async getTags(): Promise<string[]> {
|
|
39
|
+
const res = await client.get<Tags>("/api/tags");
|
|
40
|
+
return res.data.tags;
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async getRuleSet(): Promise<string[]> {
|
|
44
|
+
const res = await client.get<RuleSet>("/api/rules/ids");
|
|
45
|
+
return res.data.ruleIds;
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
async deleteAlert(id: string): Promise<void> {
|
|
49
|
+
await client.delete(`/api/alerts/${id}`);
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
async getArtifact(id: string): Promise<ArtifactWithTags> {
|
|
53
|
+
const res = await client.get(`/api/artifacts/${id}`);
|
|
54
|
+
return res.data;
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
async enrichArtifact(id: string): Promise<void> {
|
|
58
|
+
await client.get(`/api/artifacts/${id}/enrich`);
|
|
59
|
+
return;
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async deleteArtifact(id: string): Promise<void> {
|
|
63
|
+
await client.delete(`/api/artifacts/${id}`);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async getRules(params: RuleSearchParams): Promise<Rules> {
|
|
67
|
+
params.page = params.page || 1;
|
|
68
|
+
const res = await client.get<Rules>("/api/rules", {
|
|
69
|
+
params: params,
|
|
70
|
+
});
|
|
71
|
+
return res.data;
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
async getRule(id: string): Promise<Rule> {
|
|
75
|
+
const res = await client.get<Rule>(`/api/rules/${id}`);
|
|
76
|
+
return res.data;
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
async runRule(id: string): Promise<void> {
|
|
80
|
+
await client.get<void>(`/api/rules/${id}/run`);
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
async createRule(payload: CreateRule): Promise<Rule> {
|
|
84
|
+
const res = await client.post<Rule>("/api/rules/", payload);
|
|
85
|
+
return res.data;
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
async updateRule(payload: UpdateRule): Promise<Rule> {
|
|
89
|
+
const res = await client.put<Rule>("/api/rules/", payload);
|
|
90
|
+
return res.data;
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
async deleteRule(id: string): Promise<void> {
|
|
94
|
+
await client.delete<void>(`/api/rules/${id}`);
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
async deleteTag(name: string): Promise<void> {
|
|
98
|
+
await client.delete(`/api/tags/${name}`);
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async getIPInfo(ipAddress: string): Promise<IPInfo> {
|
|
102
|
+
const res = await client.get<IPInfo>(`/api/ip_addresses/${ipAddress}`);
|
|
103
|
+
return res.data;
|
|
104
|
+
},
|
|
105
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="notification is-danger is-light">
|
|
3
|
+
<p v-if="error.response.data?.message">{{ error.response.data.message }}</p>
|
|
4
|
+
<p v-else>{{ error }}</p>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<article class="message" v-if="error.response.data?.details">
|
|
8
|
+
<div class="message-body">
|
|
9
|
+
<VueJsonPretty :data="error.response.data.details"></VueJsonPretty>
|
|
10
|
+
</div>
|
|
11
|
+
</article>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script lang="ts">
|
|
15
|
+
import "vue-json-pretty/lib/styles.css";
|
|
16
|
+
|
|
17
|
+
import { defineComponent } from "vue";
|
|
18
|
+
import VueJsonPretty from "vue-json-pretty";
|
|
19
|
+
|
|
20
|
+
export default defineComponent({
|
|
21
|
+
name: "ErrorItem",
|
|
22
|
+
props: {
|
|
23
|
+
error: {
|
|
24
|
+
type: Object,
|
|
25
|
+
required: true,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
components: {
|
|
29
|
+
VueJsonPretty,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
</script>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="has-text-centered">
|
|
3
|
+
<div class="fa-3x">
|
|
4
|
+
<i class="fas fa-spinner fa-spin"></i>
|
|
5
|
+
</div>
|
|
6
|
+
</div>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script lang="ts">
|
|
10
|
+
import { defineComponent } from "vue";
|
|
11
|
+
|
|
12
|
+
export default defineComponent({
|
|
13
|
+
name: "LoadingItem",
|
|
14
|
+
});
|
|
15
|
+
</script>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<nav
|
|
3
|
+
role="navigation"
|
|
4
|
+
aria-label="main navigation"
|
|
5
|
+
class="navbar is-fixed-top"
|
|
6
|
+
>
|
|
7
|
+
<div class="navbar-brand">
|
|
8
|
+
<a class="navbar-item"><h1 class="title">Mihari</h1></a
|
|
9
|
+
><a role="button" aria-label="menu" class="navbar-burger burger"
|
|
10
|
+
><span aria-hidden="true"></span><span aria-hidden="true"></span
|
|
11
|
+
><span aria-hidden="true"></span
|
|
12
|
+
></a>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="navbar-menu">
|
|
15
|
+
<div class="navbar-start"></div>
|
|
16
|
+
<div class="navbar-end">
|
|
17
|
+
<router-link class="navbar-item" :to="{ name: 'Alerts' }"
|
|
18
|
+
>Alerts</router-link
|
|
19
|
+
>
|
|
20
|
+
<router-link class="navbar-item" :to="{ name: 'NewRule' }"
|
|
21
|
+
>New rule</router-link
|
|
22
|
+
>
|
|
23
|
+
<router-link class="navbar-item" :to="{ name: 'Rules' }"
|
|
24
|
+
>Rules</router-link
|
|
25
|
+
>
|
|
26
|
+
<router-link class="navbar-item" :to="{ name: 'Configs' }"
|
|
27
|
+
>Configs</router-link
|
|
28
|
+
>
|
|
29
|
+
<a class="navbar-item"
|
|
30
|
+
><a href="/redoc-static.html" target="_blank" class="navbar-item"
|
|
31
|
+
>API</a
|
|
32
|
+
></a
|
|
33
|
+
>
|
|
34
|
+
<a class="navbar-item"
|
|
35
|
+
><a
|
|
36
|
+
href="https://github.com/ninoseki/mihari"
|
|
37
|
+
target="_blank"
|
|
38
|
+
class="navbar-item"
|
|
39
|
+
>GitHub</a
|
|
40
|
+
></a
|
|
41
|
+
>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
</nav>
|
|
45
|
+
</template>
|
|
46
|
+
|
|
47
|
+
<script lang="ts">
|
|
48
|
+
import { defineComponent } from "vue";
|
|
49
|
+
|
|
50
|
+
export default defineComponent({
|
|
51
|
+
name: "NavbarItem",
|
|
52
|
+
});
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<style scoped>
|
|
56
|
+
.navbar {
|
|
57
|
+
border-bottom: 1px solid lightgray;
|
|
58
|
+
}
|
|
59
|
+
</style>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<article class="message is-warning" v-if="total === 0">
|
|
3
|
+
<div class="message-body">There is no result to show.</div>
|
|
4
|
+
</article>
|
|
5
|
+
<nav class="pagination" role="navigation" aria-label="pagination" v-else>
|
|
6
|
+
<ul class="pagination-list" v-if="hasOnlyOnePage">
|
|
7
|
+
<li>
|
|
8
|
+
<a class="pagination-link mt-2 is-current" @click="updatePage(1)">1</a>
|
|
9
|
+
</li>
|
|
10
|
+
</ul>
|
|
11
|
+
<ul class="pagination-list" v-else>
|
|
12
|
+
<li v-if="hasPreviousPage && isPreviousPageNotFirst">
|
|
13
|
+
<a class="pagination-link mt-2" @click="updatePage(1)"> 1</a>
|
|
14
|
+
</li>
|
|
15
|
+
<li v-if="hasPreviousPage && isPreviousPageNotFirst">
|
|
16
|
+
<span class="pagination-ellipsis">…</span>
|
|
17
|
+
</li>
|
|
18
|
+
<li v-if="hasPreviousPage">
|
|
19
|
+
<a class="pagination-link mt-2" @click="updatePage(currentPage - 1)">
|
|
20
|
+
{{ currentPage - 1 }}</a
|
|
21
|
+
>
|
|
22
|
+
</li>
|
|
23
|
+
<li>
|
|
24
|
+
<a
|
|
25
|
+
class="pagination-link mt-2 is-current"
|
|
26
|
+
@click="updatePage(currentPage)"
|
|
27
|
+
>
|
|
28
|
+
{{ currentPage }}</a
|
|
29
|
+
>
|
|
30
|
+
</li>
|
|
31
|
+
<li v-if="hasNextPage">
|
|
32
|
+
<a class="pagination-link mt-2" @click="updatePage(currentPage + 1)">
|
|
33
|
+
{{ currentPage + 1 }}</a
|
|
34
|
+
>
|
|
35
|
+
</li>
|
|
36
|
+
<li v-if="hasNextPage && isNextPageNotLast">
|
|
37
|
+
<span class="pagination-ellipsis">…</span>
|
|
38
|
+
</li>
|
|
39
|
+
<li v-if="hasNextPage && isNextPageNotLast">
|
|
40
|
+
<a class="pagination-link mt-2" @click="updatePage(totalPageCount)">{{
|
|
41
|
+
totalPageCount
|
|
42
|
+
}}</a>
|
|
43
|
+
</li>
|
|
44
|
+
</ul>
|
|
45
|
+
</nav>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<script lang="ts">
|
|
49
|
+
import { useRouteQuery } from "@vueuse/router";
|
|
50
|
+
import { computed, defineComponent, onMounted, Ref } from "vue";
|
|
51
|
+
import { useRoute, useRouter } from "vue-router";
|
|
52
|
+
|
|
53
|
+
export default defineComponent({
|
|
54
|
+
name: "AlertsPagination",
|
|
55
|
+
props: {
|
|
56
|
+
currentPage: {
|
|
57
|
+
type: Number,
|
|
58
|
+
required: true,
|
|
59
|
+
},
|
|
60
|
+
pageSize: {
|
|
61
|
+
type: Number,
|
|
62
|
+
required: true,
|
|
63
|
+
},
|
|
64
|
+
total: {
|
|
65
|
+
type: Number,
|
|
66
|
+
required: true,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
emits: ["update-page"],
|
|
70
|
+
setup(props, context) {
|
|
71
|
+
const route = useRoute();
|
|
72
|
+
const router = useRouter();
|
|
73
|
+
const options = { route, router };
|
|
74
|
+
|
|
75
|
+
const totalPageCount = computed(() => {
|
|
76
|
+
return Math.ceil(props.total / props.pageSize);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const hasOnlyOnePage = computed(() => {
|
|
80
|
+
return totalPageCount.value === 1;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const hasPreviousPage = computed(() => {
|
|
84
|
+
return props.currentPage > 1;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const isPreviousPageNotFirst = computed(() => {
|
|
88
|
+
return props.currentPage - 1 !== 1;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const hasNextPage = computed(() => {
|
|
92
|
+
return props.currentPage < totalPageCount.value;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const isNextPageNotLast = computed(() => {
|
|
96
|
+
return props.currentPage + 1 !== totalPageCount.value;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const updatePage = (page: number) => {
|
|
100
|
+
const pageQuery = useRouteQuery("page", page.toString(), options);
|
|
101
|
+
pageQuery.value = page.toString();
|
|
102
|
+
|
|
103
|
+
context.emit("update-page", page);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
onMounted(() => {
|
|
107
|
+
const pageQuery = useRouteQuery("page", null, options) as Ref<
|
|
108
|
+
string | null
|
|
109
|
+
>;
|
|
110
|
+
if (pageQuery.value && parseInt(pageQuery.value) !== props.currentPage) {
|
|
111
|
+
updatePage(parseInt(pageQuery.value));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
updatePage,
|
|
117
|
+
hasNextPage,
|
|
118
|
+
hasOnlyOnePage,
|
|
119
|
+
hasPreviousPage,
|
|
120
|
+
isNextPageNotLast,
|
|
121
|
+
isPreviousPageNotFirst,
|
|
122
|
+
totalPageCount,
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
</script>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="box">
|
|
3
|
+
<table class="table is-fullwidth is-completely-borderless">
|
|
4
|
+
<tr>
|
|
5
|
+
<th>ID</th>
|
|
6
|
+
<td>
|
|
7
|
+
{{ alert.id }}
|
|
8
|
+
<button
|
|
9
|
+
class="button is-light is-small is-pulled-right"
|
|
10
|
+
@click="deleteAlert"
|
|
11
|
+
>
|
|
12
|
+
<span>Delete</span>
|
|
13
|
+
<span class="icon is-small">
|
|
14
|
+
<i class="fas fa-times"></i>
|
|
15
|
+
</span>
|
|
16
|
+
</button>
|
|
17
|
+
</td>
|
|
18
|
+
</tr>
|
|
19
|
+
<tr>
|
|
20
|
+
<th>Rule</th>
|
|
21
|
+
<td>
|
|
22
|
+
<router-link :to="{ name: 'Rule', params: { id: alert.ruleId } }">{{
|
|
23
|
+
alert.ruleId
|
|
24
|
+
}}</router-link>
|
|
25
|
+
</td>
|
|
26
|
+
</tr>
|
|
27
|
+
<tr>
|
|
28
|
+
<th>Artifacts</th>
|
|
29
|
+
<td>
|
|
30
|
+
<Artifacts :artifacts="alert.artifacts"></Artifacts>
|
|
31
|
+
</td>
|
|
32
|
+
</tr>
|
|
33
|
+
<tr v-if="alert.tags.length > 0">
|
|
34
|
+
<th>Tags</th>
|
|
35
|
+
<td>
|
|
36
|
+
<Tags :tags="alert.tags" @update-tag="updateTag"></Tags>
|
|
37
|
+
</td>
|
|
38
|
+
</tr>
|
|
39
|
+
</table>
|
|
40
|
+
<p class="help">Created at: {{ alert.createdAt }}</p>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<script lang="ts">
|
|
45
|
+
import { defineComponent, PropType } from "vue";
|
|
46
|
+
|
|
47
|
+
import { generateDeleteAlertTask } from "@/api-helper";
|
|
48
|
+
import Artifacts from "@/components/artifact/ArtifactTags.vue";
|
|
49
|
+
import Tags from "@/components/tag/Tags.vue";
|
|
50
|
+
import { Alert } from "@/types";
|
|
51
|
+
import { getHumanizedRelativeTime, getLocalDatetime } from "@/utils";
|
|
52
|
+
|
|
53
|
+
export default defineComponent({
|
|
54
|
+
name: "AlertItem",
|
|
55
|
+
components: {
|
|
56
|
+
Artifacts,
|
|
57
|
+
Tags,
|
|
58
|
+
},
|
|
59
|
+
props: {
|
|
60
|
+
alert: {
|
|
61
|
+
type: Object as PropType<Alert>,
|
|
62
|
+
required: true,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
setup(props, context) {
|
|
66
|
+
const updateTag = (tag: string) => {
|
|
67
|
+
context.emit("update-tag", tag);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const deleteAlertTask = generateDeleteAlertTask();
|
|
71
|
+
|
|
72
|
+
const deleteAlert = async () => {
|
|
73
|
+
const result = window.confirm(
|
|
74
|
+
`Are you sure you want to delete ${props.alert.id}?`
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (result) {
|
|
78
|
+
await deleteAlertTask.perform(props.alert.id);
|
|
79
|
+
// refresh the page
|
|
80
|
+
context.emit("refresh-page");
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
updateTag,
|
|
86
|
+
deleteAlert,
|
|
87
|
+
getLocalDatetime,
|
|
88
|
+
getHumanizedRelativeTime,
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
</script>
|