├── .prettierignore ├── .dockerignore ├── src ├── assets │ ├── logo-blue.png │ ├── logo-dark.png │ ├── logo-white.png │ ├── icons │ │ ├── 192x192.png │ │ ├── 512x512.png │ │ ├── favicon.ico │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── mstile-150x150.png │ │ ├── apple-touch-icon.png │ │ └── browserconfig.xml │ ├── intel_owl_negative.png │ ├── intel_owl_positive.png │ └── manifest.json ├── app │ ├── pages │ │ ├── plugins │ │ │ ├── connectors-management │ │ │ │ ├── tabs │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── connectors-cards.component.ts │ │ │ │ │ └── connectors-table.component.ts │ │ │ │ └── connectors-management.ts │ │ │ ├── analyzers-management │ │ │ │ ├── tabs │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── analyzers-cards.component.ts │ │ │ │ │ ├── analyzers-tree.component.ts │ │ │ │ │ └── analyzers-table.component.ts │ │ │ │ └── analyzers-management.component.ts │ │ │ ├── plugins-routing.module.ts │ │ │ ├── plugins.module.ts │ │ │ └── lib │ │ │ │ └── components.ts │ │ ├── dashboard │ │ │ ├── job-result │ │ │ │ ├── job-actions-menu │ │ │ │ │ ├── job-actions-menu.component.scss │ │ │ │ │ ├── job-actions-menu.component.html │ │ │ │ │ └── job-actions-menu.component.ts │ │ │ │ ├── job-info-list │ │ │ │ │ ├── job-info-list.component.scss │ │ │ │ │ ├── job-info-list.component.ts │ │ │ │ │ └── job-info-list.component.html │ │ │ │ ├── job-result.component.scss │ │ │ │ └── job-result.component.html │ │ │ ├── dashboard.component.scss │ │ │ ├── dashboard.module.ts │ │ │ ├── dashboard.component.html │ │ │ └── dashboard.component.ts │ │ ├── scans │ │ │ ├── scans-management │ │ │ │ ├── scans-management.component.html │ │ │ │ ├── scans-management.component.ts │ │ │ │ ├── scan-file │ │ │ │ │ ├── scan-file.component.ts │ │ │ │ │ └── scan-file.component.html │ │ │ │ ├── scan-observable │ │ │ │ │ ├── scan-observable.component.ts │ │ │ │ │ └── scan-observable.component.html │ │ │ │ └── base-scan.component.ts │ │ │ ├── scans-routing.module.ts │ │ │ ├── scans.module.ts │ │ │ └── lib │ │ │ │ └── edit-config-params-dialog.component.ts │ │ ├── pages.component.scss │ │ ├── pages.module.ts │ │ ├── pages-routing.module.ts │ │ └── pages.component.ts │ ├── @theme │ │ ├── pipes │ │ │ ├── index.ts │ │ │ ├── number-with-commas.pipe.ts │ │ │ ├── capitalize.pipe.ts │ │ │ ├── timing.pipe.ts │ │ │ └── markdown.pipe.ts │ │ ├── layouts │ │ │ ├── index.ts │ │ │ ├── one-column │ │ │ │ ├── one-column.layout.scss │ │ │ │ └── one-column.layout.ts │ │ │ ├── two-columns │ │ │ │ ├── two-columns.layout.scss │ │ │ │ └── two-columns.layout.ts │ │ │ └── three-columns │ │ │ │ ├── three-columns.layout.scss │ │ │ │ └── three-columns.layout.ts │ │ ├── styles │ │ │ ├── _layout.scss │ │ │ ├── pace.theme.scss │ │ │ ├── theme.dark.ts │ │ │ ├── theme.default.ts │ │ │ ├── styles.scss │ │ │ ├── _overrides.scss │ │ │ └── themes.scss │ │ ├── components │ │ │ ├── social-links │ │ │ │ ├── social-links.component.scss │ │ │ │ ├── social-links.component.ts │ │ │ │ └── social-links.component.html │ │ │ ├── header │ │ │ │ ├── theme-switcher │ │ │ │ │ ├── theme-switcher.component.html │ │ │ │ │ └── theme-switcher.component.ts │ │ │ │ ├── header.component.html │ │ │ │ ├── header.component.scss │ │ │ │ └── header.component.ts │ │ │ ├── app-json-editor │ │ │ │ ├── app-json-editor.component.scss │ │ │ │ ├── app-json-editor.component.html │ │ │ │ └── app-json-editor.component.ts │ │ │ ├── footer │ │ │ │ ├── footer.component.ts │ │ │ │ └── footer.component.scss │ │ │ ├── echarts-pie │ │ │ │ ├── echarts-pie.component.html │ │ │ │ └── echarts-pie.component.ts │ │ │ ├── image-visualizer │ │ │ │ └── image-visualizer.component.ts │ │ │ ├── ngx-tagger │ │ │ │ ├── ngx-tagger.component.scss │ │ │ │ ├── ngx-tagger.component.ts │ │ │ │ └── ngx-tagger.component.html │ │ │ └── echarts-tree │ │ │ │ └── echarts-tree.component.ts │ │ ├── directives │ │ │ └── app-debounce-click.directive.ts │ │ └── theme.module.ts │ ├── @core │ │ ├── module-import-guard.ts │ │ ├── auth │ │ │ ├── auth-management.component.ts │ │ │ ├── auth-routing.module.ts │ │ │ ├── auth.module.ts │ │ │ └── login │ │ │ │ ├── login.component.scss │ │ │ │ ├── login.component.ts │ │ │ │ └── login.component.html │ │ ├── services │ │ │ ├── dexie.service.ts │ │ │ ├── auth-gaurd.service.ts │ │ │ ├── user.service.ts │ │ │ ├── toast.service.ts │ │ │ ├── connector-config.service.ts │ │ │ ├── indexdb.service.ts │ │ │ ├── tag.service.ts │ │ │ ├── analyzer-config.service.ts │ │ │ ├── http.intercepter.ts │ │ │ ├── plugin.service.ts │ │ │ ├── auth.service.ts │ │ │ ├── job.service.ts │ │ │ ├── http.service.ts │ │ │ └── scan.service.ts │ │ ├── core.module.ts │ │ └── models │ │ │ └── models.ts │ ├── app.component.ts │ ├── app-routing.module.ts │ └── app.module.ts ├── typings.d.ts ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── tsconfig.spec.json ├── main.ts ├── test.ts ├── polyfills.ts └── index.html ├── proxy.conf.json ├── .travis.yml ├── e2e ├── tsconfig.json ├── src │ ├── app.po.ts │ └── app.e2e-spec.ts └── protractor.conf.js ├── Dockerfile ├── tsconfig.spec.json ├── tsconfig.app.json ├── tsconfig.json ├── .browserslistrc ├── tsconfig.base.json ├── LICENSE-akveo ├── karma.conf.js ├── .gitignore ├── tslint.json ├── package.json ├── angular.json └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | *.json 2 | *.md 3 | *.yml 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .git 4 | .gitignore 5 | proxy.conf.json 6 | .travis.yml -------------------------------------------------------------------------------- /src/assets/logo-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intelowlproject/IntelOwl-ng/HEAD/src/assets/logo-blue.png -------------------------------------------------------------------------------- /src/assets/logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intelowlproject/IntelOwl-ng/HEAD/src/assets/logo-dark.png -------------------------------------------------------------------------------- /src/assets/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intelowlproject/IntelOwl-ng/HEAD/src/assets/logo-white.png -------------------------------------------------------------------------------- /proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": { 3 | "target": "http://localhost:80", 4 | "secure": false 5 | } 6 | } -------------------------------------------------------------------------------- /src/assets/icons/192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intelowlproject/IntelOwl-ng/HEAD/src/assets/icons/192x192.png -------------------------------------------------------------------------------- /src/assets/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intelowlproject/IntelOwl-ng/HEAD/src/assets/icons/512x512.png -------------------------------------------------------------------------------- /src/assets/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intelowlproject/IntelOwl-ng/HEAD/src/assets/icons/favicon.ico -------------------------------------------------------------------------------- /src/assets/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intelowlproject/IntelOwl-ng/HEAD/src/assets/icons/favicon-16x16.png -------------------------------------------------------------------------------- /src/assets/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intelowlproject/IntelOwl-ng/HEAD/src/assets/icons/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intelowlproject/IntelOwl-ng/HEAD/src/assets/icons/mstile-150x150.png -------------------------------------------------------------------------------- /src/assets/intel_owl_negative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intelowlproject/IntelOwl-ng/HEAD/src/assets/intel_owl_negative.png -------------------------------------------------------------------------------- /src/assets/intel_owl_positive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intelowlproject/IntelOwl-ng/HEAD/src/assets/intel_owl_positive.png -------------------------------------------------------------------------------- /src/assets/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intelowlproject/IntelOwl-ng/HEAD/src/assets/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /src/app/pages/plugins/connectors-management/tabs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './connectors-table.component'; 2 | export * from './connectors-cards.component'; 3 | -------------------------------------------------------------------------------- /src/app/@theme/pipes/index.ts: -------------------------------------------------------------------------------- 1 | export * from './capitalize.pipe'; 2 | export * from './markdown.pipe'; 3 | export * from './timing.pipe'; 4 | export * from './number-with-commas.pipe'; 5 | -------------------------------------------------------------------------------- /src/app/@theme/layouts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './one-column/one-column.layout'; 2 | export * from './two-columns/two-columns.layout'; 3 | export * from './three-columns/three-columns.layout'; 4 | -------------------------------------------------------------------------------- /src/app/@theme/styles/_layout.scss: -------------------------------------------------------------------------------- 1 | @mixin ngx-layout() { 2 | @include media-breakpoint-down(is) { 3 | .row { 4 | margin-left: -10px; 5 | margin-right: -10px; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/app/pages/plugins/analyzers-management/tabs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './analyzers-table.component'; 2 | export * from './analyzers-tree.component'; 3 | export * from './analyzers-cards.component'; 4 | -------------------------------------------------------------------------------- /src/app/pages/dashboard/job-result/job-actions-menu/job-actions-menu.component.scss: -------------------------------------------------------------------------------- 1 | .dl-btn { 2 | margin-right: 6.5px; 3 | } 4 | 5 | .dl-btn, 6 | .del-btn { 7 | cursor: pointer; 8 | color: inherit; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/pages/dashboard/job-result/job-info-list/job-info-list.component.scss: -------------------------------------------------------------------------------- 1 | nb-list-item { 2 | padding: 0.5rem; 3 | margin: 0 !important; 4 | display: flex; 5 | align-items: center !important; 6 | justify-content: space-between; 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: false 3 | 4 | language: node_js 5 | node_js: 6 | - "12.18.0" 7 | 8 | cache: 9 | directories: 10 | - ./node_modules 11 | 12 | install: 13 | - npm install 14 | 15 | script: 16 | - npm run lint 17 | - npm run prettier:check -------------------------------------------------------------------------------- /src/app/@core/module-import-guard.ts: -------------------------------------------------------------------------------- 1 | export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) { 2 | if (parentModule) { 3 | throw new Error( 4 | `${moduleName} has already been loaded. Import Core modules in the AppModule only.` 5 | ); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es2018", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/@theme/components/social-links/social-links.component.scss: -------------------------------------------------------------------------------- 1 | .links { 2 | text-align: center; 3 | 4 | .socials a { 5 | margin: 0 1rem; 6 | text-decoration: none; 7 | vertical-align: middle; 8 | 9 | &.with-icon { 10 | font-size: 2rem; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/@theme/components/header/theme-switcher/theme-switcher.component.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/@theme/pipes/number-with-commas.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ name: 'ngxNumberWithCommas' }) 4 | export class NumberWithCommasPipe implements PipeTransform { 5 | transform(input: number): string { 6 | return new Intl.NumberFormat().format(input); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/assets/icons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Akveo. All Rights Reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | */ 6 | 7 | /* SystemJS module definition */ 8 | declare var module: NodeModule; 9 | interface NodeModule { 10 | id: string; 11 | } 12 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Akveo. All Rights Reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | */ 6 | export const environment = { 7 | production: true, 8 | api: '/api/', 9 | intel_owl_version: 'v2.x.x', 10 | }; 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Stage 1: Build our angular application 2 | FROM node:lts-alpine3.11 AS build-artifacts 3 | WORKDIR /usr/src/app 4 | COPY . ./ 5 | RUN yarn install 6 | RUN yarn build:prod 7 | 8 | # Stage 2: copy only dist/ to the scratch base image 9 | FROM scratch 10 | COPY --from=build-artifacts /usr/src/app/dist /usr/src/app/dist 11 | -------------------------------------------------------------------------------- /src/app/@core/auth/auth-management.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 | 6 | 7 | 8 | `, 9 | }) 10 | export class AuthManagementComponent { 11 | constructor() {} 12 | } 13 | -------------------------------------------------------------------------------- /src/app/@theme/layouts/one-column/one-column.layout.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/themes"; 2 | @import "~bootstrap/scss/mixins/breakpoints"; 3 | @import "~@nebular/theme/styles/global/breakpoints"; 4 | 5 | @include nb-install-component() { 6 | .menu-sidebar ::ng-deep .scrollable { 7 | padding-top: nb-theme(layout-padding-top); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/@theme/layouts/two-columns/two-columns.layout.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/themes"; 2 | @import "~bootstrap/scss/mixins/breakpoints"; 3 | @import "~@nebular/theme/styles/global/breakpoints"; 4 | 5 | @include nb-install-component() { 6 | .menu-sidebar ::ng-deep .scrollable { 7 | padding-top: nb-theme(layout-padding-top); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/@theme/layouts/three-columns/three-columns.layout.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/themes"; 2 | @import "~bootstrap/scss/mixins/breakpoints"; 3 | @import "~@nebular/theme/styles/global/breakpoints"; 4 | 5 | @include nb-install-component() { 6 | .menu-sidebar ::ng-deep .scrollable { 7 | padding-top: nb-theme(layout-padding-top); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/app/@theme/pipes/capitalize.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ name: 'ngxCapitalize' }) 4 | export class CapitalizePipe implements PipeTransform { 5 | transform(input: string): string { 6 | return input && input.length 7 | ? input.charAt(0).toUpperCase() + input.slice(1).toLowerCase() 8 | : input; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('ngx-app .content span')).getText() as Promise< 10 | string 11 | >; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Akveo. All Rights Reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | */ 6 | import { Component } from '@angular/core'; 7 | 8 | @Component({ 9 | selector: 'ngx-app', 10 | template: '', 11 | }) 12 | export class AppComponent {} 13 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "./", 6 | "types": [ 7 | "jasmine", 8 | "node" 9 | ] 10 | }, 11 | "files": [ 12 | "test.ts", 13 | "polyfills.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [], 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "exclude": [ 12 | "src/test.ts", 13 | "**/*.spec.ts", 14 | "node_modules/@nebular/**/*.spec.ts" 15 | ], 16 | "include": [ 17 | "src/*.ts", 18 | "src/**/*.ts", 19 | "node_modules/@nebular/**/*.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /src/app/@theme/components/app-json-editor/app-json-editor.component.scss: -------------------------------------------------------------------------------- 1 | :host ::ng-deep json-editor, 2 | :host ::ng-deep json-editor .jsoneditor, 3 | :host ::ng-deep json-editor > div, 4 | :host ::ng-deep json-editor jsoneditor-outer { 5 | height: 300px; 6 | width: 750px; 7 | } 8 | 9 | :host ::ng-deep json-editor jsoneditor-text { 10 | min-height: 300px; 11 | width: 750px; 12 | } 13 | 14 | :host ::ng-deep json-editor { 15 | background-color: #fff !important; 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* 2 | This is a "Solution Style" tsconfig.json file, and is used by editors and TypeScript’s language server to improve development experience. 3 | It is not intended to be used to perform a compilation. 4 | 5 | To learn more about this file see: https://angular.io/config/solution-tsconfig. 6 | */ 7 | { 8 | "files": [], 9 | "references": [ 10 | { 11 | "path": "./tsconfig.app.json" 12 | }, 13 | { 14 | "path": "./tsconfig.spec.json" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not and_qq 10.4 13 | not and_uc 12.12 14 | not baidu 7.12 15 | not kaios 2.5 16 | not op_mini all 17 | not op_mob > 1 -------------------------------------------------------------------------------- /src/app/@theme/components/app-json-editor/app-json-editor.component.html: -------------------------------------------------------------------------------- 1 | 2 | Edit this only if you know what you are doing! 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/app/@theme/styles/pace.theme.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Akveo. All Rights Reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | */ 6 | 7 | @mixin ngx-pace-theme() { 8 | .pace .pace-progress { 9 | background: nb-theme(color-primary-default); 10 | } 11 | 12 | .pace .pace-progress-inner { 13 | box-shadow: 0 0 10px nb-theme(color-primary-default), 14 | 0 0 5px nb-theme(color-primary-default); 15 | } 16 | 17 | .pace .pace-activity { 18 | display: none; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/@theme/components/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'ngx-footer', 5 | styleUrls: ['./footer.component.scss'], 6 | template: ` 7 | 8 | Copyright © {{ currentYear }} 9 | 10 | IntelOwl 13 | 14 | Project Organization. 15 | 16 | `, 17 | }) 18 | export class FooterComponent { 19 | currentYear: number = new Date().getFullYear(); 20 | } 21 | -------------------------------------------------------------------------------- /src/app/pages/dashboard/dashboard.component.scss: -------------------------------------------------------------------------------- 1 | ngx-echarts-pie { 2 | display: block; 3 | width: 100%; 4 | } 5 | 6 | nb-card-header { 7 | display: flex; 8 | align-items: center !important; 9 | justify-content: space-between; 10 | } 11 | 12 | .cursor-pointer { 13 | cursor: pointer; 14 | } 15 | 16 | #sync-btn { 17 | margin-right: 10px; 18 | cursor: pointer; 19 | } 20 | 21 | .rotate { 22 | animation: rotate 1s ease-in-out 0s; 23 | } 24 | 25 | @keyframes rotate { 26 | from { 27 | transform: rotate(0deg); 28 | } 29 | to { 30 | transform: rotate(360deg); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "es2020", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "lib": [ 15 | "es2018", 16 | "dom" 17 | ] 18 | }, 19 | "angularCompilerOptions": { 20 | "fullTemplateTypeCheck": true, 21 | "strictInjectionParameters": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/@theme/pipes/timing.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ name: 'timing' }) 4 | export class TimingPipe implements PipeTransform { 5 | transform(time: number): string { 6 | if (time) { 7 | const minutes = Math.floor(time / 60); 8 | const seconds = Math.floor(time % 60); 9 | return `${this.initZero(minutes)}${minutes}:${this.initZero( 10 | seconds 11 | )}${seconds}`; 12 | } 13 | 14 | return '00:00'; 15 | } 16 | 17 | private initZero(time: number): string { 18 | return time < 10 ? '0' : ''; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Akveo. All Rights Reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | */ 6 | import { enableProdMode } from '@angular/core'; 7 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 8 | 9 | import { AppModule } from './app/app.module'; 10 | import { environment } from './environments/environment'; 11 | 12 | if (environment.production) { 13 | enableProdMode(); 14 | } 15 | 16 | platformBrowserDynamic() 17 | .bootstrapModule(AppModule) 18 | .catch((err) => console.error(err)); 19 | -------------------------------------------------------------------------------- /src/assets/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "IntelOwl", 3 | "short_name": "IntelOwl", 4 | "description": "Analyze files, domains, IPs in multiple ways from a single API at scale. ", 5 | "icons": [ 6 | { 7 | "src": "icons/192x192.png", 8 | "sizes": "192x192", 9 | "type": "image/png" 10 | }, 11 | { 12 | "src": "icons/512x512.png", 13 | "sizes": "512x512", 14 | "type": "image/png" 15 | } 16 | ], 17 | "theme_color": "#ffffff", 18 | "background_color": "#151a30", 19 | "display": "standalone" 20 | } 21 | -------------------------------------------------------------------------------- /src/app/@theme/components/echarts-pie/echarts-pie.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ pieChartName }} 4 | 10 | 11 | 12 | 13 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /src/app/@theme/styles/theme.dark.ts: -------------------------------------------------------------------------------- 1 | import { NbJSThemeOptions, DARK_THEME as baseTheme } from '@nebular/theme'; 2 | 3 | const baseThemeVariables = baseTheme.variables; 4 | 5 | export const DARK_THEME = { 6 | name: 'dark', 7 | base: 'dark', 8 | variables: { 9 | echarts: { 10 | bg: baseThemeVariables.bg, 11 | textColor: baseThemeVariables.fgText, 12 | axisLineColor: baseThemeVariables.fgText, 13 | splitLineColor: baseThemeVariables.separator, 14 | itemHoverShadowColor: 'rgba(0, 0, 0, 0.5)', 15 | tooltipBackgroundColor: baseThemeVariables.primary, 16 | areaOpacity: '0.7', 17 | }, 18 | }, 19 | } as NbJSThemeOptions; 20 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Akveo. All Rights Reserved. 4 | * Licensed under the MIT License. See License.txt in the project root for license information. 5 | */ 6 | // The file contents for the current environment will overwrite these during build. 7 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 8 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 9 | // The list of which env maps to which file can be found in `.angular-cli.json`. 10 | 11 | export const environment = { 12 | production: false, 13 | api: '/api/', 14 | intel_owl_version: 'v2.x.x', 15 | }; 16 | -------------------------------------------------------------------------------- /src/app/@theme/styles/theme.default.ts: -------------------------------------------------------------------------------- 1 | import { NbJSThemeOptions, DEFAULT_THEME as baseTheme } from '@nebular/theme'; 2 | 3 | const baseThemeVariables = baseTheme.variables; 4 | 5 | export const DEFAULT_THEME = { 6 | name: 'default', 7 | base: 'default', 8 | variables: { 9 | echarts: { 10 | bg: baseThemeVariables.bg, 11 | textColor: baseThemeVariables.fgText, 12 | axisLineColor: baseThemeVariables.fgText, 13 | splitLineColor: baseThemeVariables.separator, 14 | itemHoverShadowColor: 'rgba(0, 0, 0, 0.5)', 15 | tooltipBackgroundColor: baseThemeVariables.primary, 16 | areaOpacity: '0.7', 17 | }, 18 | }, 19 | } as NbJSThemeOptions; 20 | -------------------------------------------------------------------------------- /src/app/pages/scans/scans-management/scans-management.component.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 |
11 |
Recent Scans
12 |
13 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /src/app/pages/pages.component.scss: -------------------------------------------------------------------------------- 1 | @import "../@theme/styles/themes"; 2 | 3 | @include nb-install-component() { 4 | ::ng-deep router-outlet + * { 5 | display: block; 6 | animation: fade 1s; 7 | 8 | @keyframes fade { 9 | from { 10 | opacity: 0; 11 | } 12 | 13 | to { 14 | opacity: 1; 15 | } 16 | } 17 | } 18 | } 19 | 20 | :host #goto-top-btn { 21 | position: fixed; /* Fixed/sticky position */ 22 | bottom: 30px; /* Place the button at the bottom of the page */ 23 | right: 40px; /* Place the button 30px from the right */ 24 | z-index: 99; /* Make sure it does not overlap */ 25 | &:focus { 26 | box-shadow: none !important; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/@theme/components/image-visualizer/image-visualizer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'intelowl-image-visualizer', 5 | template: '', 6 | }) 7 | export class ImageVisualizerComponent implements OnInit { 8 | @Input() imageResult: string; 9 | public imgSrc: string; 10 | 11 | constructor() {} 12 | 13 | ngOnInit(): void { 14 | let imgSrc: string = 'data:image/jpg;base64,'; 15 | try { 16 | window.atob(this.imageResult); 17 | imgSrc += this.imageResult; // img is base64 18 | } catch { 19 | imgSrc = this.imageResult; // img is url 20 | } 21 | this.imgSrc = imgSrc; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/app/@core/services/dexie.service.ts: -------------------------------------------------------------------------------- 1 | import Dexie from 'dexie'; 2 | import { Injectable } from '@angular/core'; 3 | 4 | @Injectable() 5 | export class DexieService extends Dexie { 6 | constructor() { 7 | super('IntelOwlAppDatabase'); 8 | 9 | this.version(1).stores({ 10 | recent_scans: '++jobId,status', 11 | /* 12 | tags: '++label,color', 13 | jobs: '++id,source,tags,is_sample,md5,' 14 | + 'observable_name,observable_classification,' 15 | + 'file_name,file_mimetype,status,analyzers_requested,analyzers_to_execute,analysis_reports,received_request_time,' 16 | + 'finished_analysis_time,force_privacy,disable_external_analyzers,errors,file', 17 | */ 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/@theme/components/footer/footer.component.scss: -------------------------------------------------------------------------------- 1 | @import "../../styles/themes"; 2 | @import "~@nebular/theme/styles/global/breakpoints"; 3 | @import "~bootstrap/scss/mixins/breakpoints"; 4 | 5 | @include nb-install-component() { 6 | width: 100%; 7 | display: flex; 8 | justify-content: space-between; 9 | align-items: center; 10 | 11 | .socials { 12 | font-size: 2rem; 13 | 14 | a { 15 | padding: 0.4rem; 16 | color: nb-theme(text-hint-color); 17 | transition: color ease-out 0.1s; 18 | 19 | &:hover { 20 | color: nb-theme(text-basic-color); 21 | } 22 | } 23 | } 24 | 25 | @include media-breakpoint-down(is) { 26 | .socials { 27 | font-size: 1.5rem; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/pages/scans/scans-management/scans-management.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ScanService } from '../../../@core/services/scan.service'; 3 | 4 | @Component({ 5 | templateUrl: './scans-management.component.html', 6 | }) 7 | export class ScansManagementComponent { 8 | tabs = [ 9 | { 10 | title: 'Observable Scan', 11 | route: './observable', 12 | icon: 'search-outline', 13 | responsive: true, 14 | }, 15 | { 16 | title: 'File Scan', 17 | route: './file', 18 | icon: 'file-add-outline', 19 | responsive: true, 20 | }, 21 | ]; 22 | constructor(public readonly scanService: ScanService) {} 23 | public trackByFn = (_index, item) => item.key; 24 | } 25 | -------------------------------------------------------------------------------- /src/app/pages/dashboard/job-result/job-result.component.scss: -------------------------------------------------------------------------------- 1 | // Report Viewer Parent 2 | #selected-row-report { 3 | height: auto; 4 | } 5 | 6 | // Image Viewer 7 | #result-image-viewer { 8 | overflow: scroll; 9 | margin-top: 20px; 10 | padding: 0; 11 | height: 600px; 12 | } 13 | 14 | // JSON Editor 15 | 16 | #result-json-viewer { 17 | margin: 0; 18 | padding: 0; 19 | background-color: #fff !important; 20 | box-shadow: 0 0.5px 2px 0.5px #000; 21 | } 22 | 23 | :host ::ng-deep json-editor, 24 | :host ::ng-deep json-editor .jsoneditor, 25 | :host ::ng-deep json-editor > div, 26 | :host ::ng-deep json-editor jsoneditor-outer { 27 | height: 600px; 28 | } 29 | 30 | :host ::ng-deep json-editor jsoneditor-text { 31 | min-height: 600px; 32 | } 33 | -------------------------------------------------------------------------------- /src/app/@theme/components/social-links/social-links.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'intelowl-social-links', 5 | templateUrl: './social-links.component.html', 6 | styleUrls: ['./social-links.component.scss'], 7 | }) 8 | export class SocialLinksComponent { 9 | socialLinks: any[] = [ 10 | { 11 | url: 'https://github.com/intelowlproject', 12 | target: '_blank', 13 | icon: 'github', 14 | }, 15 | { 16 | url: 'https://gsoc-slack.honeynet.org/', 17 | target: '_blank', 18 | icon: 'message-circle-outline', 19 | }, 20 | { 21 | url: 'https://twitter.com/intel_owl', 22 | target: '_blank', 23 | icon: 'twitter', 24 | }, 25 | ]; 26 | } 27 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('intelowl-ng app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain( 20 | jasmine.objectContaining({ 21 | level: logging.Level.SEVERE, 22 | } as logging.Entry) 23 | ); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/@core/auth/auth-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule, Routes } from '@angular/router'; 2 | import { NgModule } from '@angular/core'; 3 | import { LoginComponent } from './login/login.component'; 4 | import { AuthManagementComponent } from './auth-management.component'; 5 | 6 | const routes: Routes = [ 7 | { 8 | path: '', 9 | component: AuthManagementComponent, 10 | children: [ 11 | { 12 | path: '', 13 | pathMatch: 'full', 14 | redirectTo: 'login', 15 | }, 16 | { 17 | path: 'login', 18 | component: LoginComponent, 19 | }, 20 | ], 21 | }, 22 | ]; 23 | 24 | @NgModule({ 25 | imports: [RouterModule.forChild(routes)], 26 | exports: [RouterModule], 27 | }) 28 | export class AuthRoutingModule {} 29 | -------------------------------------------------------------------------------- /src/app/@theme/pipes/markdown.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { DomSanitizer } from '@angular/platform-browser'; 3 | import marked from 'marked'; 4 | 5 | // constants 6 | const mdRenderer = new marked.Renderer(); 7 | mdRenderer.link = (href, title, text) => 8 | `${text}`; 9 | 10 | @Pipe({ 11 | name: 'markdown', 12 | pure: true, 13 | }) 14 | export class MarkdownPipe implements PipeTransform { 15 | constructor(private sanitized: DomSanitizer) {} 16 | 17 | transform(value: string): any { 18 | const html = marked.parseInline(value, { renderer: mdRenderer }); 19 | const output = this.sanitized.bypassSecurityTrustHtml(html); 20 | return output; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/pages/pages.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { NbButtonModule, NbMenuModule } from '@nebular/theme'; 3 | 4 | import { ThemeModule } from '../@theme/theme.module'; 5 | import { PagesComponent } from './pages.component'; 6 | import { DashboardModule } from './dashboard/dashboard.module'; 7 | import { ScansModule } from './scans/scans.module'; 8 | import { PluginsModule } from './plugins/plugins.module'; 9 | 10 | import { PagesRoutingModule } from './pages-routing.module'; 11 | 12 | @NgModule({ 13 | imports: [ 14 | NbMenuModule, 15 | NbButtonModule, 16 | PagesRoutingModule, 17 | ThemeModule, 18 | DashboardModule, 19 | PluginsModule, 20 | ScansModule, 21 | ], 22 | declarations: [PagesComponent], 23 | }) 24 | export class PagesModule {} 25 | -------------------------------------------------------------------------------- /src/app/@theme/components/social-links/social-links.component.html: -------------------------------------------------------------------------------- 1 | 25 | -------------------------------------------------------------------------------- /src/app/@theme/components/app-json-editor/app-json-editor.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ViewChild } from '@angular/core'; 2 | import { JsonEditorOptions, JsonEditorComponent } from 'ang-jsoneditor'; 3 | import { NbDialogRef } from '@nebular/theme'; 4 | 5 | @Component({ 6 | selector: 'app-json-editor', 7 | templateUrl: './app-json-editor.component.html', 8 | styleUrls: ['./app-json-editor.component.scss'], 9 | }) 10 | export class AppJsonEditorComponent { 11 | @ViewChild(JsonEditorComponent, { static: false }) 12 | editor: JsonEditorComponent; 13 | 14 | @Input() data: any; 15 | @Input() editorOptions: JsonEditorOptions = new JsonEditorOptions(); 16 | 17 | constructor(protected dialogRef: NbDialogRef) {} 18 | 19 | save() { 20 | this.dialogRef.close(this.editor.get()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/app/@core/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { 4 | NbInputModule, 5 | NbButtonModule, 6 | NbAlertModule, 7 | NbLayoutModule, 8 | } from '@nebular/theme'; 9 | import { LoginComponent } from './login/login.component'; 10 | import { AuthRoutingModule } from './auth-routing.module'; 11 | import { AuthManagementComponent } from './auth-management.component'; 12 | import { ThemeModule } from '../../@theme/theme.module'; 13 | 14 | @NgModule({ 15 | imports: [ 16 | AuthRoutingModule, 17 | FormsModule, 18 | ThemeModule, 19 | NbLayoutModule, 20 | NbInputModule, 21 | NbButtonModule, 22 | NbAlertModule, 23 | ], 24 | declarations: [AuthManagementComponent, LoginComponent], 25 | }) 26 | export class AuthModule {} 27 | -------------------------------------------------------------------------------- /src/app/@theme/layouts/one-column/one-column.layout.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'ngx-one-column-layout', 5 | styleUrls: ['./one-column.layout.scss'], 6 | template: ` 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | `, 25 | }) 26 | export class OneColumnLayoutComponent {} 27 | -------------------------------------------------------------------------------- /src/app/pages/dashboard/job-result/job-info-list/job-info-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { Job } from 'src/app/@core/models/models'; 3 | 4 | @Component({ 5 | selector: 'intelowl-job-info-list', 6 | templateUrl: './job-info-list.component.html', 7 | styleUrls: ['./job-info-list.component.scss'], 8 | }) 9 | export class JobInfoListComponent { 10 | // Current Job Data 11 | @Input() jobObj: Job; 12 | 13 | get runtimeConfiguration(): Record> { 14 | return this.jobObj.analyzer_reports.reduce( 15 | (acc, { name, runtime_configuration: runtimeConf }) => 16 | Object.keys(runtimeConf).length > 0 17 | ? { 18 | ...acc, 19 | [name]: runtimeConf, 20 | } 21 | : acc, 22 | {} 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app/pages/scans/scans-management/scan-file/scan-file.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { IScanForm } from '../../../../@core/models/models'; 3 | 4 | @Component({ 5 | templateUrl: './scan-file.component.html', 6 | }) 7 | export class ScanFileComponent { 8 | formData: IScanForm; 9 | 10 | constructor() { 11 | this.formData = { 12 | classification: 'file', 13 | file: null, 14 | file_name: null, 15 | analyzers_requested: [], 16 | connectors_requested: [], 17 | tlp: 'WHITE', 18 | check_existing_or_force: 'check_all', 19 | tags_labels: [], 20 | runtime_configuration: {}, 21 | } as IScanForm; 22 | } 23 | 24 | handleFileInput(files) { 25 | this.formData.file = files.item(0); 26 | this.formData.file_name = (files.item(0).name as string).slice(0, 512); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/@core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ModuleWithProviders, 3 | NgModule, 4 | Optional, 5 | SkipSelf, 6 | } from '@angular/core'; 7 | import { CommonModule } from '@angular/common'; 8 | import { throwIfAlreadyLoaded } from './module-import-guard'; 9 | import { AuthModule } from './auth/auth.module'; 10 | import { AuthService } from './services/auth.service'; 11 | import { UserService } from './services/user.service'; 12 | 13 | @NgModule({ 14 | imports: [CommonModule, AuthModule], 15 | declarations: [], 16 | providers: [AuthService, UserService], 17 | }) 18 | export class CoreModule { 19 | constructor(@Optional() @SkipSelf() parentModule: CoreModule) { 20 | throwIfAlreadyLoaded(parentModule, 'CoreModule'); 21 | } 22 | 23 | static forRoot(): ModuleWithProviders { 24 | return { 25 | ngModule: CoreModule, 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/pages/dashboard/job-result/job-actions-menu/job-actions-menu.component.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 25 | 26 | 33 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting, 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: { 11 | context( 12 | path: string, 13 | deep?: boolean, 14 | filter?: RegExp 15 | ): { 16 | keys(): string[]; 17 | (id: string): T; 18 | }; 19 | }; 20 | 21 | // First, initialize the Angular testing environment. 22 | getTestBed().initTestEnvironment( 23 | BrowserDynamicTestingModule, 24 | platformBrowserDynamicTesting() 25 | ); 26 | // Then we find all the tests. 27 | const context = require.context('./', true, /\.spec\.ts$/); 28 | // And load the modules. 29 | context.keys().map(context); 30 | -------------------------------------------------------------------------------- /src/app/@core/services/auth-gaurd.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate, Router } from '@angular/router'; 3 | import { tap } from 'rxjs/operators'; 4 | import { AuthService } from './auth.service'; 5 | 6 | @Injectable() 7 | export class AuthGuard implements CanActivate { 8 | constructor(private authService: AuthService, private router: Router) {} 9 | 10 | /* canActivate guard to redirect users to login page.. 11 | .. if they are unauthenticated */ 12 | canActivate() { 13 | return this.authService.isAuthenticated().pipe( 14 | tap((authenticated: boolean) => { 15 | if (!authenticated) { 16 | // remove tokens if any and redirect user to login 17 | this.authService.removePayload(); 18 | return this.router.navigate(['auth/login']); 19 | } 20 | return true; 21 | }) 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/@theme/components/header/theme-switcher/theme-switcher.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NbThemeService } from '@nebular/theme'; 3 | 4 | @Component({ 5 | selector: 'theme-switcher', 6 | templateUrl: './theme-switcher.component.html', 7 | }) 8 | export class ThemeSwitcherComponent { 9 | isDarkTheme: boolean; 10 | 11 | constructor(private themeService: NbThemeService) { 12 | this.isDarkTheme = 13 | ThemeSwitcherComponent.getThemeName() === 'dark' ? true : false; 14 | } 15 | 16 | changeTheme(toggleFlag: boolean): void { 17 | let themeName: string; 18 | toggleFlag ? (themeName = 'dark') : (themeName = 'default'); 19 | localStorage.setItem('themeName', themeName); 20 | this.themeService.changeTheme(themeName); 21 | } 22 | 23 | static getThemeName(): string { 24 | return localStorage.getItem('themeName') || 'dark'; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/@theme/layouts/two-columns/two-columns.layout.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'ngx-two-columns-layout', 5 | styleUrls: ['./two-columns.layout.scss'], 6 | template: ` 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | `, 27 | }) 28 | export class TwoColumnsLayoutComponent {} 29 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { ExtraOptions, RouterModule, Routes } from '@angular/router'; 2 | import { NgModule } from '@angular/core'; 3 | import { AuthGuard } from './@core/services/auth-gaurd.service'; 4 | 5 | export const routes: Routes = [ 6 | { 7 | path: 'pages', 8 | canActivate: [AuthGuard], 9 | loadChildren: () => 10 | import('./pages/pages.module').then((m) => m.PagesModule), 11 | }, 12 | { 13 | path: 'auth', 14 | loadChildren: () => 15 | import('./@core/auth/auth.module').then((m) => m.AuthModule), 16 | }, 17 | { path: '', redirectTo: 'pages', pathMatch: 'full' }, 18 | { path: '**', redirectTo: 'pages' }, 19 | ]; 20 | 21 | const config: ExtraOptions = { 22 | useHash: false, 23 | relativeLinkResolution: 'legacy', 24 | }; 25 | 26 | @NgModule({ 27 | imports: [RouterModule.forRoot(routes, config)], 28 | exports: [RouterModule], 29 | }) 30 | export class AppRoutingModule {} 31 | -------------------------------------------------------------------------------- /src/app/pages/scans/scans-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule, Routes } from '@angular/router'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { ScansManagementComponent } from './scans-management/scans-management.component'; 5 | import { ScanObservableComponent } from './scans-management/scan-observable/scan-observable.component'; 6 | import { ScanFileComponent } from './scans-management/scan-file/scan-file.component'; 7 | 8 | const routes: Routes = [ 9 | { 10 | path: '', 11 | component: ScansManagementComponent, 12 | children: [ 13 | { 14 | path: 'observable', 15 | component: ScanObservableComponent, 16 | }, 17 | { 18 | path: 'file', 19 | component: ScanFileComponent, 20 | }, 21 | ], 22 | }, 23 | ]; 24 | 25 | @NgModule({ 26 | imports: [RouterModule.forChild(routes)], 27 | exports: [RouterModule], 28 | }) 29 | export class ScansRoutingModule {} 30 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: ['./src/**/*.e2e-spec.ts'], 13 | capabilities: { 14 | browserName: 'chrome', 15 | }, 16 | directConnect: true, 17 | baseUrl: 'http://localhost:4200/', 18 | framework: 'jasmine', 19 | jasmineNodeOpts: { 20 | showColors: true, 21 | defaultTimeoutInterval: 30000, 22 | print: function () {}, 23 | }, 24 | onPrepare() { 25 | require('ts-node').register({ 26 | project: require('path').join(__dirname, './tsconfig.json'), 27 | }); 28 | jasmine 29 | .getEnv() 30 | .addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /src/app/@theme/styles/styles.scss: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Open+Sans:400,600,700&display=swap"); 2 | 3 | // themes - our custom or/and out of the box themes 4 | @import "themes"; 5 | 6 | // framework component themes (styles tied to theme variables) 7 | @import "~@nebular/theme/styles/globals"; 8 | 9 | @import "~bootstrap/scss/functions"; 10 | @import "~bootstrap/scss/variables"; 11 | @import "~bootstrap/scss/mixins"; 12 | @import "~bootstrap/scss/grid"; 13 | 14 | // loading progress bar theme 15 | @import "./pace.theme"; 16 | 17 | @import "./layout"; 18 | @import "./overrides"; 19 | 20 | // JSON editor 21 | @import "~jsoneditor/dist/jsoneditor.min.css"; 22 | 23 | // install the framework and custom global styles 24 | @include nb-install() { 25 | // framework global styles 26 | @include nb-theme-global(); 27 | 28 | @include ngx-layout(); 29 | // loading progress bar 30 | @include ngx-pace-theme(); 31 | 32 | @include nb-overrides(); 33 | } 34 | -------------------------------------------------------------------------------- /src/app/@theme/layouts/three-columns/three-columns.layout.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'ngx-three-columns-layout', 5 | styleUrls: ['./three-columns.layout.scss'], 6 | template: ` 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | `, 29 | }) 30 | export class ThreeColumnsLayoutComponent {} 31 | -------------------------------------------------------------------------------- /src/app/@core/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, ReplaySubject } from 'rxjs'; 3 | import { AuthService } from './auth.service'; 4 | 5 | @Injectable({ 6 | providedIn: 'root', 7 | }) 8 | export class UserService { 9 | private _user$: ReplaySubject = new ReplaySubject(1) as ReplaySubject< 10 | string 11 | >; 12 | 13 | constructor(private readonly authService: AuthService) { 14 | this.authService.onTokenChange$.subscribe(async (token: string) => { 15 | if (token) { 16 | try { 17 | const username: string = this.authService.getPayload(); 18 | if (username) { 19 | this._user$.next(username); 20 | } 21 | } catch (e) { 22 | console.error(e); 23 | } 24 | } 25 | }); 26 | } 27 | 28 | get user$(): Observable { 29 | return this._user$.asObservable(); 30 | } 31 | 32 | logOut() { 33 | this.authService.logout(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/@theme/directives/app-debounce-click.directive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | EventEmitter, 4 | HostListener, 5 | Input, 6 | OnDestroy, 7 | OnInit, 8 | Output, 9 | } from '@angular/core'; 10 | import { Subject, Subscription } from 'rxjs'; 11 | import { debounceTime } from 'rxjs/operators'; 12 | 13 | @Directive({ 14 | selector: '[appDebounceClick]', 15 | }) 16 | export class DebounceClickDirective implements OnInit, OnDestroy { 17 | @Input() debounceTime = 2000; // 2 seconds 18 | @Output() debounceClick = new EventEmitter(); 19 | private clicks = new Subject(); 20 | private subscription: Subscription; 21 | 22 | constructor() {} 23 | 24 | ngOnInit() { 25 | this.subscription = this.clicks 26 | .pipe(debounceTime(this.debounceTime)) 27 | .subscribe((e) => this.debounceClick.emit(e)); 28 | } 29 | 30 | ngOnDestroy() { 31 | this.subscription.unsubscribe(); 32 | } 33 | 34 | @HostListener('click', ['$event']) 35 | clickEvent(event) { 36 | event.preventDefault(); 37 | event.stopPropagation(); 38 | this.clicks.next(event); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/@theme/components/ngx-tagger/ngx-tagger.component.scss: -------------------------------------------------------------------------------- 1 | nb-card { 2 | width: max-content; 3 | height: max-content; 4 | } 5 | 6 | nb-icon { 7 | cursor: pointer; 8 | } 9 | 10 | nb-icon:hover { 11 | color: #222; 12 | } 13 | 14 | .tag { 15 | display: flex; 16 | align-items: center; 17 | flex-direction: row !important; 18 | } 19 | 20 | span.tag-label { 21 | display: flex; 22 | justify-content: space-between; 23 | align-items: center; 24 | color: #ffffff; 25 | border-radius: 3px; 26 | font-weight: 700; 27 | padding: 5px; 28 | } 29 | 30 | #tag-display-container { 31 | flex-wrap: wrap; 32 | 33 | span.tag-label { 34 | margin: 0 3px; 35 | } 36 | } 37 | 38 | #tags-container { 39 | display: flex; 40 | flex-direction: column !important; 41 | 42 | .tag-selector { 43 | justify-content: space-between; 44 | margin: 5px 0; 45 | cursor: pointer; 46 | 47 | span.tag-label { 48 | min-width: 175px; 49 | min-height: 20px; 50 | margin-right: 3px; 51 | } 52 | 53 | span.tag-label:hover { 54 | filter: brightness(1.4); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/app/pages/scans/scans-management/scan-file/scan-file.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | 6 | 7 | 14 |
15 | 16 |
17 | 18 | 21 | 30 |
31 |
32 |
33 |
34 | -------------------------------------------------------------------------------- /src/app/pages/plugins/connectors-management/connectors-management.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 |
6 | Designed to enrich data with any platform. Here's a list of integrated 7 | connectors. 8 |
9 | 10 | 11 | Hover over a configured icon to view configuration status and errors if 13 | any. 15 | 16 | 17 | 22 | 23 | 24 | `, 25 | }) 26 | export class ConnectorsManagementComponent { 27 | tabs = [ 28 | { 29 | title: 'Table', 30 | route: './table', 31 | icon: 'list', 32 | responsive: true, 33 | }, 34 | { 35 | title: 'Cards', 36 | route: './cards', 37 | icon: 'archive', 38 | responsive: true, 39 | }, 40 | ]; 41 | 42 | constructor() {} 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE-akveo: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 akveo.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma'), 14 | ], 15 | client: { 16 | clearContext: false, // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/test-app'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true, 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true, 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /src/app/@core/services/toast.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { NbToastrService } from '@nebular/theme'; 3 | 4 | @Injectable() 5 | export class ToastService { 6 | private config = { 7 | destroyByClick: true, 8 | duration: 5000, 9 | preventDuplicates: false, 10 | }; 11 | 12 | constructor(private toastrService: NbToastrService) {} 13 | 14 | public showToast(message: string, title: string, status: string) { 15 | if (status === 'error') { 16 | this.toastrService.danger(message.toString(), title, this.config); 17 | } else if (status === 'success') { 18 | this.toastrService.success(message, title, this.config); 19 | } else if (status === 'warning') { 20 | this.toastrService.warning(message, title, this.config); 21 | } else { 22 | this.toastrService.primary(message, title, this.config); 23 | } 24 | } 25 | 26 | public infiniteNotification( 27 | title: string, 28 | message: string, 29 | iconName: string 30 | ) { 31 | this.toastrService.show(message, title, { 32 | duration: 0, 33 | icon: { icon: iconName, pack: 'eva' }, 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/@theme/components/header/header.component.html: -------------------------------------------------------------------------------- 1 |
2 | 19 |
20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 35 | 36 | 37 | 38 |
39 | -------------------------------------------------------------------------------- /src/app/@core/auth/login/login.component.scss: -------------------------------------------------------------------------------- 1 | nb-card { 2 | margin: auto; 3 | height: calc(100vh - 2 * 2.5rem); 4 | } 5 | 6 | nb-card-header { 7 | display: flex; 8 | justify-content: space-between; 9 | } 10 | 11 | nb-card-body { 12 | margin: auto; 13 | width: 40%; 14 | height: 50%; 15 | } 16 | 17 | nb-alert { 18 | width: 100% !important; 19 | } 20 | 21 | ::ng-deep { 22 | /* 23 | .forgot-password { 24 | text-decoration: none; 25 | margin-bottom: 0.5rem; 26 | } 27 | */ 28 | 29 | .title { 30 | margin-top: 0; 31 | margin-bottom: 0.75rem; 32 | text-align: center; 33 | } 34 | 35 | .sub-title { 36 | margin-bottom: 2rem; 37 | text-align: center; 38 | } 39 | 40 | .form-control-group { 41 | margin-bottom: 2rem; 42 | } 43 | 44 | .form-control-group.accept-group { 45 | display: flex; 46 | justify-content: space-between; 47 | margin: 2rem 0; 48 | } 49 | 50 | .another-action { 51 | margin-top: 2rem; 52 | text-align: center; 53 | } 54 | 55 | nb-alert { 56 | .alert-title, 57 | .alert-message { 58 | margin: 0 0 0.5rem; 59 | } 60 | .alert-message-list { 61 | list-style-type: none; 62 | padding: 0; 63 | margin: 0; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/app/pages/pages-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule, Routes } from '@angular/router'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { PagesComponent } from './pages.component'; 5 | import { DashboardComponent } from './dashboard/dashboard.component'; 6 | import { JobResultComponent } from './dashboard/job-result/job-result.component'; 7 | 8 | const routes: Routes = [ 9 | { 10 | path: '', 11 | component: PagesComponent, 12 | children: [ 13 | { 14 | path: '', 15 | redirectTo: 'dashboard', 16 | }, 17 | { 18 | path: 'dashboard', 19 | component: DashboardComponent, 20 | }, 21 | { 22 | path: '', 23 | loadChildren: () => 24 | import('./plugins/plugins.module').then((m) => m.PluginsModule), 25 | }, 26 | { 27 | path: 'scan', 28 | loadChildren: () => 29 | import('./scans/scans.module').then((m) => m.ScansModule), 30 | }, 31 | { 32 | path: 'scan/result/:jobId', 33 | component: JobResultComponent, 34 | }, 35 | ], 36 | }, 37 | ]; 38 | 39 | @NgModule({ 40 | imports: [RouterModule.forChild(routes)], 41 | exports: [RouterModule], 42 | }) 43 | export class PagesRoutingModule {} 44 | -------------------------------------------------------------------------------- /src/app/pages/plugins/analyzers-management/analyzers-management.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | template: ` 5 | 6 |
One API to rule them all. Here's a list of integrated analyzers.
7 | 8 | 9 | Hover over a configured icon to view configuration status and errors if 11 | any. 13 | 14 | 15 | 20 | 21 | 22 | `, 23 | }) 24 | export class AnalyzersManagementComponent { 25 | tabs = [ 26 | { 27 | title: 'Table', 28 | route: './table', 29 | icon: 'list', 30 | responsive: true, 31 | }, 32 | { 33 | title: 'Cards', 34 | route: './cards', 35 | icon: 'archive', 36 | responsive: true, 37 | }, 38 | { 39 | title: 'Tree', 40 | route: './tree', 41 | icon: 'funnel', 42 | responsive: true, 43 | }, 44 | ]; 45 | 46 | constructor() {} 47 | } 48 | -------------------------------------------------------------------------------- /src/app/@theme/styles/_overrides.scss: -------------------------------------------------------------------------------- 1 | @import "./themes"; 2 | 3 | @mixin nb-overrides() { 4 | nb-select.size-medium button { 5 | padding: 0.4375rem 2.2rem 0.4375rem 1.125rem !important; 6 | 7 | nb-icon { 8 | right: 0.41rem !important; 9 | } 10 | } 11 | 12 | nb-card { 13 | border: 0 solid transparent; 14 | box-shadow: nb-theme(card-box-shadow); 15 | } 16 | 17 | a { 18 | text-decoration: none; 19 | } 20 | 21 | a:hover { 22 | text-decoration: underline; 23 | } 24 | 25 | .text-json { 26 | font-size: smaller; 27 | color: nb-theme(text-basic-color); 28 | } 29 | 30 | .cursor-pointer { 31 | cursor: pointer !important; 32 | } 33 | 34 | // alerts 35 | 36 | .caption { 37 | margin-top: 0.5rem; 38 | } 39 | 40 | nb-alert { 41 | width: fit-content; 42 | display: inline-block; 43 | font-size: smaller; 44 | padding: 0.6rem; 45 | } 46 | 47 | nb-alert span { 48 | margin: 0 3px; 49 | } 50 | 51 | .vr { 52 | display: inline; 53 | border-left: 1px inset #151a30; 54 | } 55 | 56 | .plugin-info-card { 57 | max-width: 800px; 58 | background-color: nb-theme(background-basic-color-3); 59 | border-color: nb-theme(border-basic-color-1); 60 | nb-card-header { 61 | border-color: nb-theme(border-basic-color-1); 62 | } 63 | margin-bottom: 0; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/app/pages/plugins/connectors-management/tabs/connectors-cards.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { first } from 'rxjs/operators'; 3 | import { IConnectorConfig } from 'src/app/@core/models/models'; 4 | import { ConnectorConfigService } from '../../../../@core/services/connector-config.service'; 5 | 6 | @Component({ 7 | template: ` 8 | 13 | 14 |
18 | 19 |
20 |
21 |
22 | `, 23 | }) 24 | export class ConnectorsCardsComponent implements OnInit { 25 | showSpinnerBool: boolean = false; 26 | connectorsList: IConnectorConfig[] = []; 27 | 28 | constructor(private readonly connectorService: ConnectorConfigService) {} 29 | 30 | ngOnInit(): void { 31 | this.showSpinnerBool = true; // spinner on 32 | this.connectorService.connectorsList$.pipe(first()).subscribe((res) => { 33 | this.connectorsList = res; 34 | this.showSpinnerBool = false; // spinner off 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/pages/plugins/analyzers-management/tabs/analyzers-cards.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { first } from 'rxjs/operators'; 3 | import { AnalyzerConfigService } from '../../../../@core/services/analyzer-config.service'; 4 | import { IAnalyzerConfig } from 'src/app/@core/models/models'; 5 | 6 | @Component({ 7 | template: ` 8 | 13 | 14 |
18 | 19 |
20 |
21 |
22 | `, 23 | }) 24 | export class AnalyzersCardsComponent implements OnInit { 25 | showSpinnerBool: boolean = false; 26 | analyzersList: IAnalyzerConfig[] = []; 27 | 28 | constructor(private readonly analyzerService: AnalyzerConfigService) {} 29 | 30 | ngOnInit(): void { 31 | this.showSpinnerBool = true; // spinner on 32 | this.analyzerService.analyzersList$.pipe(first()).subscribe((res) => { 33 | this.analyzersList = Object.values( 34 | this.analyzerService.rawAnalyzerConfig 35 | ); 36 | this.showSpinnerBool = false; // spinner off 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/app/pages/plugins/plugins-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule, Routes } from '@angular/router'; 2 | import { NgModule } from '@angular/core'; 3 | import { AnalyzersManagementComponent } from './analyzers-management/analyzers-management.component'; 4 | import { ConnectorsManagementComponent } from './connectors-management/connectors-management'; 5 | import { 6 | AnalyzersTableComponent, 7 | AnalyzersTreeComponent, 8 | AnalyzersCardsComponent, 9 | } from './analyzers-management/tabs'; 10 | import { 11 | ConnectorsTableComponent, 12 | ConnectorsCardsComponent, 13 | } from './connectors-management/tabs'; 14 | 15 | const routes: Routes = [ 16 | { 17 | path: 'analyzers', 18 | component: AnalyzersManagementComponent, 19 | children: [ 20 | { 21 | path: 'table', 22 | component: AnalyzersTableComponent, 23 | }, 24 | { 25 | path: 'tree', 26 | component: AnalyzersTreeComponent, 27 | }, 28 | { 29 | path: 'cards', 30 | component: AnalyzersCardsComponent, 31 | }, 32 | ], 33 | }, 34 | { 35 | path: 'connectors', 36 | component: ConnectorsManagementComponent, 37 | children: [ 38 | { 39 | path: 'table', 40 | component: ConnectorsTableComponent, 41 | }, 42 | { 43 | path: 'cards', 44 | component: ConnectorsCardsComponent, 45 | }, 46 | ], 47 | }, 48 | ]; 49 | 50 | @NgModule({ 51 | imports: [RouterModule.forChild(routes)], 52 | exports: [RouterModule], 53 | }) 54 | export class PluginsRoutingModule {} 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # Nuxt.js build / generate output 76 | dist 77 | 78 | # DynamoDB Local files 79 | .dynamodb/ 80 | 81 | # TernJS port file 82 | .tern-port 83 | 84 | # firebase 85 | 86 | .firebase 87 | firebase.json 88 | .firebaserc 89 | -------------------------------------------------------------------------------- /src/app/@theme/components/header/header.component.scss: -------------------------------------------------------------------------------- 1 | @import "~bootstrap/scss/mixins/breakpoints"; 2 | @import "~@nebular/theme/styles/global/breakpoints"; 3 | @import "../../styles/themes"; 4 | 5 | @include nb-install-component() { 6 | display: flex; 7 | justify-content: space-between; 8 | width: 100%; 9 | 10 | .logo-container { 11 | display: flex; 12 | align-items: center; 13 | width: calc(#{nb-theme(sidebar-width)} - #{nb-theme(header-padding)}); 14 | } 15 | 16 | nb-action { 17 | height: auto; 18 | display: flex; 19 | align-content: center; 20 | } 21 | 22 | .header-container { 23 | display: flex; 24 | align-items: center; 25 | width: auto; 26 | 27 | .sidebar-toggle { 28 | padding-right: 1.25rem; 29 | text-decoration: none; 30 | color: nb-theme(text-hint-color); 31 | nb-icon { 32 | font-size: 1.75rem; 33 | } 34 | } 35 | 36 | .logo { 37 | padding: 0 1.25rem; 38 | font-size: 1.75rem; 39 | white-space: nowrap; 40 | } 41 | 42 | .logo img { 43 | width: 150px !important; 44 | height: auto !important; 45 | } 46 | } 47 | 48 | ::ng-deep .user-action { 49 | nb-user { 50 | .user-name { 51 | font-weight: bold; 52 | } 53 | } 54 | } 55 | 56 | @include media-breakpoint-down(sm) { 57 | .logo-container { 58 | width: fit-content !important; 59 | } 60 | 61 | .header-container { 62 | .logo { 63 | padding: 0 0.3rem; 64 | font-size: 0.5rem; 65 | } 66 | 67 | .logo img { 68 | width: 90px !important; 69 | height: auto !important; 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/app/@core/auth/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { AuthService } from '../../services/auth.service'; 3 | import { Router } from '@angular/router'; 4 | import { ThemeSwitcherComponent } from '../../../@theme/components/header/theme-switcher/theme-switcher.component'; 5 | 6 | @Component({ 7 | templateUrl: './login.component.html', 8 | styleUrls: ['./login.component.scss'], 9 | }) 10 | export class LoginComponent { 11 | user: { username: string; password: string } = { 12 | username: null, 13 | password: null, 14 | }; 15 | 16 | errors: string[] = []; 17 | messages: string[] = []; 18 | submitted: boolean = false; 19 | 20 | isDarkTheme: boolean; 21 | 22 | constructor(private authService: AuthService, private router: Router) { 23 | this.isDarkTheme = 24 | ThemeSwitcherComponent.getThemeName() === 'dark' ? true : false; 25 | } 26 | 27 | async login() { 28 | this.errors = []; 29 | this.messages = []; 30 | this.submitted = true; 31 | 32 | this.authService.login(this.user).then( 33 | () => { 34 | this.messages.push('Login Successful! You will be redirected shortly.'); 35 | setTimeout(() => { 36 | return this.router.navigateByUrl('/'); 37 | }, 1000); 38 | }, 39 | (err: any) => { 40 | console.error(err); 41 | this.submitted = false; 42 | const errMsg: string = ( 43 | err?.error?.error || 44 | err?.error?.non_field_errors || 45 | err?.detail || 46 | err?.message || 47 | JSON.stringify(err) 48 | ).toString(); 49 | this.errors.push(errMsg); 50 | } 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 3 | import { NgModule } from '@angular/core'; 4 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; 5 | import { CoreModule } from './@core/core.module'; 6 | import { ThemeModule } from './@theme/theme.module'; 7 | import { AppComponent } from './app.component'; 8 | import { AppRoutingModule } from './app-routing.module'; 9 | import { NbMenuModule, NbSidebarModule, NbToastrModule } from '@nebular/theme'; 10 | 11 | import { TokenInterceptor } from './@core/services/http.intercepter'; 12 | import { APP_BASE_HREF } from '@angular/common'; 13 | import { AuthGuard } from './@core/services/auth-gaurd.service'; 14 | import { DexieService } from './@core/services/dexie.service'; 15 | import { IndexedDbService } from './@core/services/indexdb.service'; 16 | import { NbEvaIconsModule } from '@nebular/eva-icons'; 17 | import { ToastService } from './@core/services/toast.service'; 18 | 19 | @NgModule({ 20 | declarations: [AppComponent], 21 | imports: [ 22 | BrowserModule, 23 | BrowserAnimationsModule, 24 | HttpClientModule, 25 | AppRoutingModule, 26 | NbEvaIconsModule, 27 | NbSidebarModule.forRoot(), 28 | NbMenuModule.forRoot(), 29 | NbToastrModule.forRoot(), 30 | CoreModule.forRoot(), 31 | ThemeModule.forRoot(), 32 | ], 33 | bootstrap: [AppComponent], 34 | providers: [ 35 | AuthGuard, 36 | ToastService, 37 | DexieService, 38 | IndexedDbService, 39 | { provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true }, 40 | { provide: APP_BASE_HREF, useValue: '/' }, 41 | ], 42 | }) 43 | export class AppModule {} 44 | -------------------------------------------------------------------------------- /src/app/pages/scans/scans-management/scan-observable/scan-observable.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { IScanForm } from '../../../../@core/models/models'; 3 | 4 | @Component({ 5 | templateUrl: './scan-observable.component.html', 6 | }) 7 | export class ScanObservableComponent { 8 | public formData: IScanForm; 9 | public obsPlaceholder: string = '8.8.8.8'; 10 | public obsPattern: string = '[.0-9]*'; 11 | 12 | constructor() { 13 | this.formData = { 14 | classification: 'ip', 15 | observable_name: null, 16 | analyzers_requested: [], 17 | connectors_requested: [], 18 | tlp: 'WHITE', 19 | check_existing_or_force: 'check_all', 20 | tags_labels: [], 21 | runtime_configuration: {}, 22 | } as IScanForm; 23 | } 24 | 25 | onObsClassificationChange(): void { 26 | // Clear selected analyzers on classification change 27 | this.formData.analyzers_requested = []; 28 | 29 | switch (this.formData.classification) { 30 | case 'ip': 31 | this.obsPlaceholder = '8.8.8.8'; 32 | this.obsPattern = '[.0-9]*'; 33 | break; 34 | case 'url': 35 | this.obsPlaceholder = 'http://example.com/'; 36 | this.obsPattern = '(www.|http://|https://).*'; 37 | break; 38 | case 'domain': 39 | this.obsPlaceholder = 'scanme.org'; 40 | this.obsPattern = '^(www)?[.]?[-_a-zA-Z0-9]+([.][a-z0-9]+)+$'; 41 | break; 42 | case 'hash': 43 | this.obsPlaceholder = '446c5fbb11b9ce058450555c1c27153c'; 44 | this.obsPattern = '^[a-zA-Z0-9]{4,}$'; 45 | break; 46 | case 'generic': 47 | this.obsPlaceholder = 'email, phone no., city, country, registry etc.'; 48 | this.obsPattern = '.*'; 49 | break; 50 | default: 51 | break; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/app/pages/pages.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NbMenuItem } from '@nebular/theme'; 3 | 4 | @Component({ 5 | selector: 'ngx-pages', 6 | styleUrls: ['pages.component.scss'], 7 | template: ` 8 | 9 | 10 | 11 | 21 | 22 | `, 23 | }) 24 | export class PagesComponent { 25 | constructor() {} 26 | 27 | menu: NbMenuItem[] = [ 28 | { 29 | title: 'Dashboard', 30 | icon: 'home-outline', 31 | link: '/pages/dashboard', 32 | home: true, 33 | }, 34 | { 35 | title: 'Analyzers Management', 36 | group: true, 37 | }, 38 | { 39 | title: 'View', 40 | icon: 'list-outline', 41 | link: '/pages/analyzers/table', 42 | }, 43 | { 44 | title: 'Connectors Management', 45 | group: true, 46 | }, 47 | { 48 | title: 'View', 49 | icon: 'list-outline', 50 | link: '/pages/connectors/table', 51 | }, 52 | { 53 | title: 'Scans Management', 54 | group: true, 55 | }, 56 | { 57 | title: 'Scan an Observable', 58 | icon: 'search-outline', 59 | link: '/pages/scan/observable', 60 | }, 61 | { 62 | title: 'Scan a File', 63 | icon: 'file-add-outline', 64 | link: '/pages/scan/file', 65 | }, 66 | ]; 67 | 68 | goToTop(): void { 69 | document.getElementsByClassName('layout-container')[0].scrollIntoView({ 70 | behavior: 'smooth', 71 | block: 'start', 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/app/pages/scans/scans-management/scan-observable/scan-observable.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 9 | IP 10 | URL 11 | Domain 12 | Hash 13 | Generic IoC 14 | 15 | 16 |
17 | 20 | 33 | 34 |

35 | {{ formData.classification }} is required. 36 |

37 |

38 | {{ formData.classification }} must be at least 3 characters long. 39 |

40 |

41 | Invalid {{ formData.classification }} 42 |

43 |
44 |
45 |
46 |
47 | -------------------------------------------------------------------------------- /src/app/@core/services/connector-config.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable, ReplaySubject } from 'rxjs'; 4 | import { IConnectorConfig, IRawConnectorConfig } from '../models/models'; 5 | import { PluginService } from './plugin.service'; 6 | import { ToastService } from './toast.service'; 7 | 8 | @Injectable({ 9 | providedIn: 'root', 10 | }) 11 | export class ConnectorConfigService extends PluginService { 12 | private rawConnectorConfig: IRawConnectorConfig = {}; 13 | private _connectorsList$: ReplaySubject< 14 | IConnectorConfig[] 15 | > = new ReplaySubject(1); 16 | 17 | constructor(_httpClient: HttpClient, toastr: ToastService) { 18 | super(_httpClient, toastr); 19 | this.pluginType = 'connector'; 20 | this.init().then(); 21 | } 22 | 23 | get connectorsList$(): Observable { 24 | return this._connectorsList$.asObservable(); 25 | } 26 | 27 | private async init(): Promise { 28 | try { 29 | this.rawConnectorConfig = await this.query({}, 'get_connector_configs'); 30 | this.makeConnectorsList(); 31 | } catch (e) { 32 | console.error(e); 33 | } 34 | } 35 | 36 | async checkConnectorHealth(connectorName: string): Promise { 37 | return this.checkPluginHealth(connectorName); 38 | } 39 | 40 | async killConnector(job_id: number, connectorName: string): Promise { 41 | return this.killPlugin(job_id, connectorName); 42 | } 43 | 44 | async retryConnector( 45 | job_id: number, 46 | connectorName: string 47 | ): Promise { 48 | return this.retryPlugin(job_id, connectorName); 49 | } 50 | 51 | private makeConnectorsList(): void { 52 | const connectors: IConnectorConfig[] = Object.values( 53 | this.rawConnectorConfig 54 | ); 55 | this._connectorsList$.next(connectors); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/app/@theme/components/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { 3 | NbMenuItem, 4 | NbMenuService, 5 | NbSidebarService, 6 | NbThemeService, 7 | } from '@nebular/theme'; 8 | import { UserService } from '../../../@core/services/user.service'; 9 | import { filter, map, take } from 'rxjs/operators'; 10 | 11 | @Component({ 12 | selector: 'ngx-header', 13 | styleUrls: ['./header.component.scss'], 14 | templateUrl: './header.component.html', 15 | }) 16 | export class HeaderComponent implements OnInit { 17 | isDarkTheme: boolean; 18 | 19 | userMenu: NbMenuItem[] = [ 20 | { title: 'Django Admin Interface' }, 21 | { title: 'Log out' }, 22 | ]; 23 | 24 | constructor( 25 | private sidebarService: NbSidebarService, 26 | private nbMenuService: NbMenuService, 27 | private nbThemeService: NbThemeService, 28 | public userService: UserService 29 | ) { 30 | this.nbThemeService.onThemeChange().subscribe((themeName) => { 31 | this.isDarkTheme = themeName?.name === 'dark' ? true : false; 32 | }); 33 | } 34 | 35 | ngOnInit(): void { 36 | this.nbMenuService 37 | .onItemClick() 38 | .pipe( 39 | take(1), 40 | filter(({ tag }) => tag === 'user'), 41 | map(({ item }) => item) 42 | ) 43 | .subscribe((item) => { 44 | switch (item.title) { 45 | case 'Django Admin Interface': { 46 | document.location.assign('/admin'); 47 | break; 48 | } 49 | case 'Log out': { 50 | this.userService.logOut(); 51 | break; 52 | } 53 | default: { 54 | break; 55 | } 56 | } 57 | }); 58 | } 59 | 60 | toggleSidebar(): boolean { 61 | this.sidebarService.toggle(true, 'menu-sidebar'); 62 | return false; 63 | } 64 | 65 | navigateHome(): boolean { 66 | this.nbMenuService.navigateHome(); 67 | return false; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/app/pages/dashboard/dashboard.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { 3 | NbButtonModule, 4 | NbTooltipModule, 5 | NbAlertModule, 6 | NbListModule, 7 | NbTabsetModule, 8 | } from '@nebular/theme'; 9 | import { ThemeModule } from '../../@theme/theme.module'; 10 | import { EchartsPieComponent } from '../../@theme/components/echarts-pie/echarts-pie.component'; 11 | import { 12 | ViewResultButtonComponent, 13 | JobStatusIconRenderComponent, 14 | TagsRenderComponent, 15 | PluginActionsRenderComponent, 16 | } from '../../@theme/components/smart-table/smart-table'; 17 | import { JobService } from '../../@core/services/job.service'; 18 | import { JobResultComponent } from './job-result/job-result.component'; 19 | import { JobActionsMenuComponent } from './job-result/job-actions-menu/job-actions-menu.component'; 20 | import { DashboardComponent } from './dashboard.component'; 21 | import { NgxEchartsModule } from 'ngx-echarts'; 22 | import { Ng2SmartTableModule } from 'ng2-smart-table'; 23 | import { DebounceClickDirective } from 'src/app/@theme/directives/app-debounce-click.directive'; 24 | import { NgJsonEditorModule } from 'ang-jsoneditor'; 25 | 26 | import * as echarts from 'echarts'; 27 | import { JobInfoListComponent } from './job-result/job-info-list/job-info-list.component'; 28 | 29 | @NgModule({ 30 | imports: [ 31 | ThemeModule, 32 | NbButtonModule, 33 | NbListModule, 34 | NbTabsetModule, 35 | NbTooltipModule, 36 | NbAlertModule, 37 | Ng2SmartTableModule, 38 | NgxEchartsModule.forRoot({ echarts }), 39 | NgJsonEditorModule, 40 | ], 41 | declarations: [ 42 | DashboardComponent, 43 | JobResultComponent, 44 | JobActionsMenuComponent, 45 | ViewResultButtonComponent, 46 | JobStatusIconRenderComponent, 47 | TagsRenderComponent, 48 | EchartsPieComponent, 49 | DebounceClickDirective, 50 | JobInfoListComponent, 51 | PluginActionsRenderComponent, 52 | ], 53 | providers: [JobService], 54 | }) 55 | export class DashboardModule {} 56 | -------------------------------------------------------------------------------- /src/app/@theme/styles/themes.scss: -------------------------------------------------------------------------------- 1 | // @nebular theming framework 2 | @import "~@nebular/theme/styles/theming"; 3 | // @nebular out of the box themes 4 | @import "~@nebular/theme/styles/themes/dark"; 5 | @import "~@nebular/theme/styles/themes/default"; 6 | 7 | $nb-themes: nb-register-theme( 8 | ( 9 | toggle-height: 1.3rem, 10 | toggle-width: 2.3rem, 11 | toggle-switcher-size: 1.15rem, 12 | toggle-switcher-icon-size: 0.5rem, 13 | layout-padding-top: 2.25rem, 14 | menu-item-icon-margin: 0 0.5rem 0 0, 15 | card-height-tiny: 13.5rem, 16 | card-height-small: 21.1875rem, 17 | card-height-medium: 28.875rem, 18 | card-height-large: 36.5625rem, 19 | card-height-giant: 44.25rem, 20 | card-margin-bottom: 1.875rem, 21 | card-header-with-select-padding-top: 0.5rem, 22 | card-header-with-select-padding-bottom: 0.5rem, 23 | card-box-shadow: 0 0.5rem 1rem 0.4rem rgba(44, 51, 73, 0.1), 24 | select-min-width: 6rem, 25 | slide-out-background: #f7f9fc, 26 | slide-out-shadow-color: 0 4px 14px 0 #8f9bb3, 27 | slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3, 28 | ), 29 | default, 30 | default 31 | ); 32 | 33 | $nb-themes: nb-register-theme( 34 | ( 35 | toggle-height: 1.3rem, 36 | toggle-width: 2.3rem, 37 | toggle-switcher-size: 1.15rem, 38 | toggle-switcher-icon-size: 0.5rem, 39 | layout-padding-top: 2.25rem, 40 | menu-item-icon-margin: 0 0.5rem 0 0, 41 | card-height-tiny: 13.5rem, 42 | card-height-small: 21.1875rem, 43 | card-height-medium: 28.875rem, 44 | card-height-large: 36.5625rem, 45 | card-height-giant: 44.25rem, 46 | card-margin-bottom: 1.875rem, 47 | card-header-with-select-padding-top: 0.5rem, 48 | card-header-with-select-padding-bottom: 0.5rem, 49 | card-box-shadow: 0 0.5rem 1rem 0 #1a1f33, 50 | select-min-width: 6rem, 51 | slide-out-background: linear-gradient(270deg, #222b45 0%, #151a30 100%), 52 | slide-out-shadow-color: 0 4px 14px 0 #8f9bb3, 53 | slide-out-shadow-color-rtl: 0 4px 14px 0 #8f9bb3, 54 | ), 55 | dark, 56 | dark 57 | ); 58 | -------------------------------------------------------------------------------- /src/app/@core/services/indexdb.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { DexieService } from './dexie.service'; 3 | import { IRecentScan } from '../models/models'; 4 | 5 | @Injectable() 6 | export class IndexedDbService { 7 | tables: string[] = ['recent_scans']; 8 | 9 | constructor(private dexieService: DexieService) {} 10 | 11 | async getOne(tableName: string, key: string | number) { 12 | return await this.dexieService.table(tableName).get(key); 13 | } 14 | 15 | async getRecentScans(): Promise { 16 | return await this.dexieService.table('recent_scans').toArray(); 17 | } 18 | 19 | async addToRecentScans(obj: IRecentScan) { 20 | const table = this.dexieService.table('recent_scans'); 21 | if ((await table.count()) >= 10) { 22 | // at a time, there shouldn't be more than 10 entries 23 | table.limit(3).delete(); 24 | } 25 | await table.put(obj); 26 | } 27 | 28 | async addOrReplaceOne(tableName: string, obj: any, forceUpdate?: boolean) { 29 | if (this.tables.indexOf(tableName) < 0) { 30 | return; 31 | } 32 | try { 33 | if (forceUpdate) { 34 | await this.dexieService.table(tableName).put(obj, obj.id); 35 | return; 36 | } 37 | const res = await this.dexieService.table(tableName).get(obj.id); 38 | if (!res) { 39 | await this.dexieService.table(tableName).add(obj); 40 | } else { 41 | await this.dexieService.table(tableName).update(obj.id, obj); 42 | } 43 | } catch (e) { 44 | console.error(e); 45 | } 46 | } 47 | 48 | async addOrReplaceBulk(tableName: string, arr: any[]) { 49 | if (this.tables.indexOf(tableName) < 0) { 50 | return; 51 | } 52 | try { 53 | await this.dexieService.table(tableName).bulkPut(arr); 54 | } catch (e) { 55 | console.error(e); 56 | } 57 | } 58 | 59 | getAllInstances(tableName: string) { 60 | return this.dexieService.table(tableName).toArray(); 61 | } 62 | 63 | getTableInstance(tableName: string) { 64 | return this.dexieService.table(tableName); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** IE10 and IE11 requires the following to support `@angular/animation`. */ 25 | /** ALL Firefox browsers require the following to support `@angular/animation`. **/ 26 | import 'web-animations-js'; // Run `npm install --save web-animations-js`. 27 | 28 | /** Evergreen browsers require these. **/ 29 | import 'core-js/es/reflect'; 30 | 31 | /*************************************************************************************************** 32 | * Zone JS is required by Angular itself. 33 | */ 34 | import 'zone.js/dist/zone'; // Included with Angular CLI. 35 | 36 | /*************************************************************************************************** 37 | * APPLICATION IMPORTS 38 | */ 39 | 40 | /** 41 | * Date, currency, decimal and percent pipes. 42 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 43 | */ 44 | import 'core-js/es/array'; 45 | import 'core-js/es/object'; 46 | 47 | if (typeof SVGElement.prototype.contains === 'undefined') { 48 | SVGElement.prototype.contains = HTMLDivElement.prototype.contains; 49 | } 50 | -------------------------------------------------------------------------------- /src/app/pages/scans/scans.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { ThemeModule } from '../../@theme/theme.module'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { 5 | NbInputModule, 6 | NbButtonModule, 7 | NbSelectModule, 8 | NbCheckboxModule, 9 | NbRouteTabsetModule, 10 | NbRadioModule, 11 | NbToggleModule, 12 | NbPopoverModule, 13 | NbSpinnerModule, 14 | NbTooltipModule, 15 | NbDialogModule, 16 | } from '@nebular/theme'; 17 | import { ScansManagementComponent } from './scans-management/scans-management.component'; 18 | import { ScanFileComponent } from './scans-management/scan-file/scan-file.component'; 19 | import { ScanObservableComponent } from './scans-management/scan-observable/scan-observable.component'; 20 | import { ScansRoutingModule } from './scans-routing.module'; 21 | import { ScanService } from '../../@core/services/scan.service'; 22 | import { NgxTaggerComponent } from '../../@theme/components/ngx-tagger/ngx-tagger.component'; 23 | import { NgJsonEditorModule } from 'ang-jsoneditor'; 24 | import { AppJsonEditorComponent } from 'src/app/@theme/components/app-json-editor/app-json-editor.component'; 25 | import { BaseScanFormComponent } from './scans-management/base-scan.component'; 26 | import { EditConfigParamsDialogComponent } from './lib/edit-config-params-dialog.component'; 27 | 28 | @NgModule({ 29 | declarations: [ 30 | // theme 31 | NgxTaggerComponent, 32 | AppJsonEditorComponent, 33 | // lib 34 | EditConfigParamsDialogComponent, 35 | // components 36 | ScansManagementComponent, 37 | BaseScanFormComponent, 38 | ScanFileComponent, 39 | ScanObservableComponent, 40 | ], 41 | imports: [ 42 | ScansRoutingModule, 43 | ThemeModule, 44 | FormsModule, 45 | NbInputModule, 46 | NbSelectModule, 47 | NbToggleModule, 48 | NbCheckboxModule, 49 | NbRadioModule, 50 | NbButtonModule, 51 | NbRouteTabsetModule, 52 | NbPopoverModule, 53 | NbSpinnerModule, 54 | NbTooltipModule, 55 | NbDialogModule.forChild(), 56 | NgJsonEditorModule, 57 | ], 58 | providers: [ScanService], 59 | }) 60 | export class ScansModule {} 61 | -------------------------------------------------------------------------------- /src/app/pages/plugins/analyzers-management/tabs/analyzers-tree.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { IAnalyzersList } from '../../../../@core/models/models'; 3 | import { AnalyzerConfigService } from '../../../../@core/services/analyzer-config.service'; 4 | import { first } from 'rxjs/operators'; 5 | 6 | @Component({ 7 | template: ` 8 | 14 | 15 | 19 | 20 | 21 | `, 22 | }) 23 | export class AnalyzersTreeComponent implements OnInit { 24 | // Dendogram Data 25 | public treeData: any; 26 | showSpinnerBool: boolean = false; 27 | 28 | constructor(private readonly analyzerService: AnalyzerConfigService) {} 29 | 30 | ngOnInit(): void { 31 | this.showSpinnerBool = true; // spinner on 32 | this.analyzerService.analyzersList$ 33 | .pipe(first()) 34 | .subscribe((aList: IAnalyzersList) => { 35 | this.treeData = { 36 | name: 'IntelOwl', 37 | children: [ 38 | { 39 | name: 'Observable Analyzers', 40 | children: [ 41 | { 42 | name: 'IP', 43 | children: aList['ip'], 44 | }, 45 | { 46 | name: 'URL', 47 | children: aList['url'], 48 | }, 49 | { 50 | name: 'Domain', 51 | children: aList['domain'], 52 | }, 53 | { 54 | name: 'Hash', 55 | children: aList['hash'], 56 | }, 57 | ], 58 | }, 59 | { 60 | name: 'File Analyzers', 61 | children: aList['file'], 62 | }, 63 | ], 64 | }; 65 | this.showSpinnerBool = false; // spinner off 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/app/@core/services/tag.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { HttpService } from './http.service'; 4 | import { Tag } from '../models/models'; 5 | import { ToastService } from './toast.service'; 6 | import { Subject } from 'rxjs'; 7 | 8 | @Injectable({ 9 | providedIn: 'root', 10 | }) 11 | export class TagService extends HttpService { 12 | private _tags$: Subject = new Subject() as Subject; 13 | public tags: Tag[]; 14 | 15 | constructor(private toastr: ToastService, private _httpClient: HttpClient) { 16 | super(_httpClient); 17 | this.init().then(); 18 | } 19 | 20 | get tags$() { 21 | return this._tags$.asObservable(); 22 | } 23 | 24 | private async init() { 25 | try { 26 | const result: Tag[] = await this.query({}, 'tags'); 27 | this.tags = result; 28 | this._tags$.next(result); 29 | } catch (e) { 30 | console.error(e); 31 | if (e.status >= 500) { 32 | // this.offlineInit(); 33 | } 34 | } 35 | } 36 | 37 | /* depecrated atm 38 | private async offlineInit() { 39 | this.indexDB.getAllInstances('tags').then((res) => { 40 | this.tags = res; 41 | this._tags$.next(res); 42 | }); 43 | } 44 | */ 45 | 46 | async updateTag(tag: Tag): Promise { 47 | try { 48 | const obj: Tag = await this.update(tag.id, tag, {}, 'tags'); 49 | this.toastr.showToast('Updated tag', `Tag #${obj.id}`, 'success'); 50 | return obj; 51 | } catch (e) { 52 | const err = Object.values(e.error); 53 | this.toastr.showToast( 54 | `Error: ${err}`, 55 | `Failed to update tag #${tag.id}`, 56 | 'error' 57 | ); 58 | return Promise.reject(); 59 | } 60 | } 61 | 62 | async createTag(tag: Tag): Promise { 63 | try { 64 | const obj: Tag = await this.create(tag, {}, 'tags'); 65 | this.toastr.showToast('Created tag', `Tag #${obj.id}`, 'success'); 66 | return obj; 67 | } catch (e) { 68 | const err = Object.values(e.error); 69 | this.toastr.showToast( 70 | `Error: ${err}`, 71 | `Failed to create tag #${tag.label}`, 72 | 'error' 73 | ); 74 | return Promise.reject(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/app/pages/dashboard/job-result/job-actions-menu/job-actions-menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { JobService } from '../../../../@core/services/job.service'; 4 | import { ToastService } from 'src/app/@core/services/toast.service'; 5 | import { Job } from '../../../../@core/models/models'; 6 | 7 | @Component({ 8 | selector: 'job-actions-menu', 9 | templateUrl: './job-actions-menu.component.html', 10 | styleUrls: ['./job-actions-menu.component.scss'], 11 | }) 12 | export class JobActionsMenuComponent { 13 | @Input() job: Job; 14 | 15 | constructor( 16 | private readonly jobService: JobService, 17 | private readonly toastr: ToastService, 18 | private readonly router: Router 19 | ) {} 20 | 21 | async getJobSample(): Promise { 22 | const url: string = await this.jobService.downloadJobSample(+this.job.id); 23 | window.open(url, 'rel=noopener,noreferrer'); 24 | } 25 | 26 | async getJobRawJson(): Promise { 27 | const url: string = await this.jobService.downloadJobRawJson(+this.job.id); 28 | window.open(url, 'rel=noopener,noreferrer'); 29 | } 30 | 31 | async deleteJob(): Promise { 32 | const sure = confirm('Are you sure?'); 33 | if (!sure) return; 34 | const success = await this.jobService.deleteJobById(+this.job.id); 35 | if (success) { 36 | this.toastr.showToast( 37 | 'Deleted successfully.', 38 | `Job #${this.job.id}`, 39 | 'success' 40 | ); 41 | setTimeout(() => this.router.navigate(['/']), 1000); 42 | } else { 43 | this.toastr.showToast( 44 | 'Could not be deleted. Reason: "Insufficient Permission".', 45 | `Job #${this.job.id}`, 46 | 'error' 47 | ); 48 | } 49 | } 50 | 51 | async killJob(): Promise { 52 | const sure = confirm('Are you sure?'); 53 | if (!sure) return; 54 | const success = await this.jobService.killJobById(+this.job.id); 55 | if (success) { 56 | this.toastr.showToast( 57 | 'Marked as "killed" successfully.', 58 | `Job #${this.job.id}`, 59 | 'success' 60 | ); 61 | } else { 62 | this.toastr.showToast( 63 | 'Could not be "killed". Reason: "Insufficient Permission".', 64 | `Job #${this.job.id}`, 65 | 'error' 66 | ); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/app/@core/services/analyzer-config.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ReplaySubject } from 'rxjs'; 3 | import { HttpClient } from '@angular/common/http'; 4 | import { IAnalyzersList, IRawAnalyzerConfig } from '../models/models'; 5 | import { PluginService } from './plugin.service'; 6 | import { ToastService } from './toast.service'; 7 | 8 | @Injectable({ 9 | providedIn: 'root', 10 | }) 11 | export class AnalyzerConfigService extends PluginService { 12 | public rawAnalyzerConfig: IRawAnalyzerConfig = {}; 13 | private _analyzersList$: ReplaySubject = new ReplaySubject(1); 14 | 15 | constructor(_httpClient: HttpClient, toastr: ToastService) { 16 | super(_httpClient, toastr); 17 | this.pluginType = 'analyzer'; 18 | this.init().then(); 19 | } 20 | 21 | get analyzersList$() { 22 | return this._analyzersList$.asObservable(); 23 | } 24 | 25 | private async init(): Promise { 26 | try { 27 | this.rawAnalyzerConfig = await this.query({}, 'get_analyzer_configs'); 28 | this.makeAnalyzersList(); 29 | } catch (e) { 30 | console.error(e); 31 | } 32 | } 33 | 34 | async checkAnalyzerHealth(analyzer_name: string): Promise { 35 | return this.checkPluginHealth(analyzer_name); 36 | } 37 | 38 | async killAnalyzer(job_id: number, analyzer_name: string): Promise { 39 | return this.killPlugin(job_id, analyzer_name); 40 | } 41 | 42 | async retryAnalyzer(job_id: number, analyzer_name: string): Promise { 43 | return this.retryPlugin(job_id, analyzer_name); 44 | } 45 | 46 | private makeAnalyzersList(): void { 47 | const analyzers: IAnalyzersList = { 48 | ip: [], 49 | hash: [], 50 | domain: [], 51 | url: [], 52 | generic: [], 53 | file: [], 54 | }; 55 | 56 | Object.entries(this.rawAnalyzerConfig).forEach(([key, obj]) => { 57 | // filter on basis of type 58 | if (obj.type === 'file') { 59 | analyzers.file.push(obj); 60 | } else { 61 | obj.observable_supported.forEach((clsfn: string) => { 62 | analyzers[clsfn].push(obj); 63 | }); 64 | } 65 | }); 66 | this._analyzersList$.next(analyzers); 67 | } 68 | 69 | constructTableData(): any[] { 70 | return Object.values(this.rawAnalyzerConfig).map((obj) => { 71 | if (obj.type === 'observable') { 72 | obj['supports'] = obj['observable_supported']; 73 | } else { 74 | obj['supports'] = obj['supported_filetypes']; 75 | } 76 | return obj; 77 | }); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/app/pages/scans/lib/edit-config-params-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | Input, 4 | OnChanges, 5 | OnInit, 6 | SimpleChanges, 7 | } from '@angular/core'; 8 | import { NbDialogRef } from '@nebular/theme'; 9 | import { JsonEditorOptions } from 'ang-jsoneditor'; 10 | import { IAbstractConfig } from 'src/app/@core/models/models'; 11 | 12 | @Component({ 13 | template: ` 17 |
18 |
19 |
{{ configItem.key }}
20 |
    21 |
  • 22 | {{ paramItem.key }} 23 |  ({{ paramItem.value.type }}) 24 |
    {{ paramItem.value.description }}
    25 |
  • 26 |
27 | 28 | null 29 | 30 |
31 |
32 |
`, 33 | }) 34 | export class EditConfigParamsDialogComponent implements OnInit, OnChanges { 35 | @Input() public configParamsMap: Record< 36 | string, 37 | Record 38 | >; 39 | public jsonInputData: any = {}; 40 | public editorOptions: JsonEditorOptions; // JSON editor 41 | 42 | constructor(protected dialogRef: NbDialogRef) { 43 | this.editorOptions = new JsonEditorOptions(); 44 | this.editorOptions.modes = ['code']; 45 | this.editorOptions.mode = 'code'; 46 | } 47 | 48 | ngOnInit(): void { 49 | this.updatejsonInputData(); 50 | } 51 | 52 | ngOnChanges(changes: SimpleChanges): void { 53 | if ( 54 | Object.keys(changes.configParamsMap.previousValue).length !== 55 | Object.keys(changes.configParamsMap.currentValue).length 56 | ) { 57 | this.updatejsonInputData(); 58 | } 59 | } 60 | 61 | public isEmptyObject(obj: any): boolean { 62 | return Object.keys(obj).length === 0; 63 | } 64 | 65 | private updatejsonInputData(): void { 66 | this.jsonInputData = Object.entries(this.configParamsMap).reduce( 67 | (acc, [name, params]) => ({ 68 | ...acc, 69 | [name]: Object.entries(params).reduce( 70 | (acc2, [pName, { value }]) => ({ 71 | ...acc2, 72 | [pName]: value, 73 | }), 74 | {} 75 | ), 76 | }), 77 | {} 78 | ); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/app/@theme/theme.module.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders, NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { 4 | NbActionsModule, 5 | NbMenuModule, 6 | NbSidebarModule, 7 | NbUserModule, 8 | NbContextMenuModule, 9 | NbThemeModule, 10 | NbSpinnerModule, 11 | NbIconModule, 12 | NbLayoutModule, 13 | NbTooltipModule, 14 | NbCardModule, 15 | NbToggleModule, 16 | } from '@nebular/theme'; 17 | 18 | import { HeaderComponent } from './components/header/header.component'; 19 | import { FooterComponent } from './components/footer/footer.component'; 20 | import { ImageVisualizerComponent } from './components/image-visualizer/image-visualizer.component'; 21 | import { ThemeSwitcherComponent } from './components/header/theme-switcher/theme-switcher.component'; 22 | import { SocialLinksComponent } from './components/social-links/social-links.component'; 23 | 24 | import { 25 | CapitalizePipe, 26 | MarkdownPipe, 27 | TimingPipe, 28 | NumberWithCommasPipe, 29 | } from './pipes'; 30 | import { 31 | OneColumnLayoutComponent, 32 | ThreeColumnsLayoutComponent, 33 | TwoColumnsLayoutComponent, 34 | } from './layouts'; 35 | import { DEFAULT_THEME } from './styles/theme.default'; 36 | import { DARK_THEME } from './styles/theme.dark'; 37 | 38 | import { FormsModule } from '@angular/forms'; 39 | 40 | const NB_MODULES = [ 41 | NbLayoutModule, 42 | NbMenuModule, 43 | NbUserModule, 44 | NbActionsModule, 45 | NbSidebarModule, 46 | NbContextMenuModule, 47 | NbSpinnerModule, 48 | NbIconModule, 49 | NbTooltipModule, 50 | NbToggleModule, 51 | ]; 52 | 53 | const COMPONENTS = [ 54 | HeaderComponent, 55 | FooterComponent, 56 | ImageVisualizerComponent, 57 | OneColumnLayoutComponent, 58 | ThreeColumnsLayoutComponent, 59 | TwoColumnsLayoutComponent, 60 | ThemeSwitcherComponent, 61 | SocialLinksComponent, 62 | ]; 63 | 64 | const PIPES = [CapitalizePipe, MarkdownPipe, TimingPipe, NumberWithCommasPipe]; 65 | 66 | // modules that will be reused across all child modules. 67 | const MODULES_TO_EXPORT = [CommonModule, NbIconModule, NbCardModule]; 68 | 69 | const ANGULAR_MODULES = [CommonModule, FormsModule]; 70 | 71 | @NgModule({ 72 | imports: [...ANGULAR_MODULES, ...NB_MODULES], 73 | exports: [...PIPES, ...COMPONENTS, ...MODULES_TO_EXPORT], 74 | declarations: [...COMPONENTS, ...PIPES], 75 | }) 76 | export class ThemeModule { 77 | static forRoot(): ModuleWithProviders { 78 | return { 79 | ngModule: ThemeModule, 80 | providers: [ 81 | ...NbThemeModule.forRoot( 82 | { 83 | name: ThemeSwitcherComponent.getThemeName(), 84 | }, 85 | [DEFAULT_THEME, DARK_THEME] 86 | ).providers, 87 | ], 88 | }; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/app/pages/plugins/plugins.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { 3 | NbAlertModule, 4 | NbButtonModule, 5 | NbPopoverModule, 6 | NbRouteTabsetModule, 7 | NbSpinnerModule, 8 | NbTagModule, 9 | NbTooltipModule, 10 | } from '@nebular/theme'; 11 | import { Ng2SmartTableModule } from 'ng2-smart-table'; 12 | import { NgxEchartsModule } from 'ngx-echarts'; 13 | import { EchartsTreeComponent } from '../../@theme/components/echarts-tree/echarts-tree.component'; 14 | import { 15 | TickCrossRenderComponent, 16 | ConfiguredIconComponent, 17 | JSONRenderComponent, 18 | TLPRenderComponent, 19 | ListCellComponent, 20 | DescriptionRenderComponent, 21 | PopoverOnCellHoverComponent, 22 | } from '../../@theme/components/smart-table/smart-table'; 23 | import { ThemeModule } from '../../@theme/theme.module'; 24 | 25 | import { 26 | PluginHealthCheckButtonRenderComponent, 27 | PluginSecretsDictRenderComponent, 28 | PluginParamsDictRenderComponent, 29 | PluginConfigDictRenderComponent, 30 | PluginInfoCardComponent, 31 | } from './lib/components'; 32 | import { AnalyzersManagementComponent } from './analyzers-management/analyzers-management.component'; 33 | import { 34 | AnalyzersTableComponent, 35 | AnalyzersTreeComponent, 36 | AnalyzersCardsComponent, 37 | } from './analyzers-management/tabs'; 38 | import { ConnectorsManagementComponent } from './connectors-management/connectors-management'; 39 | import { 40 | ConnectorsTableComponent, 41 | ConnectorsCardsComponent, 42 | } from './connectors-management/tabs'; 43 | 44 | import { PluginsRoutingModule } from './plugins-routing.module'; 45 | 46 | @NgModule({ 47 | imports: [ 48 | ThemeModule, 49 | PluginsRoutingModule, 50 | NbAlertModule, 51 | NbButtonModule, 52 | NbTagModule, 53 | NbRouteTabsetModule, 54 | NbSpinnerModule, 55 | Ng2SmartTableModule, 56 | NbPopoverModule, 57 | NgxEchartsModule, 58 | NbTooltipModule, 59 | ], 60 | declarations: [ 61 | AnalyzersManagementComponent, 62 | ConnectorsManagementComponent, 63 | AnalyzersTableComponent, 64 | AnalyzersTreeComponent, 65 | AnalyzersCardsComponent, 66 | ConnectorsTableComponent, 67 | ConnectorsCardsComponent, 68 | // lib 69 | PluginHealthCheckButtonRenderComponent, 70 | PluginSecretsDictRenderComponent, 71 | PluginParamsDictRenderComponent, 72 | PluginConfigDictRenderComponent, 73 | PluginInfoCardComponent, 74 | // theme/ smart table components 75 | EchartsTreeComponent, 76 | TickCrossRenderComponent, 77 | ConfiguredIconComponent, 78 | JSONRenderComponent, 79 | TLPRenderComponent, 80 | ListCellComponent, 81 | DescriptionRenderComponent, 82 | PopoverOnCellHoverComponent, 83 | ], 84 | }) 85 | export class PluginsModule {} 86 | -------------------------------------------------------------------------------- /src/app/@core/services/http.intercepter.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Injector } from '@angular/core'; 2 | import { 3 | HttpEvent, 4 | HttpHandler, 5 | HttpInterceptor, 6 | HttpRequest, 7 | } from '@angular/common/http'; 8 | import { switchMap, catchError } from 'rxjs/operators'; 9 | import { Observable, throwError } from 'rxjs'; 10 | import { AuthService } from './auth.service'; 11 | 12 | @Injectable() 13 | export class TokenInterceptor implements HttpInterceptor { 14 | constructor(private injector: Injector) {} 15 | 16 | filterReqs(req: HttpRequest) { 17 | if (req.url === '/api/auth/login') { 18 | return true; 19 | } else if (req.url.startsWith('/api/')) { 20 | return false; 21 | } 22 | return true; 23 | } 24 | 25 | intercept( 26 | req: HttpRequest, 27 | next: HttpHandler 28 | ): Observable> { 29 | // do not intercept request whose urls are filtered by the injected filter 30 | if (!this.filterReqs(req)) { 31 | return this.authService.isAuthenticated().pipe( 32 | switchMap((authenticated: boolean) => { 33 | if (authenticated) { 34 | return this.authService.getToken().pipe( 35 | switchMap((token: string) => { 36 | req = req.clone({ 37 | setHeaders: { 38 | Authorization: `Token ${token}`, 39 | }, 40 | }); 41 | return next 42 | .handle(req) 43 | .pipe( 44 | catchError((err: any, _caught: any) => 45 | this.handleError(err) 46 | ) 47 | ); 48 | }) 49 | ); 50 | } else { 51 | // Request is sent to server without authentication so that the client code 52 | // receives the 401/403 error and can act as desired ('session expired', redirect to login, aso) 53 | return next 54 | .handle(req) 55 | .pipe( 56 | catchError((err: any, _caught: any) => this.handleError(err)) 57 | ); 58 | } 59 | }) 60 | ); 61 | } else { 62 | return next.handle(req); 63 | } 64 | } 65 | 66 | private handleError(err: any) { 67 | if (err.status === 401 && !err.url.includes('logout')) 68 | this.authService.logout(); 69 | const errMsg: string = ( 70 | err?.error?.error || 71 | err?.error?.detail || 72 | err?.error?.non_field_errors || 73 | err?.detail || 74 | err?.message || 75 | JSON.stringify(err) 76 | ).toString(); 77 | return throwError(new Error(errMsg)); 78 | } 79 | 80 | protected get authService(): AuthService { 81 | return this.injector.get(AuthService); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/app/@core/services/plugin.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { HealthCheckStatus } from '../models/models'; 4 | import { HttpService } from './http.service'; 5 | import { ToastService } from './toast.service'; 6 | 7 | @Injectable({ 8 | providedIn: 'root', 9 | }) 10 | export class PluginService extends HttpService { 11 | pluginType: string; 12 | 13 | constructor( 14 | private _httpClient: HttpClient, 15 | private readonly toastr: ToastService 16 | ) { 17 | super(_httpClient); 18 | } 19 | 20 | private toTitleCase(value: string): string { 21 | return value[0].toUpperCase() + value.substring(1).toLowerCase(); 22 | } 23 | 24 | async killPlugin(jobId: number, plugin: string): Promise { 25 | const sure = confirm('Are you sure?'); 26 | if (!sure) return; 27 | 28 | try { 29 | await this.patch( 30 | {}, 31 | {}, 32 | `job/${jobId}/${this.pluginType}/${plugin}/kill` 33 | ); 34 | this.toastr.showToast( 35 | '"killed" successfully.', 36 | `Job #${jobId} ${this.toTitleCase(this.pluginType)}: ${plugin}`, 37 | 'success' 38 | ); 39 | return true; 40 | } catch (e) { 41 | this.toastr.showToast( 42 | 'Could not be "killed". Reason: "Insufficient Permission".', 43 | `Job #${jobId} ${this.toTitleCase(this.pluginType)}: ${plugin}`, 44 | 'error' 45 | ); 46 | return false; 47 | } 48 | } 49 | 50 | async retryPlugin(jobId: number, plugin: string): Promise { 51 | const sure = confirm('Are you sure?'); 52 | if (!sure) return; 53 | 54 | try { 55 | await this.patch( 56 | {}, 57 | {}, 58 | `job/${jobId}/${this.pluginType}/${plugin}/retry` 59 | ); 60 | this.toastr.showToast( 61 | '"retry" request sent successfully.', 62 | `Job #${jobId} ${this.toTitleCase(this.pluginType)}: ${plugin}`, 63 | 'success' 64 | ); 65 | return true; 66 | } catch (e) { 67 | this.toastr.showToast( 68 | 'Could not be send "retry" request. Reason: "Insufficient Permission".', 69 | `Job #${jobId} ${this.toTitleCase(this.pluginType)}: ${plugin}`, 70 | 'error' 71 | ); 72 | return false; 73 | } 74 | } 75 | 76 | async checkPluginHealth(plugin: string): Promise { 77 | try { 78 | const result: HealthCheckStatus = await this.query( 79 | {}, 80 | `${this.pluginType}/${plugin}/healthcheck` 81 | ); 82 | return result.status; 83 | } catch (e) { 84 | this.toastr.showToast( 85 | 'Health Check Request Failed', 86 | `${this.toTitleCase(this.pluginType)}: ${plugin}`, 87 | 'error' 88 | ); 89 | return null; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/app/@theme/components/ngx-tagger/ngx-tagger.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy, Output, ViewChild } from '@angular/core'; 2 | import { TagService } from '../../../@core/services/tag.service'; 3 | import { Tag } from '../../../@core/models/models'; 4 | import { Subscription } from 'rxjs'; 5 | import { EventEmitter } from '@angular/core'; 6 | import { NbPopoverDirective } from '@nebular/theme'; 7 | 8 | @Component({ 9 | selector: 'ngx-tagger', 10 | templateUrl: './ngx-tagger.component.html', 11 | styleUrls: ['./ngx-tagger.component.scss'], 12 | }) 13 | export class NgxTaggerComponent implements OnInit, OnDestroy { 14 | @ViewChild(NbPopoverDirective) popover: NbPopoverDirective; 15 | @Output() private onOpen: EventEmitter = new EventEmitter(); 16 | @Output() private onClose: EventEmitter = new EventEmitter(); 17 | 18 | private sub: Subscription; 19 | tags: Tag[]; 20 | selectedTags: Set = new Set(); 21 | mutableTag: Tag = { 22 | label: 'label', 23 | color: '#ffffff', 24 | } as Tag; 25 | private savedTag: Tag = Object.create(null); 26 | editMode: boolean = false; 27 | 28 | constructor(private tagService: TagService) {} 29 | 30 | ngOnInit(): void { 31 | this.tags = this.tagService.tags; 32 | this.sub = this.tagService.tags$.subscribe((res) => (this.tags = res)); 33 | } 34 | 35 | // Popover 36 | 37 | open(): void { 38 | this.onOpen.emit(); 39 | this.popover.show(); 40 | } 41 | 42 | close(): void { 43 | const tagstoOutput: string[] = new Array(); 44 | this.selectedTags.forEach((tag) => tagstoOutput.push(tag.label)); 45 | this.onClose.emit(tagstoOutput); 46 | this.popover.hide(); 47 | } 48 | 49 | // Tag selection 50 | 51 | onTagClick(event: Tag): void { 52 | if (this.selectedTags.has(event)) { 53 | this.selectedTags.delete(event); 54 | } else { 55 | this.selectedTags.add(event); 56 | } 57 | } 58 | 59 | // Tag update/create on client side 60 | editTag(event: Tag): void { 61 | // save current state 62 | Object.assign(this.savedTag, event); 63 | this.mutableTag = event; 64 | this.editMode = true; 65 | } 66 | 67 | newTag(): void { 68 | this.mutableTag = { 69 | label: 'label', 70 | color: '#ffffff', 71 | } as Tag; 72 | this.editMode = true; 73 | } 74 | 75 | // Tag update/create on server 76 | 77 | async updateTag(): Promise { 78 | this.editMode = false; 79 | if (this.mutableTag.id) { 80 | this.tagService 81 | .updateTag(this.mutableTag) 82 | .then((obj: Tag) => Object.assign(this.mutableTag, obj)) 83 | .catch(() => Object.assign(this.mutableTag, this.savedTag)); 84 | } else { 85 | this.tagService 86 | .createTag(this.mutableTag) 87 | .then((obj: Tag) => this.tags.unshift(obj)); 88 | } 89 | } 90 | 91 | ngOnDestroy(): void { 92 | this.sub && this.sub.unsubscribe(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/app/@core/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, of, ReplaySubject } from 'rxjs'; 3 | import { map } from 'rxjs/operators'; 4 | import { HttpClient } from '@angular/common/http'; 5 | import { HttpService } from './http.service'; 6 | import { ILoginPayload } from '../models/models'; 7 | import { ToastService } from './toast.service'; 8 | import { Router } from '@angular/router'; 9 | 10 | @Injectable({ 11 | providedIn: 'root', 12 | }) 13 | export class AuthService extends HttpService { 14 | private readonly TOKEN_NAME = 'TOKEN'; 15 | private readonly PAYLOAD_NAME = 'PAYLOAD'; 16 | 17 | private _onTokenChange$: ReplaySubject = new ReplaySubject( 18 | 1 19 | ) as ReplaySubject; 20 | 21 | constructor( 22 | private _httpClient: HttpClient, 23 | private toastr: ToastService, 24 | private router: Router 25 | ) { 26 | super(_httpClient); 27 | this.getToken() 28 | .toPromise() 29 | .then((token: string) => this._onTokenChange$.next(token)); 30 | } 31 | 32 | login(user: { username: string; password: string }): any { 33 | return this.create(user, {}, 'auth/login').then( 34 | (resp: ILoginPayload) => { 35 | this.storePayload(resp); 36 | this._onTokenChange$.next(resp.token); 37 | return true; 38 | }, 39 | (err: any) => { 40 | this._onTokenChange$.error(err); 41 | return Promise.reject(err); 42 | } 43 | ); 44 | } 45 | 46 | logout(): void { 47 | this.create({}, {}, 'auth/logout').finally(() => { 48 | this.removePayload(); 49 | this.toastr.showToast("You've been logged out.", 'Unauthorized', 'error'); 50 | setTimeout(() => this.router.navigate(['auth/login']), 1000); 51 | }); 52 | } 53 | 54 | /** 55 | * Returns tokens stream 56 | * @returns {Observable} 57 | */ 58 | get onTokenChange$(): Observable { 59 | return this._onTokenChange$.asObservable(); 60 | } 61 | 62 | /** 63 | * Returns true if a valid auth token is present in the LocalStorage 64 | * @returns {Observable} 65 | */ 66 | isAuthenticated(): Observable { 67 | return this.getToken().pipe(map((token: string) => !!token)); 68 | } 69 | 70 | /** 71 | * returns current token as an observable stream 72 | * @returns {Observable} 73 | */ 74 | getToken(): Observable { 75 | return of(localStorage.getItem(this.TOKEN_NAME)); 76 | } 77 | 78 | /** 79 | * stores login payload in localStorage. 80 | * Internal use only. 81 | */ 82 | private storePayload(payload: ILoginPayload): void { 83 | localStorage.setItem(this.TOKEN_NAME, payload.token); 84 | localStorage.setItem(this.PAYLOAD_NAME, payload.user.username); 85 | } 86 | 87 | getPayload(): string { 88 | return localStorage.getItem(this.PAYLOAD_NAME); 89 | } 90 | 91 | /** 92 | * removes token from localStorage 93 | */ 94 | public removePayload(): void { 95 | localStorage.removeItem(this.TOKEN_NAME); 96 | localStorage.removeItem(this.PAYLOAD_NAME); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/app/@theme/components/echarts-pie/echarts-pie.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | OnDestroy, 4 | Input, 5 | Output, 6 | EventEmitter, 7 | ChangeDetectionStrategy, 8 | OnChanges, 9 | ChangeDetectorRef, 10 | } from '@angular/core'; 11 | import { NbThemeService } from '@nebular/theme'; 12 | import { Subscription } from 'rxjs'; 13 | 14 | @Component({ 15 | selector: 'ngx-echarts-pie', 16 | templateUrl: './echarts-pie.component.html', 17 | changeDetection: ChangeDetectionStrategy.OnPush, 18 | }) 19 | export class EchartsPieComponent implements OnChanges, OnDestroy { 20 | options: any; 21 | private themeSubscription: Subscription; 22 | 23 | @Input() pieChartData: any; 24 | @Input() pieChartName: any; 25 | @Output() onPieSelect: EventEmitter = new EventEmitter(); 26 | 27 | constructor( 28 | private readonly theme: NbThemeService, 29 | private readonly cdRef: ChangeDetectorRef 30 | ) {} 31 | 32 | downloadPieChart(): void { 33 | alert('Right click on the chart -> "Save image as"'); 34 | } 35 | 36 | ngOnChanges(): void { 37 | if (this.pieChartData === null || this.pieChartData === undefined) { 38 | return; 39 | } 40 | this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { 41 | this.cdRef.markForCheck(); // so theme gets updated 42 | const colors = config.variables; 43 | const echarts: any = config.variables.echarts; 44 | 45 | this.options = { 46 | backgroundColor: echarts.bg, 47 | color: [ 48 | colors.warningLight as string, 49 | colors.dangerLight as string, 50 | colors.primaryLight as string, 51 | colors.successLight as string, 52 | colors.infoLight as string, 53 | ], 54 | tooltip: { 55 | trigger: 'item', 56 | formatter: '{b} : {c} ({d}%)', 57 | }, 58 | legend: { 59 | orient: 'horizontal', 60 | data: this.pieChartData.map((d) => { 61 | return d.name; 62 | }), 63 | textStyle: { 64 | color: echarts.textColor, 65 | }, 66 | }, 67 | textStyle: { 68 | color: echarts.textColor, 69 | }, 70 | series: [ 71 | { 72 | name: this.pieChartName, 73 | type: 'pie', 74 | radius: '35%', 75 | height: 375, 76 | center: ['50%', '50%'], 77 | data: this.pieChartData, 78 | emphasis: { 79 | itemStyle: { 80 | shadowBlur: 10, 81 | shadowOffsetX: 0, 82 | shadowColor: echarts.itemHoverShadowColor, 83 | }, 84 | }, 85 | labelLine: { 86 | lineStyle: { 87 | color: echarts.axisLineColor, 88 | }, 89 | }, 90 | }, 91 | ], 92 | }; 93 | }); 94 | } 95 | 96 | ngOnDestroy(): void { 97 | this.themeSubscription && this.themeSubscription.unsubscribe(); 98 | } 99 | 100 | onChartMouseDown(event): void { 101 | this.onPieSelect.emit(event.data); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "deprecation": { 7 | "severity": "warning" 8 | }, 9 | "trailing-comma": [ 10 | true, 11 | { 12 | "multiline":{ 13 | "objects": "always", 14 | "arrays": "always", 15 | "functions": "never", 16 | "typeLiterals": "ignore" 17 | }, 18 | "singleline": "never" 19 | } 20 | ], 21 | "arrow-return-shorthand": true, 22 | "callable-types": true, 23 | "class-name": true, 24 | "comment-format": [ 25 | true, 26 | "check-space" 27 | ], 28 | "eofline": true, 29 | "forin": true, 30 | "import-blacklist": [ 31 | true 32 | ], 33 | "import-spacing": true, 34 | "indent": [ 35 | true, 36 | "spaces", 37 | 2 38 | ], 39 | "interface-over-type-literal": true, 40 | "label-position": true, 41 | "max-line-length": [ 42 | true, 43 | 140 44 | ], 45 | "member-access": false, 46 | "no-arg": true, 47 | "no-console": [ 48 | true, 49 | "debug", 50 | "log", 51 | "time", 52 | "timeEnd", 53 | "trace" 54 | ], 55 | "no-construct": true, 56 | "no-debugger": true, 57 | "no-duplicate-super": true, 58 | "no-empty": false, 59 | "no-empty-interface": true, 60 | "no-eval": true, 61 | "no-misused-new": true, 62 | "no-non-null-assertion": true, 63 | "no-shadowed-variable": true, 64 | "no-string-literal": false, 65 | "no-string-throw": true, 66 | "no-switch-case-fall-through": true, 67 | "no-trailing-whitespace": true, 68 | "no-unnecessary-initializer": true, 69 | "no-var-keyword": true, 70 | "object-literal-sort-keys": false, 71 | "one-line": [ 72 | true, 73 | "check-open-brace", 74 | "check-catch", 75 | "check-else", 76 | "check-whitespace" 77 | ], 78 | "prefer-const": true, 79 | "quotemark": [ 80 | true, 81 | "single", 82 | "avoid-escape" 83 | ], 84 | "radix": true, 85 | "semicolon": [ 86 | true, 87 | "always" 88 | ], 89 | "triple-equals": [ 90 | true, 91 | "allow-null-check" 92 | ], 93 | "typedef-whitespace": [ 94 | true, 95 | { 96 | "call-signature": "nospace", 97 | "index-signature": "nospace", 98 | "parameter": "nospace", 99 | "property-declaration": "nospace", 100 | "variable-declaration": "nospace" 101 | } 102 | ], 103 | "unified-signatures": true, 104 | "variable-name": false, 105 | "whitespace": [ 106 | true, 107 | "check-branch", 108 | "check-decl", 109 | "check-operator", 110 | "check-separator", 111 | "check-type" 112 | ], 113 | "directive-selector": [ 114 | true, 115 | "attribute", 116 | "camelCase" 117 | ], 118 | "component-selector": [ 119 | true, 120 | "element", 121 | "kebab-case" 122 | ], 123 | "ban": [ 124 | true, 125 | "eval", 126 | "fit", 127 | "fdescribe", 128 | { 129 | "name": "$", 130 | "message": "please don't" 131 | } 132 | ], 133 | "no-inputs-metadata-property": true, 134 | "no-outputs-metadata-property": true, 135 | "no-output-rename": true, 136 | "use-lifecycle-interface": true, 137 | "use-pipe-transform-interface": true, 138 | "component-class-suffix": true, 139 | "directive-class-suffix": true 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/app/@theme/components/echarts-tree/echarts-tree.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | OnDestroy, 4 | Input, 5 | OnInit, 6 | ChangeDetectionStrategy, 7 | ChangeDetectorRef, 8 | } from '@angular/core'; 9 | import { NbThemeService } from '@nebular/theme'; 10 | import { Subscription } from 'rxjs'; 11 | 12 | @Component({ 13 | selector: 'ngx-echarts-tree', 14 | template: ` 15 |
21 | `, 22 | changeDetection: ChangeDetectionStrategy.OnPush, 23 | }) 24 | export class EchartsTreeComponent implements OnInit, OnDestroy { 25 | private themeSubscription: Subscription; 26 | @Input() private treeInputData: any; 27 | public options: any; 28 | 29 | constructor( 30 | private readonly theme: NbThemeService, 31 | private readonly cdRef: ChangeDetectorRef 32 | ) {} 33 | 34 | ngOnInit(): void { 35 | if ( 36 | this.treeInputData === null || 37 | this.treeInputData === undefined || 38 | this.options 39 | ) { 40 | return; 41 | } 42 | this.themeSubscription = this.theme.getJsTheme().subscribe((config) => { 43 | this.cdRef.markForCheck(); // so theme gets updated 44 | const colors = config.variables.echarts; 45 | 46 | this.options = { 47 | tooltip: { 48 | trigger: 'item', 49 | }, 50 | series: [ 51 | { 52 | type: 'tree', 53 | name: 'Analyzers Tree', 54 | // orient: "TB", 55 | data: [this.treeInputData], 56 | top: '0%', 57 | left: '5%', 58 | right: '40%', 59 | bottom: '0%', 60 | // height: "1000", 61 | initialTreeDepth: 2, 62 | symbolSize: 12, 63 | symbol: 'emptyCircle', 64 | expandAndCollapse: true, 65 | animationDuration: 550, 66 | animationDurationUpdate: 750, 67 | 68 | lineStyle: { 69 | color: colors['tooltipBackgroundColor'], 70 | curveness: 0.5, 71 | width: 0.4, 72 | }, 73 | itemStyle: { 74 | color: '#111', 75 | borderColor: colors['tooltipBackgroundColor'], 76 | }, 77 | label: { 78 | normal: { 79 | position: 'top', 80 | verticalAlign: 'top', 81 | align: 'center', 82 | color: colors['textColor'], 83 | lineHeight: -20, 84 | fontSize: 16, 85 | }, 86 | }, 87 | leaves: { 88 | label: { 89 | normal: { 90 | position: 'right', 91 | verticalAlign: 'medium', 92 | align: 'left', 93 | color: colors['textColor'], 94 | lineHeight: 1, 95 | fontSize: 11, 96 | }, 97 | }, 98 | }, 99 | }, 100 | ], 101 | }; 102 | }); 103 | } 104 | 105 | ngOnDestroy(): void { 106 | this.themeSubscription && this.themeSubscription.unsubscribe(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intelowl-ng", 3 | "version": "3.0.1", 4 | "license": "AGPL-3.0", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/intelowlproject/intelowl-ng.git" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/intelowlproject/intelowl-ng/issues" 11 | }, 12 | "scripts": { 13 | "ng": "ng", 14 | "ngcc": "ngcc", 15 | "start": "ng serve --proxy-config proxy.conf.json", 16 | "build": "ng build", 17 | "build:prod": "ng build --prod", 18 | "test": "ng test", 19 | "lint": "ng lint intelowl-ng", 20 | "lint:fix": "ng lint intelowl-ng --fix", 21 | "e2e": "ng e2e", 22 | "prettier:write": "prettier --write .", 23 | "prettier:check": "prettier --check .", 24 | "pretty-quick": "pretty-quick --staged" 25 | }, 26 | "prettier": { 27 | "singleQuote": true, 28 | "trailingComma": "es5", 29 | "overrides": [ 30 | { 31 | "files": "*.scss", 32 | "options": { 33 | "singleQuote": false 34 | } 35 | } 36 | ] 37 | }, 38 | "husky": { 39 | "hooks": { 40 | "pre-commit": "pretty-quick --staged" 41 | } 42 | }, 43 | "dependencies": { 44 | "@angular/animations": "^11.2.14", 45 | "@angular/cdk": "^10.2.1", 46 | "@angular/common": "^11.2.14", 47 | "@angular/compiler": "^11.2.14", 48 | "@angular/core": "^11.2.14", 49 | "@angular/forms": "^11.2.14", 50 | "@angular/platform-browser": "^11.2.14", 51 | "@angular/platform-browser-dynamic": "^11.2.14", 52 | "@angular/router": "^11.2.14", 53 | "@nebular/eva-icons": "7.0.0", 54 | "@nebular/theme": "7.0.0", 55 | "ang-jsoneditor": "^1.10.5", 56 | "bootstrap": "4.5.3", 57 | "core-js": "^3.6.3", 58 | "dexie": "^2.0.4", 59 | "echarts": "^5.1.1", 60 | "eva-icons": "^1.1.3", 61 | "marked": "^3.0.3", 62 | "ng-animate": "^1.0.0", 63 | "ng2-completer": "^9.0.1", 64 | "ng2-smart-table": "^1.7.2", 65 | "ngx-dexie": "^1.1.0", 66 | "ngx-echarts": "^7.0.1", 67 | "normalize.css": "6.0.0", 68 | "pace-js": "1.0.2", 69 | "rxjs": "6.6.2", 70 | "ts-md5": "^1.2.7", 71 | "tslib": "^2.0.0", 72 | "typeface-exo": "0.0.22", 73 | "web-animations-js": "^2.3.2", 74 | "zone.js": "~0.10.2" 75 | }, 76 | "devDependencies": { 77 | "@angular-devkit/build-angular": "^0.1102.13", 78 | "@angular/cli": "^11.2.13", 79 | "@angular/compiler-cli": "^11.2.14", 80 | "@angular/language-service": "11.2.14", 81 | "@juggle/resize-observer": "^3.3.1", 82 | "@types/echarts": "^4.4.2", 83 | "@types/jasmine": "~3.6.0", 84 | "@types/jasminewd2": "2.0.3", 85 | "@types/node": "12.11.1", 86 | "codelyzer": "^6.0.0", 87 | "husky": "4.2.5", 88 | "jasmine-core": "~3.6.0", 89 | "jasmine-spec-reporter": "~5.0.0", 90 | "jsoneditor": "^9.4.1", 91 | "karma": "~6.3.2", 92 | "karma-chrome-launcher": "~3.1.0", 93 | "karma-coverage-istanbul-reporter": "~3.0.2", 94 | "karma-jasmine": "~4.0.0", 95 | "karma-jasmine-html-reporter": "^1.5.0", 96 | "prettier": "2.0.5", 97 | "pretty-quick": "^2.0.1", 98 | "protractor": "~7.0.0", 99 | "ts-node": "~8.3.0", 100 | "tslint": "~6.1.0", 101 | "typescript": "~4.1.5" 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/app/pages/dashboard/dashboard.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Intel Owl: Analyze files, domains, IPs in multiple ways from a single API at 4 | scale. 5 |
6 | 7 |
8 | 9 | 10 |
11 | 16 |
17 | 18 | 19 | 20 |
21 | 26 |
27 | 28 | 29 | 30 |
31 | 36 |
37 | 38 | 39 | 40 |
41 | 46 |
47 |
48 | 49 | 50 | 51 | Click on a pie-chart slice to filter jobs list 52 | 53 | 54 | 55 | 56 | Click on a pie-chart legend to disable it 57 | 58 | 59 | 60 | 61 | Click on a tag in table to only show jobs with that tag 62 | 63 | 64 | 65 | 66 |
67 |
68 | 69 | 70 | Jobs - count: {{ source.count() }} 71 | {{ filterField }}: {{ filterEl }} 74 | 75 | 84 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
98 |
99 |
100 | -------------------------------------------------------------------------------- /src/app/@core/models/models.ts: -------------------------------------------------------------------------------- 1 | export interface IScanForm { 2 | // required default ones 3 | md5?: string | Int32Array; 4 | analyzers_requested: string[]; 5 | connectors_requested: string[]; 6 | tlp?: 'WHITE' | 'GREEN' | 'AMBER' | 'RED'; 7 | check_existing_or_force?: string; 8 | // extra config 9 | tags_labels: string[]; 10 | classification: 'ip' | 'domain' | 'hash' | 'url' | 'generic' | 'file'; 11 | runtime_configuration?: Object; 12 | // for observable form 13 | observable_name?: string; 14 | // for file form 15 | file?: File; 16 | file_name?: string; 17 | } 18 | 19 | export interface Tag { 20 | id?: number; 21 | label?: string; 22 | color?: string; 23 | } 24 | 25 | export interface Job { 26 | id: number | string; 27 | source: string; 28 | type?: string; 29 | tags: Tag[]; 30 | is_sample?: boolean | string; 31 | md5: string; 32 | observable_name?: string; 33 | observable_classification?: 'ip' | 'domain' | 'hash' | 'url' | 'generic'; 34 | file_name?: string; 35 | file_mimetype?: string; 36 | status: string; 37 | analyzers_requested: string[] | string; 38 | connectors_requested: string[] | string; 39 | analyzers_to_execute: string[]; 40 | connectors_to_execute: string[]; 41 | analyzer_reports?: any[]; 42 | connector_reports?: any[]; 43 | received_request_time: string | Date; 44 | finished_analysis_time?: string | Date; 45 | job_process_time?: number; 46 | errors?: any; 47 | file?: any; 48 | [key: string]: any; 49 | } 50 | 51 | export interface IRecentScan { 52 | jobId: number | string; 53 | status: string; 54 | } 55 | 56 | export interface IRawAnalyzerConfig { 57 | [name: string]: IAnalyzerConfig; 58 | } 59 | 60 | export interface IRawConnectorConfig { 61 | [name: string]: IConnectorConfig; 62 | } 63 | 64 | interface Param { 65 | value: any; 66 | type: string; 67 | description: string; 68 | } 69 | 70 | interface Secret { 71 | env_var_key: string; 72 | description: string; 73 | required: boolean; 74 | } 75 | 76 | export interface IAbstractConfig { 77 | // Abstract for common fields in IAnalyzerConfig and IConnectorConfig 78 | name: string; 79 | python_module: string; 80 | description: string; 81 | config: { 82 | queue: string; 83 | soft_time_limit: number; 84 | }; 85 | params: Record; 86 | secrets: Record; 87 | verification?: { 88 | configured: boolean; 89 | error_message?: string; 90 | missing_secrets: string[]; 91 | }; 92 | } 93 | 94 | export interface IAnalyzerConfig extends IAbstractConfig { 95 | disabled?: boolean; 96 | type: string; 97 | external_service?: boolean; 98 | leaks_info?: boolean; 99 | run_hash?: boolean; 100 | run_hash_type?: string; 101 | // one of supported_filetypes or observable_supported 102 | supported_filetypes?: string[]; 103 | not_supported_filetypes?: string[]; 104 | observable_supported?: string[]; 105 | } 106 | 107 | export interface IConnectorConfig extends IAbstractConfig { 108 | disabled?: boolean; 109 | } 110 | 111 | export interface IAnalyzersList { 112 | ip: IAnalyzerConfig[]; 113 | hash: IAnalyzerConfig[]; 114 | domain: IAnalyzerConfig[]; 115 | url: IAnalyzerConfig[]; 116 | generic: IAnalyzerConfig[]; 117 | file: IAnalyzerConfig[]; 118 | } 119 | 120 | export interface ILoginPayload { 121 | token?: string; 122 | username?: string; 123 | user?: { username?: string }; 124 | } 125 | 126 | export interface HealthCheckStatus { 127 | status?: boolean | null; 128 | } 129 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IntelOwl 6 | 7 | 8 | 9 | 15 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | 113 | 114 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/app/pages/dashboard/job-result/job-info-list/job-info-list.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
9 | 10 | {{ jobObj?.file_name }} 11 |
12 |
17 | 18 | {{ jobObj?.observable_name }} 19 |
20 |
21 | 22 | 26 | 27 |

28 | 29 |
30 |
31 | 32 |
33 | 34 | 35 | 36 | 37 | Job ID 38 | {{ jobObj?.id }} 39 | 40 | 41 | 42 | Source 43 | {{ jobObj?.source }} 44 | 45 | 46 | 47 | Status 48 | {{ jobObj?.status }} 49 | 50 | 51 | 52 | md5 53 |
{{ jobObj?.md5 | json }}
54 |
55 | 56 | 57 | Classification 58 | {{ jobObj?.observable_classification }} 59 | 60 | 61 | File Mimetype 62 | {{ jobObj?.file_mimetype }} 63 | 64 | 65 | 66 | Requested Analyzers 67 |
{{
 68 |           jobObj.analyzers_requested.length
 69 |             ? (jobObj.analyzers_requested | json)
 70 |             : 'all'
 71 |         }}
72 |
73 | 74 | 75 | Requested Connectors 76 |
{{
 77 |           jobObj.connectors_requested.length
 78 |             ? (jobObj.connectors_requested | json)
 79 |             : 'all'
 80 |         }}
81 |
82 | 83 | 84 | Received Request Time 85 | {{ jobObj?.received_request_time }} 88 | 89 | 90 | 91 | Finished Analysis Time 92 | {{ jobObj?.finished_analysis_time }} 95 | 96 | 97 | 98 | Process Time 99 | {{ jobObj?.job_process_time }} seconds 100 | 101 | 102 | 103 | TLP 104 | {{ jobObj?.tlp }} 105 | 106 | 107 | 108 | Errors 109 |
{{ jobObj?.errors | json }}
110 |
111 | 112 | 113 | Runtime Configuration 114 |
{{ runtimeConfiguration | json }}
115 |
116 |
117 |
118 |
119 | -------------------------------------------------------------------------------- /src/app/pages/scans/scans-management/base-scan.component.ts: -------------------------------------------------------------------------------- 1 | import { ScanService } from 'src/app/@core/services/scan.service'; 2 | import { Component, Input, OnInit } from '@angular/core'; 3 | import { NgForm } from '@angular/forms'; 4 | import { 5 | IScanForm, 6 | IAnalyzersList, 7 | IConnectorConfig, 8 | } from 'src/app/@core/models/models'; 9 | import { Md5 } from 'ts-md5'; 10 | import { AnalyzerConfigService } from 'src/app/@core/services/analyzer-config.service'; 11 | import { first } from 'rxjs/operators'; 12 | import { NbDialogService } from '@nebular/theme'; 13 | import { tlpColors } from 'src/app/@theme/components/smart-table/smart-table'; 14 | import { ConnectorConfigService } from 'src/app/@core/services/connector-config.service'; 15 | import { EditConfigParamsDialogComponent } from '../lib/edit-config-params-dialog.component'; 16 | 17 | @Component({ 18 | selector: 'intelowl-base-scan', 19 | templateUrl: './base-scan.component.html', 20 | styles: [ 21 | ` 22 | .json-background { 23 | background-color: #14192f !important; 24 | box-shadow: inherit; 25 | color: #ecedef !important; 26 | } 27 | `, 28 | ], 29 | }) 30 | export class BaseScanFormComponent implements OnInit { 31 | @Input() scanForm: NgForm; 32 | @Input() formData: IScanForm; 33 | analyzersList: IAnalyzersList; 34 | connectorsList: IConnectorConfig[]; 35 | isBtnDisabled: boolean = false; 36 | showSpinnerBool: boolean = false; 37 | formDebugBool: boolean = false; 38 | showDescriptionBool: boolean = true; 39 | 40 | tlpColors = tlpColors; 41 | 42 | constructor( 43 | private readonly scanService: ScanService, 44 | private readonly analyzerService: AnalyzerConfigService, 45 | private readonly connectorService: ConnectorConfigService, 46 | private dialogService: NbDialogService 47 | ) {} 48 | 49 | ngOnInit(): void { 50 | // rxjs/first() -> take first and complete observable 51 | this.analyzerService.analyzersList$ 52 | .pipe(first()) 53 | .subscribe((aList: IAnalyzersList) => (this.analyzersList = aList)); 54 | this.connectorService.connectorsList$ 55 | .pipe(first()) 56 | .subscribe((cList: IConnectorConfig[]) => (this.connectorsList = cList)); 57 | } 58 | 59 | isFormValid(): boolean { 60 | return ( 61 | (this.scanForm.form.status === 'VALID' || 62 | this.scanForm.form.status === 'DISABLED') && 63 | !this.isBtnDisabled 64 | ); 65 | } 66 | 67 | async onScanSubmit(): Promise { 68 | // spinner on 69 | this.showSpinnerBool = true; 70 | if (this.formData.classification === 'file') { 71 | const fr = new FileReader(); 72 | fr.onload = (event) => { 73 | this.formData.md5 = Md5.hashAsciiStr(event.target.result.toString()); 74 | }; 75 | fr.readAsBinaryString(this.formData.file); 76 | fr.onloadend = () => this.scanService.requestScan(this.formData, 'file'); 77 | } else { 78 | this.formData.md5 = Md5.hashStr(this.formData.observable_name); 79 | this.scanService.requestScan(this.formData, 'observable'); 80 | } 81 | // spinner off 82 | setTimeout(() => (this.showSpinnerBool = false), 1000); 83 | } 84 | 85 | /* Additional Config Params */ 86 | editAnalyzerParams(): void { 87 | let configParamsMap = this.formData.analyzers_requested.reduce( 88 | (acc: any, name: string) => ({ 89 | ...acc, 90 | [name]: this.analyzerService.rawAnalyzerConfig[name].params, 91 | }), 92 | {} 93 | ); 94 | if (this.formData?.runtime_configuration) { 95 | configParamsMap = { 96 | ...configParamsMap, 97 | ...this.formData.runtime_configuration, 98 | }; 99 | } 100 | this.dialogService 101 | .open(EditConfigParamsDialogComponent, { 102 | context: { 103 | configParamsMap: configParamsMap, 104 | }, 105 | closeOnEsc: false, 106 | }) 107 | .onClose.pipe(first()) 108 | .subscribe((newConfig) => { 109 | if (newConfig) { 110 | this.formData.runtime_configuration = newConfig; 111 | } 112 | }); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/app/@core/services/job.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpParams } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { BehaviorSubject, Observable } from 'rxjs'; 4 | import { HttpService, IRestQuery } from './http.service'; 5 | import { Job } from '../models/models'; 6 | 7 | @Injectable({ 8 | providedIn: 'root', 9 | }) 10 | export class JobService extends HttpService { 11 | private _jobs$: BehaviorSubject = new BehaviorSubject( 12 | null 13 | ) as BehaviorSubject; 14 | private _jobResult$: BehaviorSubject = new BehaviorSubject( 15 | null 16 | ) as BehaviorSubject; 17 | 18 | constructor(private _httpClient: HttpClient) { 19 | super(_httpClient); 20 | this.initOrRefresh().then(); 21 | } 22 | 23 | get jobs$(): Observable { 24 | return this._jobs$.asObservable(); 25 | } 26 | 27 | get jobResult$(): Observable { 28 | return this._jobResult$.asObservable(); 29 | } 30 | 31 | // not private since it should be accessible for dashboard's 32 | // sync button 33 | async initOrRefresh(): Promise { 34 | try { 35 | const resp: Job[] = await this.query({}, 'jobs'); 36 | resp.map((job) => { 37 | if (job.is_sample) { 38 | job['type'] = 'file'; 39 | } else { 40 | job['type'] = 'observable'; 41 | } 42 | }); 43 | this._jobs$.next(resp); 44 | return Promise.resolve(); 45 | } catch (e) { 46 | console.error(e); 47 | this._jobs$.error(e); 48 | return Promise.reject(e); 49 | } 50 | } 51 | 52 | async pollForJob(id: number, fields: string[] = []): Promise { 53 | console.info( 54 | `Polling for Job with id: ${id} ${ 55 | fields.length ? `, fields: ${fields}` : `` 56 | }` 57 | ); 58 | const query: IRestQuery = {}; 59 | if (fields.length) { 60 | query.params = new HttpParams(); 61 | query.params.set('fields', fields.join(',')); 62 | } 63 | return this.get(id, query, 'jobs') 64 | .then((res: Job) => this._jobResult$.next(res)) 65 | .catch(() => Promise.reject()); 66 | } 67 | 68 | async downloadJobSample(jobId: number): Promise { 69 | try { 70 | const query = { 71 | job_id: jobId, 72 | }; 73 | const blob: Blob = await this.downloadFile( 74 | query, 75 | `jobs/${jobId}/download_sample` 76 | ); 77 | const url: string = window.URL.createObjectURL(blob); 78 | return url; 79 | } catch (e) { 80 | console.error(e); 81 | return Promise.reject(e); 82 | } 83 | } 84 | 85 | async downloadJobRawJson(jobId: number): Promise { 86 | try { 87 | const blob: Blob = await this.downloadFile({}, `jobs/${jobId}`); 88 | const url = window.URL.createObjectURL(blob); 89 | return url; 90 | } catch (e) { 91 | console.error(e); 92 | return Promise.reject(e); 93 | } 94 | } 95 | 96 | async deleteJobById(jobId: number): Promise { 97 | try { 98 | const _answer = await this.delete(jobId, {}, 'jobs'); 99 | // update jobs list 100 | const filteredJobs = this._jobs$.getValue().filter((j) => j.id != jobId); 101 | this._jobs$.next(filteredJobs); 102 | return true; 103 | } catch (e) { 104 | return false; 105 | } 106 | } 107 | 108 | async killJobById(jobId: number): Promise { 109 | try { 110 | const _answer = await this.patch({}, {}, `jobs/${jobId}/kill`); 111 | // update jobs list 112 | const filteredJobs = this._jobs$.getValue().map((j) => { 113 | if (j.id == jobId) j.status = 'killed'; 114 | return j; 115 | }); 116 | this._jobs$.next(filteredJobs); 117 | return true; 118 | } catch (e) { 119 | return false; 120 | } 121 | } 122 | 123 | /* deprecated atm 124 | * See Issue: https://github.com/intelowlproject/IntelOwl-ng/issues/16 125 | */ 126 | /* 127 | private async offlineInit() { 128 | this.indexDB.getAllInstances('jobs').then((res) => { 129 | this._jobs$.next(res); 130 | }); 131 | } 132 | */ 133 | } 134 | -------------------------------------------------------------------------------- /src/app/pages/plugins/connectors-management/tabs/connectors-table.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { LocalDataSource } from 'ng2-smart-table'; 3 | import { first } from 'rxjs/operators'; 4 | import { ConnectorConfigService } from 'src/app/@core/services/connector-config.service'; 5 | import { 6 | TickCrossRenderComponent, 7 | ConfiguredIconComponent, 8 | TLPRenderComponent, 9 | DescriptionRenderComponent, 10 | PopoverOnCellHoverComponent, 11 | tableBooleanInverseFilter, 12 | } from 'src/app/@theme/components/smart-table/smart-table'; 13 | import { 14 | PluginHealthCheckButtonRenderComponent, 15 | PluginInfoCardComponent, 16 | } from '../../lib/components'; 17 | 18 | @Component({ 19 | template: ` 20 | 26 | 27 | Connectors - count: {{ tableSource.count() }} 28 | 29 | 30 | 31 | 32 | 33 | 34 | `, 35 | }) 36 | export class ConnectorsTableComponent implements OnInit { 37 | // ng2-smart-table data source 38 | tableSource: LocalDataSource = new LocalDataSource(); 39 | showSpinnerBool: boolean = false; 40 | 41 | // ng2-smart-table settings 42 | tableSettings = { 43 | actions: { 44 | add: false, 45 | edit: false, 46 | delete: false, 47 | }, 48 | pager: { 49 | display: true, 50 | perPage: 10, 51 | }, 52 | columns: { 53 | name: { 54 | title: 'Name', 55 | }, 56 | description: { 57 | title: 'Description', 58 | type: 'custom', 59 | renderComponent: DescriptionRenderComponent, 60 | }, 61 | moreInfo: { 62 | title: 'More Info', 63 | type: 'custom', 64 | filter: false, 65 | sort: false, 66 | valuePrepareFunction: (c, r) => ({ 67 | component: PluginInfoCardComponent, 68 | context: { pluginInfo: r }, 69 | }), 70 | renderComponent: PopoverOnCellHoverComponent, 71 | }, 72 | disabled: { 73 | title: 'Active', 74 | type: 'custom', 75 | filter: tableBooleanInverseFilter, 76 | sort: false, 77 | valuePrepareFunction: (c, r) => !c, // disabled = !active 78 | renderComponent: TickCrossRenderComponent, 79 | }, 80 | configured: { 81 | title: 'Configured', 82 | type: 'custom', 83 | filter: false, 84 | sort: false, 85 | valuePrepareFunction: (c, r) => r.verification.configured, 86 | renderComponent: ConfiguredIconComponent, 87 | }, 88 | maximum_tlp: { 89 | title: 'Maximum TLP', 90 | type: 'custom', 91 | renderComponent: TLPRenderComponent, 92 | }, 93 | healthCheck: { 94 | title: 'Health Check', 95 | filter: false, 96 | sort: false, 97 | type: 'custom', 98 | renderComponent: PluginHealthCheckButtonRenderComponent, 99 | valuePrepareFunction: (c, r) => ({ status: c, disabled: false }), 100 | onComponentInitFunction: (instance: any) => { 101 | instance.emitter.subscribe(async (rowData) => { 102 | const status = await this.connectorService.checkConnectorHealth( 103 | rowData['name'] 104 | ); 105 | this.tableSource.update(rowData, { 106 | ...rowData, 107 | healthCheck: status, 108 | }); 109 | }); 110 | }, 111 | }, 112 | }, 113 | }; 114 | 115 | constructor(private readonly connectorService: ConnectorConfigService) {} 116 | 117 | ngOnInit(): void { 118 | this.showSpinnerBool = true; // spinner on 119 | // rxjs/first() -> take first and complete observable 120 | this.connectorService.connectorsList$.pipe(first()).subscribe((res) => { 121 | this.tableSource.load(res); 122 | // default alphabetically sort. 123 | this.tableSource.setSort([{ field: 'name', direction: 'asc' }]); 124 | this.showSpinnerBool = false; // spinner off 125 | }); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/app/pages/dashboard/job-result/job-result.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

No data available for Job ID #{{ jobId }}.

4 |
5 | 6 | 7 |
8 | 9 |
10 | 11 |
12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | This analysis is still running. The result is synced with the 22 | server every 5 seconds. 24 | 25 | 26 | 30 | Analyzer Reports 31 | {{ generateReportTableMetrics('analyzer') }} 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 50 | 51 | {{ generateAlertMsgForConnectorReports() }} 52 | 53 | 54 | 55 | 56 | The connectors are still running. The result is synced with the 58 | server every 15 seconds. 60 | 61 | 62 | 66 | Connector Reports 67 | {{ generateReportTableMetrics('connector') }} 68 | 69 | 70 | 76 | 77 | 78 | 79 | 80 | 81 |
82 |
83 | 84 | 85 | 86 | 87 | 88 | Selected: {{ selectedRowName }} 89 | 90 | 91 | 92 |
93 | 94 |
95 | 96 |
97 | 100 |
101 |
102 |
103 |
104 |
105 | 106 | 107 |

Loading...

108 |
109 | -------------------------------------------------------------------------------- /src/app/@core/auth/login/login.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | IntelOwl logo 9 | IntelOwl logo 15 | 16 | 17 | 18 | 19 | 20 | IntelOwl Logo 25 | 26 |

Login

27 |

Hello! Log in with your username.

28 | 29 |
30 | 31 |

Oh snap!

32 |
    33 |
  • 34 | {{ error }} 35 |
  • 36 |
37 |
38 | 39 | 40 |

Hooray!

41 |
    42 |
  • 43 | {{ message }} 44 |
  • 45 |
46 |
47 |
48 | 49 | 64 | 65 |

66 | username is required! 67 |

68 |
69 |
70 | 71 |
72 | 73 | 74 | 75 | 94 | 95 |

96 | Password is required! 97 |

98 |

102 | Password should contain from 4 to 64 characters 103 |

104 |
105 |
106 | 107 | 117 |
118 | 119 |
120 | Don't have an account? 121 | Contact the administrator for access. 122 |
123 |
124 |
125 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "intelowl-ng": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "intelowl", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "style": "scss" 14 | } 15 | }, 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "preserveSymlinks": true, 21 | "outputPath": "dist", 22 | "index": "src/index.html", 23 | "main": "src/main.ts", 24 | "tsConfig": "tsconfig.app.json", 25 | "aot": true, 26 | "polyfills": "src/polyfills.ts", 27 | "assets": [ 28 | "src/assets" 29 | ], 30 | "styles": [ 31 | "node_modules/bootstrap/dist/css/bootstrap.css", 32 | "node_modules/typeface-exo/index.css", 33 | "node_modules/pace-js/templates/pace-theme-flash.tmpl.css", 34 | "src/app/@theme/styles/styles.scss" 35 | ], 36 | "scripts": [ 37 | "node_modules/pace-js/pace.min.js" 38 | ] 39 | }, 40 | "configurations": { 41 | "production": { 42 | "budgets": [ 43 | { 44 | "type": "anyComponentStyle", 45 | "maximumWarning": "6kb" 46 | } 47 | ], 48 | "optimization": true, 49 | "outputHashing": "all", 50 | "sourceMap": false, 51 | "namedChunks": false, 52 | "aot": true, 53 | "extractLicenses": true, 54 | "vendorChunk": false, 55 | "buildOptimizer": true, 56 | "fileReplacements": [ 57 | { 58 | "replace": "src/environments/environment.ts", 59 | "with": "src/environments/environment.prod.ts" 60 | } 61 | ] 62 | } 63 | } 64 | }, 65 | "serve": { 66 | "builder": "@angular-devkit/build-angular:dev-server", 67 | "options": { 68 | "browserTarget": "intelowl-ng:build" 69 | }, 70 | "configurations": { 71 | "production": { 72 | "browserTarget": "intelowl-ng:build:production" 73 | } 74 | } 75 | }, 76 | "extract-i18n": { 77 | "builder": "@angular-devkit/build-angular:extract-i18n", 78 | "options": { 79 | "browserTarget": "intelowl-ng:build" 80 | } 81 | }, 82 | "test": { 83 | "builder": "@angular-devkit/build-angular:karma", 84 | "options": { 85 | "main": "src/test.ts", 86 | "karmaConfig": "./karma.conf.js", 87 | "polyfills": "src/polyfills.ts", 88 | "tsConfig": "tsconfig.spec.json", 89 | "scripts": [ 90 | "node_modules/pace-js/pace.min.js" 91 | ], 92 | "styles": [ 93 | "node_modules/bootstrap/dist/css/bootstrap.css", 94 | "node_modules/typeface-exo/index.css", 95 | "node_modules/pace-js/templates/pace-theme-flash.tmpl.css", 96 | "src/app/@theme/styles/styles.scss" 97 | ], 98 | "assets": [ 99 | "src/assets" 100 | ] 101 | } 102 | }, 103 | "lint": { 104 | "builder": "@angular-devkit/build-angular:tslint", 105 | "options": { 106 | "tsConfig": [ 107 | "tsconfig.app.json", 108 | "tsconfig.spec.json" 109 | ], 110 | "typeCheck": true, 111 | "exclude": [] 112 | } 113 | }, 114 | "e2e": { 115 | "builder": "@angular-devkit/build-angular:protractor", 116 | "options": { 117 | "protractorConfig": "e2e/protractor.conf.js", 118 | "devServerTarget": "intelowl-ng:serve" 119 | }, 120 | "configurations": { 121 | "production": { 122 | "devServerTarget": "intelowl-ng:serve:production" 123 | } 124 | } 125 | } 126 | } 127 | }}, 128 | "defaultProject": "intelowl-ng" 129 | } 130 | -------------------------------------------------------------------------------- /src/app/@theme/components/ngx-tagger/ngx-tagger.component.html: -------------------------------------------------------------------------------- 1 |
2 | 15 | 20 | {{ t?.label }} 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Tags 33 | 34 | 35 | 36 |
37 | 42 | {{ t?.label }} 43 | 47 | 48 | 49 |
50 |
51 | 52 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | 73 | Create/Edit Tag 74 | 75 | 76 | 77 |
78 |
79 | 80 | 92 | 93 | 96 |

97 | Label is required. 98 |

99 |

100 | Label must be at least 3 characters long. 101 |

102 |
103 |
104 | 105 |
106 | 107 | 117 |
118 |
119 | 129 |
130 |
131 |
132 |
133 |
134 | -------------------------------------------------------------------------------- /src/app/@core/services/http.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | HttpClient, 3 | HttpHeaders, 4 | HttpParams, 5 | HttpResponse, 6 | } from '@angular/common/http'; 7 | import { Observable } from 'rxjs'; 8 | import { first } from 'rxjs/operators'; 9 | import { environment } from '../../../environments/environment'; 10 | 11 | export type IRestTransform = (response: HttpResponse) => any; 12 | 13 | export interface IRestQuery { 14 | [key: string]: any; 15 | } 16 | 17 | export interface IOption { 18 | headers?: 19 | | HttpHeaders 20 | | { 21 | [header: string]: string | string[]; 22 | }; 23 | observe?: 'body'; 24 | params?: 25 | | HttpParams 26 | | { 27 | [param: string]: string | string[]; 28 | }; 29 | reportProgress?: boolean; 30 | responseType: 'json'; 31 | withCredentials?: boolean; 32 | } 33 | 34 | export abstract class HttpService { 35 | protected transform: IRestTransform; 36 | protected http: HttpClient; 37 | private readonly _base: string = environment.api; 38 | 39 | protected constructor(http: HttpClient) { 40 | this.http = http; 41 | } 42 | 43 | protected static buildRequestOptions( 44 | query?: any, 45 | responseType?: string, 46 | observe?: string 47 | ): IOption { 48 | for (const i in query) { 49 | if (query.hasOwnProperty(i)) { 50 | if (query[i] === undefined || query[i] === null) { 51 | delete query[i]; 52 | } 53 | } 54 | } 55 | return { 56 | responseType: responseType ? responseType : 'json', 57 | params: query, 58 | withCredentials: false, 59 | observe: observe ? observe : 'body', 60 | }; 61 | } 62 | 63 | public query(query?: IRestQuery, url?: string): Promise { 64 | const request: Observable = this.http.get( 65 | this.buildUrl(undefined, url), 66 | HttpService.buildRequestOptions(query) 67 | ); 68 | return new Promise((resolve, reject) => 69 | request.pipe(first()).subscribe(resolve, reject) 70 | ); 71 | } 72 | 73 | public downloadFile(query?: IRestQuery, url?: string): Promise { 74 | const request: Observable = this.http.get( 75 | this.buildUrl(undefined, url), 76 | HttpService.buildRequestOptions(query, 'blob') 77 | ); 78 | return new Promise((resolve, reject) => 79 | request.pipe(first()).subscribe(resolve, reject) 80 | ); 81 | } 82 | 83 | public get( 84 | id: string | number, 85 | query?: IRestQuery, 86 | url?: string 87 | ): Promise { 88 | const request: Observable = this.http.get( 89 | this.buildUrl(id, url), 90 | HttpService.buildRequestOptions(query) 91 | ); 92 | return new Promise((resolve, reject) => 93 | request.pipe(first()).subscribe(resolve, reject) 94 | ); 95 | } 96 | 97 | public create(obj: T, query?: IRestQuery, url?: string): Promise { 98 | const request: Observable = this.http.post( 99 | this.buildUrl(undefined, url), 100 | obj, 101 | HttpService.buildRequestOptions(query) 102 | ); 103 | return new Promise((resolve, reject) => 104 | request.pipe(first()).subscribe(resolve, reject) 105 | ); 106 | } 107 | 108 | public update( 109 | id: string | number, 110 | obj: T, 111 | query?: IRestQuery, 112 | url?: string 113 | ): Promise { 114 | const request: Observable = this.http.put( 115 | this.buildUrl(id, url), 116 | obj, 117 | HttpService.buildRequestOptions(query) 118 | ); 119 | return new Promise((resolve, reject) => 120 | request.pipe(first()).subscribe(resolve, reject) 121 | ); 122 | } 123 | 124 | public patch(obj: T, query?: IRestQuery, url?: string): Promise { 125 | const request: Observable = this.http.patch( 126 | this.buildUrl(undefined, url), 127 | obj, 128 | HttpService.buildRequestOptions(query) 129 | ); 130 | return request.toPromise(); 131 | } 132 | 133 | public delete( 134 | id: string | number, 135 | query?: IRestQuery, 136 | url?: string 137 | ): Promise { 138 | const request: Observable = this.http.delete( 139 | this.buildUrl(id, url), 140 | HttpService.buildRequestOptions(query, 'json', 'response') 141 | ); 142 | return request.toPromise(); 143 | } 144 | 145 | protected buildUrl(id?: string | number, newUrl?: string): string { 146 | let url: string = newUrl ? newUrl : '/'; 147 | if (id) { 148 | url += `/${id}`; 149 | } 150 | url = `${this._base}${url}`; 151 | return url; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/app/@core/services/scan.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { IndexedDbService } from './indexdb.service'; 4 | import { HttpService } from './http.service'; 5 | import { IRecentScan, IScanForm } from '../models/models'; 6 | import { ToastService } from './toast.service'; 7 | import { JobService } from './job.service'; 8 | 9 | @Injectable({ 10 | providedIn: 'root', 11 | }) 12 | export class ScanService extends HttpService { 13 | public recentScans: Map = new Map(); 14 | 15 | constructor( 16 | private toastr: ToastService, 17 | private _httpClient: HttpClient, 18 | private indexedDB: IndexedDbService, 19 | private jobService: JobService 20 | ) { 21 | super(_httpClient); 22 | // load recent scans 23 | this.indexedDB.getRecentScans().then((arr: IRecentScan[]) => { 24 | arr.forEach((o: IRecentScan) => this.recentScans.set(o.jobId, o.status)); 25 | }); 26 | } 27 | 28 | // only this function is callable from the components 29 | public async requestScan(rqstData: IScanForm, type: string): Promise { 30 | let res; 31 | try { 32 | if (rqstData.check_existing_or_force === 'force_new') { 33 | res = await this._newScan(rqstData, type); 34 | } else { 35 | const exists: boolean = await this._checkInExistingScans(rqstData); 36 | if (!exists) { 37 | res = await this._newScan(rqstData, type); 38 | } 39 | } 40 | // handle response/error 41 | if (res['status'] === 'accepted' || res['status'] === 'running') { 42 | this.onSuccess(res); 43 | } else { 44 | this.onError(res); 45 | } 46 | } catch (e) { 47 | this.onError(e); 48 | } 49 | } 50 | 51 | private async _checkInExistingScans(data: IScanForm): Promise { 52 | const obj = { 53 | md5: data.md5, 54 | analyzers: data.analyzers_requested, 55 | }; 56 | if (data.check_existing_or_force === 'running_only') { 57 | obj['running_only'] = 'True'; 58 | } 59 | const answer = await this.create(obj, {}, 'ask_analysis_availability'); 60 | if (answer.status === 'not_available') { 61 | return false; 62 | } else { 63 | // tslint:disable-next-line: radix 64 | const jobId = parseInt(answer.job_id); 65 | this.recentScans.set(jobId, 'primary'); 66 | this.indexedDB.addToRecentScans({ 67 | jobId: jobId, 68 | status: 'primary', 69 | } as IRecentScan); 70 | this.toastr.showToast(`Job #${jobId}`, 'Scan Already Exists!', 'info'); 71 | return true; 72 | } 73 | } 74 | 75 | private async _newScan(data: IScanForm, type: string): Promise { 76 | const obj: any = { 77 | analyzers_requested: data.analyzers_requested, 78 | connectors_requested: data.connectors_requested, 79 | tlp: data.tlp, 80 | tags_labels: data.tags_labels || [], 81 | }; 82 | if (type === 'observable') { 83 | obj.is_sample = false; 84 | obj.observable_name = data.observable_name; 85 | obj.observable_classification = data.classification; 86 | obj.runtime_configuration = data.runtime_configuration; 87 | return this._createObservableScan(obj); 88 | } else { 89 | obj.is_sample = true; 90 | obj.file_name = data.file_name; 91 | return this._createFileScan(obj, data.file, data.runtime_configuration); 92 | } 93 | } 94 | 95 | // should never be called without context 96 | private async _createObservableScan(obj: any): Promise { 97 | return this.create(obj, {}, 'analyze_observable'); 98 | } 99 | 100 | // should never be called without context 101 | private async _createFileScan( 102 | obj: any, 103 | file: File, 104 | runtimeCfg: any 105 | ): Promise { 106 | const postFormData: FormData = new FormData(); 107 | for (const key in obj) { 108 | if (obj.hasOwnProperty(key)) { 109 | if (Array.isArray(obj[key])) { 110 | obj[key].forEach((el) => postFormData.append(key, el)); 111 | } else { 112 | postFormData.append(key, obj[key]); 113 | } 114 | } 115 | } 116 | if (runtimeCfg != null && Object.keys(runtimeCfg).length) { 117 | postFormData.append('runtime_configuration', JSON.stringify(runtimeCfg)); 118 | } 119 | postFormData.append('file', file, obj.file_name); 120 | return this.create(postFormData, {}, 'analyze_file'); 121 | } 122 | 123 | private onSuccess(res: any): void { 124 | // refresh the job list asynchronously 125 | setTimeout(() => this.jobService.initOrRefresh(), 0); 126 | // show success toast 127 | this.toastr.showToast( 128 | `Job ID: ${res.job_id}`, 129 | 'Analysis running!', 130 | 'success' 131 | ); 132 | // add to recent scans 133 | this.recentScans.set(res.job_id, 'success'); 134 | this.indexedDB.addToRecentScans({ 135 | jobId: res.job_id, 136 | status: 'success', 137 | } as IRecentScan); 138 | } 139 | 140 | private onError(e: any): void { 141 | const msg: string = e?.message || e.toString(); 142 | // show error/danger toast 143 | this.toastr.showToast( 144 | `${msg} (${e.status}: ${e.statusText})`, 145 | 'Scan Request Failed!', 146 | 'error' 147 | ); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/app/pages/plugins/analyzers-management/tabs/analyzers-table.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { first } from 'rxjs/operators'; 3 | import { LocalDataSource } from 'ng2-smart-table'; 4 | import { AnalyzerConfigService } from '../../../../@core/services/analyzer-config.service'; 5 | import { 6 | TickCrossRenderComponent, 7 | ConfiguredIconComponent, 8 | ListCellComponent, 9 | PopoverOnCellHoverComponent, 10 | tableBooleanFilter, 11 | tableBooleanInverseFilter, 12 | } from '../../../../@theme/components/smart-table/smart-table'; 13 | import { 14 | PluginInfoCardComponent, 15 | PluginHealthCheckButtonRenderComponent, 16 | } from '../../lib/components'; 17 | 18 | @Component({ 19 | template: ` 20 | 25 | 26 | Analyzers - count: {{ tableSource.count() }} 27 | 28 | 29 | 30 | 31 | 32 | 33 | `, 34 | }) 35 | export class AnalyzersTableComponent implements OnInit { 36 | // ng2-smart-table data source 37 | tableSource: LocalDataSource = new LocalDataSource(); 38 | showSpinnerBool: boolean = false; 39 | 40 | // ng2-smart-table settings 41 | tableSettings = { 42 | actions: { 43 | add: false, 44 | edit: false, 45 | delete: false, 46 | }, 47 | pager: { 48 | display: true, 49 | perPage: 10, 50 | }, 51 | columns: { 52 | name: { 53 | title: 'Name', 54 | width: '10%', 55 | }, 56 | description: { 57 | title: 'More Info', 58 | type: 'custom', 59 | width: '5%', 60 | filter: false, 61 | sort: false, 62 | valuePrepareFunction: (c, r) => ({ 63 | component: PluginInfoCardComponent, 64 | context: { pluginInfo: r }, 65 | }), 66 | renderComponent: PopoverOnCellHoverComponent, 67 | }, 68 | disabled: { 69 | title: 'Active', 70 | type: 'custom', 71 | width: '3%', 72 | filter: tableBooleanInverseFilter, 73 | sort: false, 74 | valuePrepareFunction: (c, r) => !c, // disabled = !active 75 | renderComponent: TickCrossRenderComponent, 76 | }, 77 | configured: { 78 | title: 'Configured', 79 | type: 'custom', 80 | width: '3%', 81 | filter: false, 82 | sort: false, 83 | valuePrepareFunction: (c, r) => r.verification.configured, 84 | renderComponent: ConfiguredIconComponent, 85 | }, 86 | type: { 87 | title: 'Type', 88 | width: '5%', 89 | sort: false, 90 | filter: { 91 | type: 'list', 92 | config: { 93 | list: [ 94 | { value: 'observable', title: 'observable' }, 95 | { value: 'file', title: 'file' }, 96 | ], 97 | }, 98 | }, 99 | }, 100 | supports: { 101 | title: 'Supported types', 102 | type: 'custom', 103 | width: '15%', 104 | renderComponent: ListCellComponent, 105 | }, 106 | external_service: { 107 | title: 'External Service', 108 | type: 'custom', 109 | width: '3%', 110 | filter: tableBooleanFilter, 111 | sort: false, 112 | renderComponent: TickCrossRenderComponent, 113 | }, 114 | leaks_info: { 115 | title: 'Leaks Info', 116 | type: 'custom', 117 | width: '3%', 118 | filter: tableBooleanFilter, 119 | sort: false, 120 | renderComponent: TickCrossRenderComponent, 121 | }, 122 | healthCheck: { 123 | title: 'Health Check', 124 | type: 'custom', 125 | width: '3%', 126 | filter: false, 127 | sort: false, 128 | renderComponent: PluginHealthCheckButtonRenderComponent, 129 | valuePrepareFunction: (c, r) => ({ 130 | status: c, 131 | disabled: !r.docker_based, 132 | }), 133 | onComponentInitFunction: (instance: any) => { 134 | instance.emitter.subscribe(async (rowData) => { 135 | const status = await this.analyzerService.checkAnalyzerHealth( 136 | rowData['name'] 137 | ); 138 | this.tableSource.update(rowData, { 139 | ...rowData, 140 | healthCheck: status, 141 | }); 142 | }); 143 | }, 144 | }, 145 | }, 146 | }; 147 | 148 | constructor(private readonly analyzerService: AnalyzerConfigService) {} 149 | 150 | ngOnInit(): void { 151 | this.showSpinnerBool = true; // spinner on 152 | // rxjs/first() -> take first and complete observable 153 | // analyzerList available => rawAnalyzerConfig initialized 154 | this.analyzerService.analyzersList$.pipe(first()).subscribe((res) => { 155 | const data: any[] = this.analyzerService.constructTableData(); 156 | this.tableSource.load(data); 157 | // default alphabetically sort. 158 | this.tableSource.setSort([{ field: 'name', direction: 'asc' }]); 159 | this.showSpinnerBool = false; // spinner off 160 | }); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/app/pages/plugins/lib/components.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | Input, 4 | OnInit, 5 | OnChanges, 6 | SimpleChanges, 7 | Output, 8 | EventEmitter, 9 | } from '@angular/core'; 10 | import { IAbstractConfig } from 'src/app/@core/models/models'; 11 | import { ViewCell } from 'ng2-smart-table'; 12 | 13 | // Component to render the `secrets` dict 14 | @Component({ 15 | selector: 'plugin-secrets-dict-render', 16 | template: ` 17 |
18 |
    19 |
  • 20 | 21 | {{ secret.key }} 22 |   ({{ secret.value.env_var_key }})   24 | 32 | 33 |
    37 |
  • 38 |
39 | null 40 |
41 | `, 42 | }) 43 | export class PluginSecretsDictRenderComponent { 44 | @Input() pluginSecrets: IAbstractConfig['secrets']; 45 | 46 | isConfigValid = () => Object.keys(this.pluginSecrets).length; 47 | trackByFn = (_index, item) => item.key; 48 | } 49 | 50 | // Component to render the `secrets` dict 51 | @Component({ 52 | selector: 'plugin-params-dict-render', 53 | template: ` 54 |
55 |
    56 |
  • 57 | {{ param.key }}: 58 | {{ param.value.value | json }} 59 |  ({{ param.value.type }}) 60 |
    64 |
  • 65 |
66 | null 67 |
68 | `, 69 | }) 70 | export class PluginParamsDictRenderComponent { 71 | @Input() pluginParams: IAbstractConfig['params']; 72 | 73 | isConfigValid = () => Object.keys(this.pluginParams).length; 74 | trackByFn = (_index, item) => item.key; 75 | } 76 | 77 | // Component to render the `secrets` dict 78 | @Component({ 79 | selector: 'plugin-config-dict-render', 80 | template: ` 81 |
    82 |
  • 83 | {{ configParam.key }}: 84 | {{ configParam.value }} 85 |
  • 86 |
87 | `, 88 | }) 89 | export class PluginConfigDictRenderComponent { 90 | @Input() pluginConfig: IAbstractConfig['config']; 91 | } 92 | 93 | // Component to render card with plugin info 94 | @Component({ 95 | selector: `plugin-info-card`, 96 | template: ` 97 | 98 | 99 | {{ pluginInfo?.name }} 100 | 101 | ( {{ pluginInfo?.python_module }} ) 102 | 103 | 104 | 105 |
106 |
Description
107 |

108 |
109 |
110 |
Configuration
111 | 112 | 113 |
114 |
115 |
Parameters
116 | 119 |
120 |
121 |
Secrets
122 | 125 |
126 |
127 |
128 | Verification 129 | 133 |
134 |
138 | {{ pluginInfo?.verification?.error_message }} 139 |
140 |
141 |
142 |
143 | `, 144 | }) 145 | export class PluginInfoCardComponent { 146 | @Input() pluginInfo: IAbstractConfig; 147 | } 148 | 149 | // Plugin Health Check Button Renderer 150 | @Component({ 151 | template: `
152 | {{ 153 | statusText 154 | }} 155 | 164 |
`, 165 | }) 166 | export class PluginHealthCheckButtonRenderComponent 167 | implements ViewCell, OnInit, OnChanges { 168 | @Input() value: any; 169 | @Input() rowData: any; 170 | 171 | @Output() emitter: EventEmitter = new EventEmitter(); 172 | 173 | statusText: string; 174 | statusColor: string; 175 | disabled: boolean; 176 | 177 | ngOnInit(): void { 178 | this.getIconStatus(); 179 | } 180 | 181 | ngOnChanges(changes: SimpleChanges): void { 182 | if (changes.value.previousValue !== changes.value.currentValue) { 183 | this.getIconStatus(); 184 | } 185 | } 186 | 187 | private getIconStatus(): void { 188 | this.disabled = this.value.disabled; 189 | if (this.value.status === true) { 190 | this.statusText = 'healthy'; 191 | this.statusColor = '#29D68F'; 192 | } else if (this.value.status === false) { 193 | this.statusText = 'failing'; 194 | this.statusColor = '#FC3D71'; 195 | } else if (this.value.status === null) { 196 | this.statusText = 'unknown'; 197 | this.statusColor = 'grey'; 198 | } 199 | } 200 | 201 | onClick(e) { 202 | this.emitter.emit(this.rowData); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > :warning: This repository has been archived. IntelOwl has a new GUI written in React.js and its code lives inside the [main repository](https://github.com/intelowlproject/IntelOwl/tree/dev-v4/frontend) only. 2 | 3 | 4 | # IntelOwl-ng 5 | 6 | [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/intelowlproject/IntelOwl-ng.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/intelowlproject/IntelOwl-ng/context:javascript) 7 | [![CodeFactor](https://www.codefactor.io/repository/github/intelowlproject/intelowl-ng/badge)](https://www.codefactor.io/repository/github/intelowlproject/intelowl-ng) 8 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) 9 | 10 | Official web client for [Intel Owl](https://github.com/intelowlproject/intelowl), a scalable API which gathers 11 | threat intelligence data about a particular file or observable (ip, domain, url, 12 | hash) by querying many different analyzers and services that are externally or 13 | internally available. 14 | 15 | Built with [Angular 10](https://github.com/angular/angular) on top of [ngx-admin](https://github.com/akveo/ngx-admin). 16 | 17 | #### Demo 18 | 19 | [Live Demo](https://intelowlclient.firebaseapp.com/) hosted on firebase. (_Last Updated for IntelOwl v3.0.0_) 20 | 21 | ## Features 22 | 23 | - A dashboard to display different visualizations of Job data, with the following features: 24 | - Tabular view of all jobs which can be filtered, sorted or searched through. 25 | - Pie charts for visualizing job data on the basis of `status`, `observable_classification`, `file_mimetype` and `is_sample`. 26 | - Clicking on any slice on the Pie Chart will filter the jobs list based on the selected classification. 27 | - “Save as PNG” feature for the graphs. 28 | - Job result can be viewed as a nested list or prettified JSON. 29 | - [`analyzer_config.json`](https://github.com/intelowlproject/IntelOwl/blob/master/configuration/analyzer_config.json) 30 | from IntelOwl in a tabular view which can be filtered, sorted or searched through. Along with this, there's also a dendogram-tree view. 31 | - Requesting new analysis/scans with simple-to-use forms. They take care of warnings for you and 32 | also lets you specify tags to group different analysis' together. 33 | 34 | ## Docker 35 | 36 | IntelOwl-ng's Docker image on [Dockerhub](https://hub.docker.com/r/intelowlproject/intelowl_ng) is a data-only image. In other terms, it's a scratch image that contains only the final production build artifacts and cannot be started as a container. This makes the image super light-weight i.e. ~7 mb uncompressed and we can use Docker's multi-stage builds to inject these build artifacts into another base container such as `nginx`. Official [example](https://github.com/intelowlproject/IntelOwl/blob/develop/Dockerfile_nginx) from Intel Owl repository. 37 | 38 | > You should never need to build/start this docker image yourself, you should always use the docker-compose files from main IntelOwl repository. 39 | 40 | ## Installation 41 | 42 | For a development server, we make use of proxy configuration given in [proxy.config.json](proxy.conf.json) to make calls to the backend server. 43 | Therefore, this application requires Intel Owl running on `http://localhost:80` (by default). If you wish to change this URL, you can do so by changing 44 | the `target` parameter in [proxy.config.json](proxy.conf.json). 45 | 46 | #### Clone this repository 47 | 48 | ```bash 49 | $ git clone https://github.com/intelowlproject/intelowl-ng 50 | $ cd intelowl-ng/ 51 | ``` 52 | 53 | ### Build locally 54 | 55 | ##### Dependencies 56 | 57 | - [node.js](https://github.com/nodejs/node): 58 | `v12.18.0` (Latest LTS) 59 | - any one of: [yarn](https://github.com/yarnpkg/yarn): `v1.22.4` or [npm](https://github.com/npm/npm) 60 | for package management. 61 | 62 | ##### Install packages 63 | 64 | Install the packages described in the `package.json` and 65 | verify that it works: 66 | 67 | ```bash 68 | intelowl-ng$ yarn install 69 | ``` 70 | 71 | or, 72 | 73 | ```bash 74 | intelowl-ng$ npm install 75 | ``` 76 | 77 | ##### Development server 78 | 79 | Run `npm start` or `yarn start` for a dev server. Navigate to `http://localhost:4200/`. The app will 80 | automatically reload if you change any of the source files. Shut it down manually with Ctrl-C. 81 | 82 | ## Developing 83 | 84 | ##### Project structure 85 | 86 | ``` 87 | dist/ compiled version 88 | e2e/ end-to-end tests 89 | src/ project source code 90 | |- app/ app components 91 | | |- @core/ core module (singleton services and single-use components) 92 | | |- models/models.ts various interfaces used 93 | | |- services/ injectable services 94 | | |- @theme/ reusable theme module, reusable components, directives, pipes. 95 | | |- styles/ ngx-admin themes and global scss variables 96 | | |- pages/ app's primary modules and components 97 | | |- app.component.* app root component (shell) 98 | | |- app.module.ts app root module definition 99 | | |- app-routing.module.ts app routes 100 | |- assets/ app assets (images, etc.) 101 | |- environments/ values for various build environments 102 | |- index.html html entry point 103 | |- main.ts app entry point 104 | |- polyfills.ts polyfills needed by Angular 105 | +- test.ts unit tests entry point 106 | README.md project docs and coding guides 107 | Dockerfile multi-staged Dockerfile 108 | ``` 109 | 110 | ##### Libraries 111 | 112 | - [Angular](https://angular.io) - `v10.x` 113 | - [Nebular](https://akveo.github.io/nebular/5.1.0/) - `v6.2.x` 114 | - [ngx-admin](https://github.com/akveo/ngx-admin) 115 | - [RxJS](http://reactivex.io/rxjs) - `v6.6.x` 116 | - [Eva Icons](https://akveo.github.io/eva-icons/) 117 | - [Bootstrap 4](https://getbootstrap.com/docs/4.5/getting-started/introduction/) 118 | - [ng2-smart-table](https://akveo.github.io/ng2-smart-table/#/) 119 | 120 | ##### Code scaffolding 121 | 122 | Run `ng generate component component-name` to generate a new component. 123 | You can also use `ng generate directive/pipe/service/class/module`. 124 | 125 | ##### Build 126 | 127 | Run `ng build` or `yarn build` to build the project. The build artifacts will be 128 | stored in the `dist/` directory. Use the `--prod` flag for a production build. 129 | 130 | ##### Further help 131 | 132 | To get more help on the angular-cli use `ng --help` or go check out the Angular-CLI README. 133 | 134 | ## Contributing 135 | 136 | 1. Please create a new branch based on the `develop` branch that contains the most recent changes. 137 | 138 | ```bash 139 | $ git checkout -b myfeature develop 140 | ``` 141 | 142 | 2. Run this before committing your changes to git. 143 | 144 | ```bash 145 | $ yarn prettier:write 146 | $ yarn lint 147 | ``` 148 | 149 | Fix the linting issues, if there are any. 150 | 151 | 3. Read [this](https://intelowl.readthedocs.io/en/latest/Contribute.html#create-a-pull-request) before submitting a pull request. -------------------------------------------------------------------------------- /src/app/pages/dashboard/dashboard.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy } from '@angular/core'; 2 | import { 3 | JobStatusIconRenderComponent, 4 | ViewResultButtonComponent, 5 | TagsRenderComponent, 6 | } from '../../@theme/components/smart-table/smart-table'; 7 | import { LocalDataSource } from 'ng2-smart-table'; 8 | import { JobService } from '../../@core/services/job.service'; 9 | import { Job, Tag } from '../../@core/models/models'; 10 | import { Subscription } from 'rxjs'; 11 | import { ToastService } from 'src/app/@core/services/toast.service'; 12 | import { flash } from 'ng-animate'; 13 | import { trigger, transition, useAnimation } from '@angular/animations'; 14 | 15 | @Component({ 16 | templateUrl: './dashboard.component.html', 17 | styleUrls: ['./dashboard.component.scss'], 18 | animations: [ 19 | trigger('dashboardAnimation', [ 20 | transition('false => true', useAnimation(flash)), 21 | ]), 22 | ], 23 | }) 24 | export class DashboardComponent implements OnDestroy { 25 | // animations 26 | flashAnimBool: boolean = false; 27 | private toggleAnimation = () => (this.flashAnimBool = !this.flashAnimBool); 28 | // RxJS subscriptions 29 | private jobSub: Subscription; 30 | private tagSub: Subscription; 31 | private jobs: Job[]; 32 | filterEl: string = null; 33 | filterField: string = null; 34 | iconRotateBool: boolean = false; 35 | 36 | // Pie Chart Data 37 | pieChartData: any = {}; 38 | 39 | // ng2-smart-table data source 40 | source: LocalDataSource = new LocalDataSource(); 41 | 42 | // ng2-smart-table settings 43 | settings = { 44 | actions: { 45 | add: false, 46 | edit: false, 47 | delete: false, 48 | }, 49 | pager: { 50 | display: true, 51 | perPage: 10, 52 | }, 53 | columns: { 54 | result: { 55 | title: 'Result', 56 | type: 'custom', 57 | width: '3%', 58 | filter: false, 59 | sort: false, 60 | renderComponent: ViewResultButtonComponent, 61 | }, 62 | id: { 63 | title: 'id', 64 | width: '3%', 65 | }, 66 | observable_name: { 67 | title: 'Name', 68 | valuePrepareFunction: (c, r) => r.observable_name || r.file_name, 69 | }, 70 | tags: { 71 | title: 'Tags', 72 | type: 'custom', 73 | filter: false, 74 | sort: false, 75 | renderComponent: TagsRenderComponent, 76 | onComponentInitFunction: (instance: any) => 77 | this.filterJobsByTag(instance), 78 | }, 79 | observable_classification: { 80 | title: 'Type', 81 | width: '20%', 82 | filter: false, 83 | valuePrepareFunction: (c, r) => 84 | r.observable_classification || r.file_mimetype, 85 | }, 86 | no_of_analyzers_executed: { 87 | title: 'Analyzers Called', 88 | width: '10%', 89 | filter: false, 90 | }, 91 | process_time: { 92 | title: 'Process Time (s)', 93 | width: '10%', 94 | filter: false, 95 | }, 96 | status: { 97 | title: 'Success', 98 | type: 'custom', 99 | width: '5%', 100 | filter: false, 101 | renderComponent: JobStatusIconRenderComponent, 102 | }, 103 | no_of_connectors_executed: { 104 | title: 'Connectors Called', 105 | width: '10%', 106 | filter: false, 107 | }, 108 | }, 109 | }; 110 | 111 | constructor( 112 | private readonly jobService: JobService, 113 | private readonly toastr: ToastService 114 | ) { 115 | this.jobSub = this.jobService.jobs$.subscribe( 116 | (res: Job[]) => this.initData(res), 117 | async (err: any) => 118 | this.toastr.showToast( 119 | err.message, 120 | 'Failed to fetch Data. Are you online ?', 121 | 'error' 122 | ) 123 | ); 124 | } 125 | 126 | private async initData(res: Job[]): Promise { 127 | this.jobs = res; 128 | if (this.jobs) { 129 | // load data into table 130 | this.source.load(this.jobs); 131 | // construct visualization data 132 | this.pieChartData['status'] = await this.constructPieData( 133 | this.jobs, 134 | 'status' 135 | ); 136 | this.pieChartData['type'] = await this.constructPieData( 137 | this.jobs, 138 | 'type' 139 | ); 140 | this.pieChartData[ 141 | 'observable_classification' 142 | ] = await this.constructPieData(this.jobs, 'observable_classification'); 143 | this.pieChartData['file_mimetype'] = await this.constructPieData( 144 | this.jobs, 145 | 'file_mimetype' 146 | ); 147 | } 148 | } 149 | 150 | private async constructPieData( 151 | jobs: Job[], 152 | field: string 153 | ): Promise> { 154 | const fieldValuesMap: Map = new Map(); 155 | 156 | jobs.forEach((job) => { 157 | let keys: string[] = []; 158 | 159 | if (job[field] === null || job[field] === '') { 160 | return; // jump to next iteration 161 | } else if (job[field] instanceof Object && job[field].length > 0) { 162 | keys = job[field]; 163 | } else { 164 | keys.push(job[field]); 165 | } 166 | 167 | keys.forEach((k) => { 168 | if (fieldValuesMap.get(k) === undefined) { 169 | fieldValuesMap.set(k, 0); 170 | } 171 | fieldValuesMap.set(k, fieldValuesMap.get(k) + 1); 172 | }); 173 | }); 174 | 175 | const pieData: Array<{ name: string; value: number }> = []; 176 | 177 | fieldValuesMap.forEach((v, k) => { 178 | pieData.push({ name: k, value: v }); 179 | }); 180 | 181 | return pieData; 182 | } 183 | 184 | filterJobsByField(el: any, field: string): void { 185 | const filteredJobs = this.jobs.filter((job) => el.name === job[field]); 186 | this.filterField = field; 187 | this.filterEl = el.name; 188 | this.source.load(filteredJobs); 189 | } 190 | 191 | private filterJobsByTag(tagComponentInst: any) { 192 | this.tagSub = tagComponentInst.onTagClick.subscribe((t: Tag) => { 193 | const filteredJobs = this.jobs.filter((job) => 194 | job.tags.some((o) => o.label === t.label) 195 | ); 196 | this.filterField = 'Tag'; 197 | this.filterEl = t.label; 198 | this.source.load(filteredJobs); 199 | }); 200 | } 201 | 202 | async debouncedSync() { 203 | // this function is debounced by 2 seconds 204 | this.iconRotateBool = false; 205 | // reset filters 206 | this.filterField = null; 207 | this.filterEl = null; 208 | this.toggleAnimation(); 209 | this.jobService.initOrRefresh().then( 210 | () => 211 | this.toastr.showToast( 212 | 'Dashboard is updated with latest data', 213 | 'Sync successful!', 214 | 'success' 215 | ), 216 | (err) => this.toastr.showToast(err.message, 'Sync failed!', 'error') 217 | ); 218 | } 219 | 220 | resetFilters(): void { 221 | this.filterField = null; 222 | this.filterEl = null; 223 | this.source.load(this.jobs); 224 | this.source.reset(); 225 | } 226 | 227 | ngOnDestroy(): void { 228 | this.jobSub && this.jobSub.unsubscribe(); 229 | this.tagSub && this.tagSub.unsubscribe(); 230 | } 231 | } 232 | --------------------------------------------------------------------------------