diff --git a/src/commons/addons/carousel/Carousel.tsx b/src/commons/addons/carousel/Carousel.tsx index 6d880d39f..379424e70 100644 --- a/src/commons/addons/carousel/Carousel.tsx +++ b/src/commons/addons/carousel/Carousel.tsx @@ -1,9 +1,8 @@ -/* eslint-disable jsx-a11y/no-static-element-interactions */ import makeStyles from '@mui/styles/makeStyles'; import { isArrowDown, isArrowLeft, isArrowRight, isArrowUp, isEscape } from 'commons/components/utils/keyboard'; +import React, { useEffect, useRef } from 'react'; // TODO: Add in the commons -import React, { useEffect, useRef } from 'react'; const useStyles = makeStyles(theme => ({ container: { @@ -19,7 +18,7 @@ export interface CarouselProps { disableArrowRight?: boolean; escapeCallback?: () => void; enableSwipe?: boolean; - style?: any; + style?: React.CSSProperties; children: React.ReactNode; onNext: () => void; onPrevious: () => void; diff --git a/src/commons/components/app/AppConfigs.ts b/src/commons/components/app/AppConfigs.ts index 976b8c8cb..ba98b00c6 100644 --- a/src/commons/components/app/AppConfigs.ts +++ b/src/commons/components/app/AppConfigs.ts @@ -97,8 +97,8 @@ export type AppBarThemeConfigs = { // Specification interface of describing which appbar styles are configurable. export type AppBarStyles = { - color?: any; // Configure appbar css color style. - backgroundColor?: any; // Configure appbar css background color style. + color?: React.CSSProperties['color']; // Configure appbar css color style. + backgroundColor?: React.CSSProperties['backgroundColor']; // Configure appbar css background color style. }; // Specification interface describing a sitemap route rendered within breadcrumbs. diff --git a/src/commons/components/app/AppSkeleton.tsx b/src/commons/components/app/AppSkeleton.tsx index b71987ced..1b5bc507f 100644 --- a/src/commons/components/app/AppSkeleton.tsx +++ b/src/commons/components/app/AppSkeleton.tsx @@ -2,6 +2,7 @@ import AppsIcon from '@mui/icons-material/Apps'; import { Divider, List, styled, Toolbar, useMediaQuery, useTheme } from '@mui/material'; import Skeleton from '@mui/material/Skeleton'; import useAppLayout from 'commons/components/app/hooks/useAppLayout'; +import { DetailedHTMLProps, HTMLAttributes } from 'react'; import { AppBarBase } from '../topnav/AppBar'; import { AppUserAvatar } from '../topnav/UserProfile'; import { AppLeftNavElement } from './AppConfigs'; @@ -288,12 +289,10 @@ const LeftNavElementsSkeleton = ({ elements, withText }: LeftNavElementsSkeleton ); }; -interface ButtonSkeletonProps { - style: { [styleAttr: string]: any }; - // eslint-disable-next-line react/require-default-props +interface ButtonSkeletonProps extends DetailedHTMLProps, HTMLDivElement> { withText?: boolean; - [propName: string]: any; } + const ButtonSkeleton = ({ style, withText, ...boxProps }: ButtonSkeletonProps) => { const theme = useTheme(); const isXs = useMediaQuery(theme.breakpoints.only('xs')); diff --git a/src/components/hooks/useMyAPI.tsx b/src/components/hooks/useMyAPI.tsx index 16b97d3ad..752b7c5f1 100644 --- a/src/components/hooks/useMyAPI.tsx +++ b/src/components/hooks/useMyAPI.tsx @@ -1,22 +1,23 @@ +import { Configuration } from 'components/models/base/config'; import { getFileName } from 'helpers/utils'; import getXSRFCookie from 'helpers/xsrf'; import { useTranslation } from 'react-i18next'; import useALContext from './useALContext'; import useMySnackbar from './useMySnackbar'; -import { ConfigurationDefinition, WhoAmIProps } from './useMyUser'; +import { WhoAmIProps } from './useMyUser'; const DEFAULT_RETRY_MS = 32; -export type APIResponseProps = { +export type APIResponseProps = { api_error_message: string; - api_response: any; + api_response: APIResponse; api_server_version: string; api_status_code: number; }; -export type DownloadResponseProps = { +export type DownloadResponseProps = { api_error_message: string; - api_response: any; + api_response: APIResponse; api_server_version: string; api_status_code: number; filename?: string; @@ -31,45 +32,51 @@ export type LoginParamsProps = { allow_pw_rest: boolean; }; -export default function useMyAPI() { - const { t } = useTranslation(); - const { showErrorMessage, closeSnackbar } = useMySnackbar(); - const { configuration: systemConfig } = useALContext(); +type APICallProps = { + url: string; + contentType?: string; + method?: string; + body?: any; + reloadOnUnauthorize?: boolean; + allowCache?: boolean; + onSuccess?: (api_data: APIResponseProps) => void; + onFailure?: (api_data: APIResponseProps) => void; + onEnter?: () => void; + onExit?: () => void; + onFinalize?: (api_data: APIResponseProps) => void; + retryAfter?: number; +}; - type APICallProps = { - url: string; - contentType?: string; - method?: string; - body?: any; - reloadOnUnauthorize?: boolean; - allowCache?: boolean; - onSuccess?: (api_data: APIResponseProps) => void; - onFailure?: (api_data: APIResponseProps) => void; - onEnter?: () => void; - onExit?: () => void; - onFinalize?: (api_data: APIResponseProps) => void; - retryAfter?: number; - }; +type BootstrapProps = { + switchRenderedApp: (value: string) => void; + setConfiguration: (cfg: Configuration) => void; + setLoginParams: (params: LoginParamsProps) => void; + setUser: (user: WhoAmIProps) => void; + setReady: (isReady: boolean) => void; + retryAfter?: number; +}; - type BootstrapProps = { - switchRenderedApp: (value: string) => void; - setConfiguration: (cfg: ConfigurationDefinition) => void; - setLoginParams: (params: LoginParamsProps) => void; - setUser: (user: WhoAmIProps) => void; - setReady: (isReady: boolean) => void; - retryAfter?: number; - }; +type DownloadBlobProps = { + url: string; + onSuccess?: (blob: DownloadResponseProps) => void; + onFailure?: (api_data: DownloadResponseProps) => void; + onEnter?: () => void; + onExit?: () => void; + retryAfter?: number; +}; - type DownloadBlobProps = { - url: string; - onSuccess?: (blob: DownloadResponseProps) => void; - onFailure?: (api_data: DownloadResponseProps) => void; - onEnter?: () => void; - onExit?: () => void; - retryAfter?: number; - }; +type UseMyAPIReturn = { + apiCall: (props: APICallProps) => void; + bootstrap: (props: BootstrapProps) => void; + downloadBlob: (props: DownloadBlobProps) => void; +}; - function isAPIData(value: any) { +const useMyAPI = (): UseMyAPIReturn => { + const { t } = useTranslation(); + const { showErrorMessage, closeSnackbar } = useMySnackbar(); + const { configuration: systemConfig } = useALContext(); + + const isAPIData = (value: any): boolean => { if ( value !== undefined && value !== null && @@ -81,21 +88,19 @@ export default function useMyAPI() { return true; } return false; - } + }; - function bootstrap({ + const bootstrap = ({ switchRenderedApp, setConfiguration, setLoginParams, setUser, setReady, retryAfter = DEFAULT_RETRY_MS - }: BootstrapProps) { + }: BootstrapProps) => { const requestOptions: RequestInit = { method: 'GET', - headers: { - 'X-XSRF-TOKEN': getXSRFCookie() - }, + headers: { 'X-XSRF-TOKEN': getXSRFCookie() }, credentials: 'same-origin' }; @@ -167,9 +172,9 @@ export default function useMyAPI() { switchRenderedApp('load'); } }); - } + }; - function apiCall({ + const apiCall = ({ url, contentType = 'application/json', method = 'GET', @@ -182,7 +187,7 @@ export default function useMyAPI() { onExit, onFinalize, retryAfter = DEFAULT_RETRY_MS - }: APICallProps) { + }: APICallProps) => { const requestOptions: RequestInit = { method, credentials: 'same-origin', @@ -300,22 +305,20 @@ export default function useMyAPI() { } if (onFinalize) onFinalize(api_data); }); - } + }; - function downloadBlob({ + const downloadBlob = ({ url, onSuccess, onFailure, onEnter, onExit, retryAfter = DEFAULT_RETRY_MS - }: DownloadBlobProps) { + }: DownloadBlobProps) => { const requestOptions: RequestInit = { method: 'GET', credentials: 'same-origin', - headers: { - 'X-XSRF-TOKEN': getXSRFCookie() - } + headers: { 'X-XSRF-TOKEN': getXSRFCookie() } }; // Run enter callback @@ -409,7 +412,9 @@ export default function useMyAPI() { if (retryAfter !== DEFAULT_RETRY_MS) closeSnackbar(); } }); - } + }; return { apiCall, bootstrap, downloadBlob }; -} +}; + +export default useMyAPI; diff --git a/src/components/hooks/useMyUser.tsx b/src/components/hooks/useMyUser.tsx index 673584c4e..0ed3f3a45 100644 --- a/src/components/hooks/useMyUser.tsx +++ b/src/components/hooks/useMyUser.tsx @@ -1,194 +1,37 @@ -import { AppSwitcherItem } from 'commons/components/app/AppConfigs'; -import { AppUser, AppUserService, AppUserValidatedProp } from 'commons/components/app/AppUserService'; +import { AppUserService, AppUserValidatedProp } from 'commons/components/app/AppUserService'; +import { Configuration } from 'components/models/base/config'; +import { UserSettings } from 'components/models/base/user_settings'; +import { CustomUser, Indexes, SystemMessage } from 'components/models/ui/user'; import { ClassificationDefinition } from 'helpers/classificationParser'; import { useState } from 'react'; -export type ALField = { - name: string; - indexed: boolean; - stored: boolean; - type: string; - default: boolean; - list: boolean; -}; - -type IndexDefinition = { - [propName: string]: ALField; -}; - -type IndexDefinitionMap = { - alert: IndexDefinition; - badlist: IndexDefinition; - file: IndexDefinition; - heuristic: IndexDefinition; - result: IndexDefinition; - retrohunt: IndexDefinition; - safelist: IndexDefinition; - signature: IndexDefinition; - submission: IndexDefinition; - workflow: IndexDefinition; -}; - -type SettingsDefinition = { - classification: string; - deep_scan: boolean; - description: string; - download_encoding: string; - expand_min_score: number; - ignore_cache: boolean; - ignore_dynamic_recursion_prevention: boolean; - ignore_filtering: boolean; - priority: number; - profile: boolean; - service_spec: any[]; - services: any[]; - submission_view: string; - ttl: number; -}; - -export type SystemMessageDefinition = { - user: string; - title: string; - severity: 'success' | 'info' | 'warning' | 'error'; - message: string; -}; - -export type ExternalLink = { - allow_bypass: boolean; - double_encode: boolean; - max_classification: string; - name: string; - replace_pattern: string; - url: string; -}; - -export type ExternalSource = { - max_classification: string; - name: string; -}; - -export type ConfigurationDefinition = { - auth: { - allow_2fa: boolean; - allow_apikeys: boolean; - allow_extended_apikeys: boolean; - allow_security_tokens: boolean; - }; - datastore: { - archive: { - enabled: boolean; - }; - }; - retrohunt: { - enabled: boolean; - dtl: number; - max_dtl: number; - }; - submission: { - dtl: number; - max_dtl: number; - sha256_sources: string[]; - verdicts: { - info: number; - suspicious: number; - highly_suspicious: number; - malicious: number; - }; - }; - system: { - organisation: string; - type: string; - version: string; - }; - ui: { - ai: { - enabled: boolean; - }; - alerting_meta: { - important: string[]; - subject: string[]; - url: string[]; - }; - allow_malicious_hinting: boolean; - allow_raw_downloads: boolean; - allow_replay: boolean; - allow_url_submissions: boolean; - allow_zip_downloads: boolean; - apps: AppSwitcherItem[]; - banner: { - [lang: string]: string; - }; - banner_level: 'info' | 'warning' | 'error' | 'success'; - external_links: { - tag: { [key: string]: ExternalLink[] }; - hash: { [key: string]: ExternalLink[] }; - metadata: { [key: string]: ExternalLink[] }; - }; - external_sources: ExternalSource[]; - external_source_tags: { - [tag_name: string]: string[]; - }; - read_only: boolean; - rss_feeds: string[]; - services_feed: string; - tos: boolean; - tos_lockout: boolean; - tos_lockout_notify: boolean; - url_submission_auto_service_selection: string[]; - }; - user: { - api_priv_map: { - [api_priv: string]: string[]; - }; - priv_role_dependencies: { - [priv: string]: string[]; - }; - roles: string[]; - role_dependencies: { - [role: string]: string[]; - }; - types: string[]; - }; -}; - -export interface CustomUser extends AppUser { - // Al specific props - agrees_with_tos: boolean; - classification: string; - default_view?: string; - dynamic_group: string | null; - groups: string[]; - is_active: boolean; - roles: string[]; -} - export interface CustomAppUserService extends AppUserService { c12nDef: ClassificationDefinition; - configuration: ConfigurationDefinition; - indexes: IndexDefinitionMap; - settings: SettingsDefinition; - systemMessage: SystemMessageDefinition; - setConfiguration: (cfg: ConfigurationDefinition) => void; - setSystemMessage: (msg: SystemMessageDefinition) => void; + configuration: Configuration; + indexes: Indexes; + settings: UserSettings; + systemMessage: SystemMessage; + setConfiguration: (cfg: Configuration) => void; + setSystemMessage: (msg: SystemMessage) => void; scoreToVerdict: (score: number) => string; } export interface WhoAmIProps extends CustomUser { c12nDef: ClassificationDefinition; - configuration: ConfigurationDefinition; - indexes: IndexDefinitionMap; - system_message: SystemMessageDefinition; - settings: SettingsDefinition; + configuration: Configuration; + indexes: Indexes; + system_message: SystemMessage; + settings: UserSettings; } // Application specific hook that will provide configuration to commons [useUser] hook. export default function useMyUser(): CustomAppUserService { const [user, setState] = useState(null); const [c12nDef, setC12nDef] = useState(null); - const [configuration, setConfiguration] = useState(null); - const [indexes, setIndexes] = useState(null); - const [systemMessage, setSystemMessage] = useState(null); - const [settings, setSettings] = useState(null); + const [configuration, setConfiguration] = useState(null); + const [indexes, setIndexes] = useState(null); + const [systemMessage, setSystemMessage] = useState(null); + const [settings, setSettings] = useState(null); const [flattenedProps, setFlattenedProps] = useState(null); function flatten(ob) { diff --git a/src/components/layout/externalSources.tsx b/src/components/layout/externalSources.tsx index 82d175373..0f540a702 100644 --- a/src/components/layout/externalSources.tsx +++ b/src/components/layout/externalSources.tsx @@ -1,9 +1,9 @@ import { Checkbox, FormControlLabel, Typography, useTheme } from '@mui/material'; +import Skeleton from '@mui/material/Skeleton'; import createStyles from '@mui/styles/createStyles'; import makeStyles from '@mui/styles/makeStyles'; -import Skeleton from '@mui/material/Skeleton'; import useALContext from 'components/hooks/useALContext'; -import React from 'react'; +import { UserSettings } from 'components/models/base/user_settings'; import { useTranslation } from 'react-i18next'; const useStyles = makeStyles(theme => @@ -18,7 +18,7 @@ const useStyles = makeStyles(theme => ); type ExternalSourcesProps = { - settings: any; + settings: UserSettings; onChange: (name: string) => void; disabled?: boolean; size?: 'medium' | 'small'; diff --git a/src/components/layout/serviceSpec.tsx b/src/components/layout/serviceSpec.tsx index 83e07c8d6..a3c735251 100644 --- a/src/components/layout/serviceSpec.tsx +++ b/src/components/layout/serviceSpec.tsx @@ -12,6 +12,7 @@ import { } from '@mui/material'; import FormControl from '@mui/material/FormControl'; import makeStyles from '@mui/styles/makeStyles'; +import { ServiceParameter, ServiceSpecification } from 'components/models/base/service'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -28,7 +29,17 @@ const useStyles = makeStyles(theme => ({ } })); -function Service({ disabled, service, idx, setParam, setParamAsync }) { +type ServiceProps = { + service: ServiceSpecification; + idx: number; + setParam: (service_id: number, param_id: number, param_value: any) => void; + setParamAsync: (service_id: number, param_id: number, param_value: any) => void; + isSelected?: (name: string) => boolean; + disabled?: boolean; + compressed?: boolean; +}; + +function Service({ disabled, service, idx, setParam, setParamAsync }: ServiceProps) { const theme = useTheme(); const [showMore, setShowMore] = useState(false); const { t } = useTranslation(); @@ -77,7 +88,18 @@ function Service({ disabled, service, idx, setParam, setParamAsync }) { ); } -function Param({ disabled, param, pidx, idx, setParam, setParamAsync }) { +type ParamProps = { + param: ServiceParameter; + idx: number; + pidx: number; + setParam: (service_id: number, param_id: number, param_value: any) => void; + setParamAsync: (service_id: number, param_id: number, param_value: any) => void; + isSelected?: (name: string) => boolean; + disabled?: boolean; + compressed?: boolean; +}; + +function Param({ disabled, param, pidx, idx, setParam, setParamAsync }: ParamProps) { const classes = useStyles(); const theme = useTheme(); return ( @@ -147,7 +169,7 @@ function Param({ disabled, param, pidx, idx, setParam, setParamAsync }) { } type ServiceSpecProps = { - service_spec: any[]; + service_spec: ServiceSpecification[]; setParam: (service_id: number, param_id: number, param_value: any) => void; setParamAsync: (service_id: number, param_id: number, param_value: any) => void; isSelected?: (name: string) => boolean; diff --git a/src/components/layout/serviceTree.tsx b/src/components/layout/serviceTree.tsx index 8e4a3444b..b0a16f7dc 100644 --- a/src/components/layout/serviceTree.tsx +++ b/src/components/layout/serviceTree.tsx @@ -2,6 +2,8 @@ import { Checkbox, FormControlLabel, Typography, useTheme } from '@mui/material' import Skeleton from '@mui/material/Skeleton'; import createStyles from '@mui/styles/createStyles'; import makeStyles from '@mui/styles/makeStyles'; +import { SelectedService } from 'components/models/base/service'; +import { UserSettings } from 'components/models/base/user_settings'; import React from 'react'; import { HiOutlineExternalLink } from 'react-icons/hi'; @@ -30,7 +32,7 @@ function ServiceTreeItemSkel({ size = 'medium' }: ServiceTreeItemSkelProps) { } type ServiceTreeItemProps = { - item: any; + item: SelectedService; onChange: (name: string, category: string) => void; disabled?: boolean; size: 'medium' | 'small'; @@ -147,8 +149,8 @@ function SkelItems({ size, spacing }: SkelItemsProps) { } type ServiceTreeProps = { - settings: any; - setSettings: (settings: any) => void; + settings: UserSettings; + setSettings: (settings: UserSettings) => void; setModified?: (isModified: boolean) => void; compressed?: boolean; disabled?: boolean; diff --git a/src/components/models/base/alert.ts b/src/components/models/base/alert.ts new file mode 100644 index 000000000..f6330cf7f --- /dev/null +++ b/src/components/models/base/alert.ts @@ -0,0 +1,316 @@ +import { Priority, Status } from './workflow'; + +export const ES_SUBMITTED = 'submitted'; +export const ES_SKIPPED = 'skipped'; +export const ES_INCOMPLETE = 'incomplete'; +export const ES_COMPLETED = 'completed'; +export const EXTENDED_SCAN_PRIORITY = [ES_COMPLETED, ES_INCOMPLETE, ES_SKIPPED, ES_SUBMITTED] as const; +export const EXTENDED_SCAN_VALUES = [ES_SUBMITTED, ES_SKIPPED, ES_INCOMPLETE, ES_COMPLETED] as const; +export const ENTITY_TYPE = ['user', 'workflow'] as const; +export const SUB_TYPE = ['EXP', 'CFG', 'OB', 'IMP', 'CFG', 'TA'] as const; +export const VERDICT = ['safe', 'info', 'suspicious', 'highly_suspicious', 'malicious'] as const; + +export type EntityType = (typeof ENTITY_TYPE)[number]; +export type ExtendedScanPriority = (typeof EXTENDED_SCAN_PRIORITY)[number]; +export type ExtendedScanValue = (typeof EXTENDED_SCAN_VALUES)[number]; +export type SubType = (typeof SUB_TYPE)[number]; +export type Verdict = (typeof VERDICT)[number]; + +/** Assemblyline Results Block */ +export type DetailedItem = { + /** Sub-type of the item */ + subtype?: SubType; + + /** Type of data that generated this item */ + type: string; + + /** Value of the item */ + value: string; + + /** Verdict of the item */ + verdict: Verdict; +}; + +/** Assemblyline Screenshot Block */ +export type Screenshot = { + /** Description of the screenshot */ + description: string; + + /** SHA256 hash of the image */ + img: string; + + /** Name of the screenshot */ + name: string; + + /** SHA256 hash of the thumbnail */ + thumb: string; +}; + +/** Assemblyline Detailed result block */ +export type DetailedResults = { + /** List of detailed Att&ck categories */ + attack_category: DetailedItem[]; + + /** List of detailed Att&ck patterns */ + attack_pattern: DetailedItem[]; + + /** List of detailed attribution */ + attrib: DetailedItem[]; + + /** List of detailed AV hits */ + av: DetailedItem[]; + + /** List of detailed behaviors for the alert */ + behavior: DetailedItem[]; + + /** List of detailed domains */ + domain: DetailedItem[]; + + /** List of detailed heuristics */ + heuristic: DetailedItem[]; + + /** List of detailed IPs */ + ip: DetailedItem[]; + + /** List of detailed URIs */ + uri: DetailedItem[]; + + /** List of detailed YARA rule hits */ + yara: DetailedItem[]; +}; + +/** Assemblyline Results Block */ +export type ALResults = { + /** List of attribution */ + attrib: string[]; + + /** List of AV hits */ + av: string[]; + + /** List of behaviors for the alert */ + behavior: string[]; + + /** Assemblyline Detailed result block */ + detailed: DetailedResults; + + /** List of all domains */ + domain: string[]; + + /** List of domains found during Dynamic Analysis */ + domain_dynamic: string[]; + + /** List of domains found during Static Analysis */ + domain_static: string[]; + + /** List of all IPs */ + ip: string[]; + + /** List of IPs found during Dynamic Analysis */ + ip_dynamic: string[]; + + /** List of IPs found during Static Analysis */ + ip_static: string[]; + + /** Finish time of the Assemblyline submission */ + request_end_time: string & Date; + + /** Maximum score found in the submission */ + score: number; + + /** List of all URIs */ + uri: string[]; + + /** List of URIs found during Dynamic Analysis */ + uri_dynamic: string[]; + + /** List of URIs found during Static Analysis */ + uri_static: string[]; + + /** List of YARA rule hits */ + yara: string[]; +}; + +/** File Block Associated to the Top-Level/Root File of Submission */ +export type File = { + /** MD5 hash of file */ + md5: string; + + /** Name of the file */ + name: string; + + /** Screenshots of the file */ + screenshots: Screenshot[]; + + /** SHA1 hash of the file */ + sha1: string; + + /** SHA256 hash of the file */ + sha256: string; + + /** Size of the file in bytes */ + size: number; + + /** Type of file as identified by Assemblyline */ + type: string; +}; + +/** Verdict Block of Submission */ +export type SubmissionVerdict = { + /** List of users that claim submission as malicious */ + malicious: string[]; + + /** List of users that claim submission as non-malicious */ + non_malicious: string[]; +}; + +/** Heuristic Block */ +export type Heuristic = { + /** List of related Heuristic names */ + name: string[]; +}; + +/** ATT&CK Block */ +export type Attack = { + /** List of related ATT&CK categories */ + category: string[]; + + /** List of related ATT&CK patterns */ + pattern: string[]; +}; + +/** Model of Workflow Event */ +export type Event = { + /** ID of entity associated to event */ + entity_id: string; + + /** Name of entity */ + entity_name: string; + + /** Type of entity associated to event */ + entity_type: (typeof ENTITY_TYPE)[number]; + + /** Labels added during event */ + labels?: string[]; + + /** Priority applied during event */ + priority?: Priority; + + /** Status applied during event */ + status?: Status; + + /** Timestamp of event */ + ts: string & Date; +}; + +/** Submission relations for an alert */ +export type Relationship = { + /** */ + child: string; + + /** */ + parent?: string; +}; + +/** Model for Alerts */ +export type Alert = { + /** Assemblyline Result Block */ + al: ALResults; + + /** ID of the alert */ + alert_id: string; + + /** Archiving timestamp (Deprecated) */ + archive_ts?: string & Date; + + /** ATT&CK Block */ + attack: Attack; + + /** Classification of the alert */ + classification: string; + + /** An audit of events applied to alert */ + events: Event[]; + + /** Expiry timestamp */ + expiry_ts?: string & Date; + + /** Status of the extended scan */ + extended_scan: ExtendedScanValue; + + /** File Block */ + file: File; + + /** Are the alert results filtered? */ + filtered: boolean; + + /** total number of alerts in the grouped search */ + group_count: number; + + /** Heuristic Block */ + heuristic: Heuristic; + + /** Owner of the hint */ + hint_owner: boolean; + + /** ID of the alert */ + id: string; + + /** List of labels applied to the alert */ + label: string[]; + + /** Metadata submitted with the file */ + metadata: Record; + + /** Owner of the alert */ + owner?: string; + + /** Priority applied to the alert */ + priority?: Priority; + + /** Alert creation timestamp */ + reporting_ts: string & Date; + + /** Submission ID related to this alert */ + sid: string; + + /** Status applied to the alert */ + status?: Status; + + /** Describes relationships between submissions used to build this alert */ + submission_relations: Relationship[]; + + /** File submission timestamp */ + ts: string & Date; + + /** Type of alert */ + type: string; + + /** Verdict Block */ + verdict: SubmissionVerdict; + + /** Have all workflows ran on this alert? */ + workflows_completed: boolean; +}; + +export type AlertIndexed = Pick< + Alert, + | 'al' + | 'alert_id' + | 'classification' + | 'extended_scan' + | 'file' + | 'filtered' + | 'id' + | 'label' + | 'owner' + | 'priority' + | 'reporting_ts' + | 'sid' + | 'status' + | 'submission_relations' + | 'ts' + | 'type' + | 'workflows_completed' +>; + +export type AlertUpdate = Partial>; diff --git a/src/components/models/base/badlist.ts b/src/components/models/base/badlist.ts new file mode 100644 index 000000000..4e35da1f7 --- /dev/null +++ b/src/components/models/base/badlist.ts @@ -0,0 +1,131 @@ +export const BADHASH_TYPES = ['file', 'tag', 'signature'] as const; +export const SOURCE_TYPES = ['user', 'external'] as const; + +export type BadHashType = (typeof BADHASH_TYPES)[number]; +export type SourceType = (typeof SOURCE_TYPES)[number]; + +/** Attribution Tag Model */ +export type Attribution = { + /** Attribution Actor */ + actor?: string[]; + + /** Attribution Campaign */ + campaign?: string[]; + + /** Attribution Category */ + category?: string[]; + + /** Attribution Exploit */ + exploit?: string[]; + + /** Attribution Family */ + family?: string[]; + + /** Attribution Implant */ + implant?: string[]; + + /** Attribution Network */ + network?: string[]; +}; + +/** Signature of a badlisted file */ +export type Signature = { + /** name of the signature */ + name: string; +}; + +/** Hashes of a badlisted file */ +export type Hashes = { + /** MD5 */ + md5?: string; + + /** SHA1 */ + sha1?: string; + + /** SHA256 */ + sha256?: string; + + /** SSDEEP */ + ssdeep?: string; + + /** TLSH */ + tlsh?: string; +}; + +/** File Details */ +export type File = { + /** List of names seen for that file */ + name: string[]; + + /** Size of the file in bytes */ + size?: number; + + /** Type of file as identified by Assemblyline */ + type?: string; +}; + +/** Badlist source */ +export type Source = { + /** Classification of the source */ + classification: string; + + /** Name of the source */ + name: string; + + /** Reason for why file was badlisted */ + reason: string[]; + + /** Type of badlisting source */ + type: SourceType; +}; + +/** Tag associated to file */ +export type Tag = { + /** Tag type */ + type: string; + + /** Tag value */ + value: string; +}; + +/** Badlist Model */ +export type Badlist = { + /** Date when the badlisted hash was added */ + added: string & Date; + + /** Attribution related to the bad hash */ + attribution?: Attribution; + + /** Computed max classification for the bad hash */ + classification: string; + + /** Is bad hash enabled or not? */ + enabled: boolean; + + /** When does this item expire from the list? */ + expiry_ts?: string & Date; + + /** Information about the file */ + file?: File; + + /** List of hashes related to the bad hash */ + hashes: Hashes; + + /** ID of the Badlist */ + id: string; + + /** Signature related to the bad hash */ + signature: Signature; + + /** List of reasons why hash is badlisted */ + sources: Source[]; + + /** Information about the tag */ + tag?: Tag; + + /** Type of bad hash */ + type: BadHashType; + + /** Last date when sources were added to the bad hash */ + updated: string & Date; +}; diff --git a/src/components/models/base/config.ts b/src/components/models/base/config.ts new file mode 100644 index 000000000..828287f38 --- /dev/null +++ b/src/components/models/base/config.ts @@ -0,0 +1,363 @@ +import { AppSwitcherItem } from 'commons/components/app/AppConfigs'; +import { ACL, Role, Type } from './user'; + +export const API_PRIV = ['READ', 'READ_WRITE', 'WRITE', 'CUSTOM', 'EXTENDED'] as const; +export const AUTO_PROPERTY_TYPES = ['access', 'classification', 'type', 'role', 'remove_role', 'group']; +export const BANNER_LEVELS = ['info', 'warning', 'success', 'error'] as const; +export const DOWNLOAD_ENCODINGS = ['raw', 'cart'] as const; +export const EXTERNAL_LINK_TYPES = ['hash', 'metadata', 'tag'] as const; +export const KUBERNETES_LABEL_OPS = ['In', 'NotIn', 'Exists', 'DoesNotExist'] as const; +export const REGISTRY_TYPES = ['docker', 'harbor'] as const; +export const SAFELIST_HASH_TYPES = ['sha1', 'sha256', 'md5'] as const; +export const SERVICE_CATEGORIES = [ + 'Antivirus', + 'Dynamic Analysis', + 'External', + 'Extraction', + 'Filtering', + 'Internet Connected', + 'Networking', + 'Static Analysis' +] as const; +export const SERVICE_STAGES = ['FILTER', 'EXTRACT', 'CORE', 'SECONDARY', 'POST', 'REVIEW'] as const; +export const SYSTEM_TYPES = ['production', 'staging', 'development'] as const; +export const TEMPORARY_KEY_TYPES = ['union', 'overwrite', 'ignore'] as const; + +export type APIPriv = (typeof API_PRIV)[number]; +export type AutoPropertyType = (typeof AUTO_PROPERTY_TYPES)[number]; +export type BannerLevel = (typeof BANNER_LEVELS)[number]; +export type DownloadEncoding = (typeof DOWNLOAD_ENCODINGS)[number]; +export type ExternalLinkType = (typeof EXTERNAL_LINK_TYPES)[number]; +export type KubernetesLabelOps = (typeof KUBERNETES_LABEL_OPS)[number]; +export type RegistryType = (typeof REGISTRY_TYPES)[number]; +export type SafelistHashType = (typeof SAFELIST_HASH_TYPES)[number]; +export type ServiceCategory = (typeof SERVICE_CATEGORIES)[number]; +export type ServiceStage = (typeof SERVICE_STAGES)[number]; +export type SystemType = (typeof SYSTEM_TYPES)[number]; +export type TemporaryKeyType = (typeof TEMPORARY_KEY_TYPES)[number]; + +/** Authentication Methods */ +export type Auth = { + /** Allow 2FA? */ + allow_2fa: boolean; + + /** Allow API keys? */ + allow_apikeys: boolean; + + /** Allow extended API keys? */ + allow_extended_apikeys: boolean; + + /** Allow security tokens? */ + allow_security_tokens: boolean; +}; + +/** Ingester Configuration */ +export type Ingester = { + /** How many extracted files may be added to a Submission. Overrideable via submission parameters. */ + default_max_extracted: number; + + /** How many supplementary files may be added to a Submission. Overrideable via submission parameters */ + default_max_supplementary: number; +}; + +/** A set of default values to be used running a service when no other value is set */ +export type ScalerServiceDefaults = { + /** The minimum number of service instances to be running */ + min_instances: number; +}; + +/** Scaler Configuration */ +export type Scaler = { + /** Defaults Scaler will assign to a service. */ + service_defaults: ScalerServiceDefaults; +}; + +/** Core Component Configuration */ +export type Core = { + /** Configuration for Ingester */ + ingester: Ingester; + + /** Configuration for Scaler */ + scaler: Scaler; +}; + +/** Datastore Configuration" */ +export type Datastore = { + /** Datastore Archive feature configuration */ + archive: { + /** Are we enabling Achiving features across indices? */ + enabled: boolean; + }; +}; + +/** Configuration for connecting to a retrohunt service. */ +export type Retrohunt = { + /** Is the Retrohunt functionnality enabled on the frontend */ + enabled: boolean; + + /** Number of days retrohunt jobs will remain in the system by default */ + dtl: number; + + /** Maximum number of days retrohunt jobs will remain in the system */ + max_dtl: number; +}; + +/** Services Configuration */ +export type Services = { + /** List of categories a service can be assigned to */ + categories: string[]; + + /** Default update channel to be used for new services */ + preferred_update_channel: string; + + /** List of execution stages a service can be assigned to */ + stages: ServiceStage; +}; + +/** System Configuration */ +export type System = { + /** Organisation acronym used for signatures */ + organisation: string; + + /** Type of system */ + type: SystemType; + + /** Version of the system */ + version: string; +}; + +/** AI Configuration */ +export type AI = { + /** Is AI support enabled? */ + enabled: boolean; +}; + +export type AlertingMeta = { + /** Metadata keys that are considered important */ + important: string[]; + + /** Metadata keys that refer to an email's subject */ + subject: string[]; + + /** Metadata keys that refer to a URL */ + url: string[]; +}; + +/** External link that specific metadata and tags can pivot to */ +export type ExternalLink = { + /** If the classification of the item is higher than the max_classificaiton, can we let the user bypass the check and still query the external link? */ + allow_bypass: boolean; + + /** Name of the link */ + name: string; + + /** Should the replaced value be double encoded? */ + double_encode: boolean; + + /** Minimum classification the user must have to see this link */ + classification?: string; + + /** Maximum classification of data that may be handled by the link */ + max_classification?: string; + + /** Pattern that will be replaced in the URL with the metadata or tag value */ + replace_pattern: string; + + /** URL to redirect to */ + url: string; +}; + +/** Connection details for external systems/data sources. */ +export type ExternalSource = { + /** Name of the source. */ + name: string; + + /** Minimum classification applied to information from the source and required to know the existance of the source. */ + classification?: string; + + /** Maximum classification of data that may be handled by the source */ + max_classification?: string; + + /** URL of the upstream source's lookup service. */ + url: string; +}; + +/** UI Configuration */ +export type UI = { + /** AI support for the UI */ + ai: AI; + + /** Alerting metadata fields */ + alerting_meta: AlertingMeta; + + /** Allow user to tell in advance the system that a file is malicious? */ + allow_malicious_hinting: boolean; + + /** Allow user to download raw files? */ + allow_raw_downloads: boolean; + + /** Allow users to request replay on another server? */ + allow_replay: boolean; + + /** Allow file submissions via url? */ + allow_url_submissions: boolean; + + /** Allow user to download files as password protected ZIPs? */ + allow_zip_downloads: boolean; + + /** Hogwarts App data */ + apps: AppSwitcherItem[]; + + /** Should API calls be audited and saved to a separate log file? */ + audit: boolean; + + /** Banner message display on the main page (format: {: message}) */ + banner: { [language: string]: string }; + + /** Banner message level */ + banner_level: BannerLevel; + + /** Which encoding will be used for downloads? */ + download_encoding: DownloadEncoding; + + /** Enforce the user's quotas? */ + enforce_quota: boolean; + + /** List of external pivot links */ + external_links: Record; + + /** List of external sources to query */ + external_source_tags: { [tag: string]: string[] }; + + /** List of external sources to query */ + external_sources: ExternalSource[]; + + /** Fully qualified domain name to use for the 2-factor authentication validation */ + fqdn: string; + + /** Maximum priority for ingest API */ + ingest_max_priority: number; + + /** Turn on read only mode in the UI */ + read_only: boolean; + + /** List of RSS feeds to display on the UI */ + rss_feeds: string[]; + + /** Service feed to display on the UI */ + services_feed: string; + + /** List of admins to notify when a user gets locked out */ + tos_lockout_notify: boolean; + + /** Lock out user after accepting the terms of service? */ + tos_lockout: boolean; + + /** Terms of service */ + tos?: boolean; + + /** List of services auto-selected by the UI when submitting URLs */ + url_submission_auto_service_selection: string[]; +}; + +/** Minimum score value to get the specified verdict, otherwise the file is considered safe. */ +export type Verdicts = { + /** Minimum score for the verdict to be Informational. */ + info: number; + + /** Minimum score for the verdict to be Suspicious. */ + suspicious: number; + + /** Minimum score for the verdict to be Highly Suspicious. */ + highly_suspicious: number; + + /** Minimum score for the verdict to be Malicious. */ + malicious: number; +}; + +/** Options regarding all submissions, regardless of their input method */ +export type TagTypes = { + /** Attribution tags */ + attribution: string[]; + + /** Behaviour tags */ + behavior: string[]; + + /** IOC tags */ + ioc: string[]; +}; + +/** Default values for parameters for submissions that may be overridden on a per submission basis */ +export type Submission = { + /** How many extracted files may be added to a submission? */ + default_max_extracted: number; + + /** How many supplementary files may be added to a submission? */ + default_max_supplementary: number; + + /** Number of days submissions will remain in the system by default */ + dtl: number; + + /** Maximum number of days submissions will remain in the system */ + max_dtl: number; + + /** Maximum files extraction depth */ + max_extraction_depth: number; + + /** Maximum size for files submitted in the system */ + max_file_size: number; + + /** Maximum length for each metadata values */ + max_metadata_length: number; + + /** List of external source to fetch file via their SHA256 hashes */ + sha256_sources: string[]; + + /** Tag types that show up in the submission summary */ + tag_types: TagTypes; + + /** Minimum score value to get the specified verdict. */ + verdicts: Verdicts; +}; + +/** Configuration for all users */ +export type User = { + /** API_PRIV_MAP */ + api_priv_map: Record; + + /** ACL_MAP */ + priv_role_dependencies: Record; + + /** the relation between types and roles */ + role_dependencies: Record; + + /** All user roles */ + roles: Role[]; + + /** All user types */ + types: Type[]; +}; + +/** Assemblyline Deployment Configuration */ +export type Configuration = { + /** Authentication module configuration */ + auth: Auth; + + /** Datastore configuration */ + datastore: Datastore; + + /** Retrohunt configuration for the frontend and server. */ + retrohunt: Retrohunt; + + /** Options for how submissions will be processed */ + submission: Submission; + + /** System configuration */ + system: System; + + /** UI configuration parameters */ + ui: UI; + + /** User configuration */ + user: User; +}; diff --git a/src/components/models/base/error.ts b/src/components/models/base/error.ts new file mode 100644 index 000000000..822d3bd75 --- /dev/null +++ b/src/components/models/base/error.ts @@ -0,0 +1,76 @@ +export const STATUSES = ['FAIL_NONRECOVERABLE', 'FAIL_RECOVERABLE'] as const; +export const ERROR_TYPES = [ + 'UNKNOWN', + 'EXCEPTION', + 'MAX DEPTH REACHED', + 'MAX FILES REACHED', + 'MAX RETRY REACHED', + 'SERVICE BUSY', + 'SERVICE DOWN', + 'TASK PRE-EMPTED' +] as const; + +export type Status = (typeof STATUSES)[number]; +export type ErrorType = (typeof ERROR_TYPES)[number]; + +/** Error Response from a Service */ +export type Response = { + /** Error message */ + message: string; + + /** Information about where the service was processed */ + service_debug_info?: string; + + /** Service Name */ + service_name: string; + + /** Service Tool Version */ + service_tool_version?: string; + + /** Service Version */ + service_version: 'UNKNOWN' | '0' | string | number; + + /** Status of error produced by service */ + status: Status; +}; + +/** Error Model used by Error Viewer */ +export type Error = { + /** Archiving timestamp (Deprecated) */ + archive_ts?: string & Date; + + /** Error creation timestamp */ + created: string & Date; + + /** Expiry timestamp */ + expiry_ts?: string | Date; + + /** ID of the error */ + id: string; + + /** Response from the service */ + response: Response; + + /** SHA256 of file related to service error */ + sha256: string; + + /** Type of error */ + type: ErrorType; +}; + +export type ParsedErrors = { + aggregated: { + depth: string[]; + files: string[]; + retry: string[]; + down: string[]; + busy: string[]; + preempted: string[]; + exception: string[]; + unknown: string[]; + }; + listed: string[]; + services: string[]; +}; + +export type ErrorIndexed = Pick; diff --git a/src/components/models/base/file.ts b/src/components/models/base/file.ts new file mode 100644 index 000000000..4ff405551 --- /dev/null +++ b/src/components/models/base/file.ts @@ -0,0 +1,220 @@ +import { UserIndexed } from './user'; +import { UserAvatar } from './user_avatar'; + +export const REACTIONS_TYPES = { + thumbs_up: '๐Ÿ‘', + thumbs_down: '๐Ÿ‘Ž', + love: '๐Ÿงก', + smile: '๐Ÿ˜€', + surprised: '๐Ÿ˜ฒ', + party: '๐ŸŽ‰' +} as const; + +export type ReactionType = keyof typeof REACTIONS_TYPES; + +/** URI Information Model */ +export type URIInfo = { + /** */ + uri: string; + + /** */ + scheme: string; + + /** */ + netloc: string; + + /** */ + path?: string; + + /** */ + params?: string; + + /** */ + query?: string; + + /** */ + fragment?: string; + + /** */ + username?: string; + + /** */ + password?: string; + + /** */ + hostname: string; + + /** */ + port?: number; +}; + +/** File Seen Model */ +export type Seen = { + /** How many times have we seen this file? */ + count: number; + + /** First seen timestamp */ + first: string & Date; + + /** Last seen timestamp */ + last: string & Date; +}; + +/** Label Categories Model */ +export type LabelCategories = { + /** List of extra informational labels about the file */ + info: string[]; + + /** List of labels related to the technique used by the file and the signatures that hits on it. */ + technique: string[]; + + /** List of labels related to attribution of this file (implant name, actor, campain...) */ + attribution: string[]; +}; + +/** Reaction Model */ +export type Reaction = { + /** Icon of the user who made the reaction */ + icon: ReactionType; + + /** Username of the user who made the reaction */ + uname: string; +}; + +/** Author of the Comment Model */ +export type Author = Pick & { avatar: UserAvatar }; + +/** Comment Model */ +export type Comment = { + /** Comment ID */ + cid: string; + + /** Username of the user who made the comment */ + uname: string; + + /** Datetime the comment was made on */ + date: string & Date; + + /** Text of the comment written by the author */ + text: string; + + /** List of reactions made on a comment */ + reactions: Reaction[]; +}; + +/** Model of File */ +export type File = { + /** Archiving timestamp (Deprecated) */ + archive_ts?: string & Date; + + /** Dotted ASCII representation of the first 64 bytes of the file */ + ascii: string; + + /** Classification of the file */ + classification: string; + + /** List of comments made on a file */ + comments: Comment[]; + + /** Entropy of the file */ + entropy: number; + + /** Expiry timestamp */ + expiry_ts?: string & Date; + + /** Was loaded from the archive */ + from_archive: boolean; + + /** Hex dump of the first 64 bytes of the file */ + hex: string; + + /** SHA256 hash of the file */ + id: string; + + /** Is this an image from an Image Result Section? */ + is_section_image: boolean; + + /** Is this a file generated by a service? */ + is_supplementary: boolean; + + /** List of labels of the file */ + labels: string[]; + + /** Categories of label" */ + label_categories: LabelCategories; + + /** Output from libmagic related to the file */ + magic: string; + + /** MD5 of the file */ + md5: string; + + /** MIME type of the file as identified by libmagic */ + mime?: string; + + /** Details about when the file was seen */ + seen: Seen; + + /** SHA1 hash of the file */ + sha1: string; + + /** SHA256 hash of the file */ + sha256: string; + + /** Size of the file in bytes */ + size: number; + + /** SSDEEP hash of the file */ + ssdeep: string; + + /** TLSH hash of the file */ + tlsh?: string; + + /** Type of file as identified by Assemblyline */ + type: string; + + /** URI structure to speed up specialty file searching */ + uri_info?: URIInfo; +}; + +export type FileIndexed = Pick< + File, + | 'classification' + | 'comments' + | 'entropy' + | 'from_archive' + | 'id' + | 'is_section_image' + | 'is_supplementary' + | 'label_categories' + | 'labels' + | 'md5' + | 'seen' + | 'sha1' + | 'sha256' + | 'size' + | 'tlsh' + | 'type' + | 'uri_info' +>; + +export const DEFAULT_COMMENT: Comment = { + cid: null, + date: null, + text: '', + uname: null, + reactions: [] +}; + +export const DEFAULT_AUTHOR: Author = { + uname: null, + name: null, + avatar: null, + email: null +}; + +export const DEFAULT_LABELS: LabelCategories = { + attribution: [], + technique: [], + info: [] +}; diff --git a/src/components/models/base/filescore.ts b/src/components/models/base/filescore.ts new file mode 100644 index 000000000..6cf4a1ab7 --- /dev/null +++ b/src/components/models/base/filescore.ts @@ -0,0 +1,20 @@ +/** Model of Scoring related to a File */ +export type FileScore = { + /** Parent submission ID of the associated submission */ + psid?: string; + + /** Expiry timestamp, used for garbage collection */ + expiry_ts: string | Date; + + /** Maximum score for the associated submission */ + score: number; + + /** Number of errors that occurred during the previous analysis */ + errors: number; + + /** ID of the associated submission */ + sid: string; + + /** Epoch time at which the FileScore entry was created */ + time: number; +}; diff --git a/src/components/models/base/heuristic.ts b/src/components/models/base/heuristic.ts new file mode 100644 index 000000000..b707b5c58 --- /dev/null +++ b/src/components/models/base/heuristic.ts @@ -0,0 +1,40 @@ +import { Statistic } from './statistic'; + +export const HEURISTIC_LEVELS = ['malicious', 'suspicious', 'info', 'safe'] as const; + +export type HeuristicLevel = (typeof HEURISTIC_LEVELS)[number]; + +export type Heuristic = { + /** List of all associated ATT&CK IDs */ + attack_id: string[]; + + /** Classification of the heuristic */ + classification: string; + + /** Description of the heuristic */ + description: string; + + /** What type of files does this heuristic target? */ + filetype: string; + + /** ID of the Heuristic */ + heur_id: string; + + /** ID of the Heuristic */ + id: string; + + /** Maximum score for heuristic */ + max_score?: number; + + /** Name of the heuristic */ + name: string; + + /** Default score of the heuristic */ + score: number; + + /** Score of signatures for this heuristic */ + signature_score_map: Record; + + /** Statistics related to the Heuristic */ + stats: Statistic; +}; diff --git a/src/components/models/base/result.ts b/src/components/models/base/result.ts new file mode 100644 index 000000000..0a725055c --- /dev/null +++ b/src/components/models/base/result.ts @@ -0,0 +1,220 @@ +import { SectionBody } from './result_body'; + +export const PROMOTE_TO = ['SCREENSHOT', 'ENTROPY', 'URI_PARAMS'] as const; + +/** + * This is a "keys-only" representation of the PROMOTE_TO StringTable in + * assemblyline-v4-service/assemblyline_v4_service/common/result.py. + * Any updates here need to go in that StringTable also. + */ +export type PromoteTo = (typeof PROMOTE_TO)[number]; + +export type Tagging = { + type: string; + short_type: string; + value: string; + safelisted: boolean; + classification: string; +}; + +export type SectionItem = { + id: number; + children: SectionItem[]; +}; + +export type Attack = { + /** ID */ + attack_id: string; + + /** Categories */ + categories: string[]; + + /** Pattern Name */ + pattern: string; +}; + +/** Heuristic Signatures */ +export type Signature = { + /** Number of times this signature triggered the heuristic */ + frequency: number; + + /** Name of the signature that triggered the heuristic */ + name: string; + + /** Is the signature safelisted or not */ + safe: boolean; +}; + +/** Heuristic associated to the Section */ +export type Heuristic = { + /** List of Att&ck IDs related to this heuristic */ + attack: Attack[]; + + /** ID of the heuristic triggered */ + heur_id: string; + + /** Name of the heuristic */ + name: string; + + /** Calculated Heuristic score */ + score: number; + + /** List of signatures that triggered the heuristic */ + signature: Signature[]; +}; + +export type Section = SectionBody & { + /** Should the section be collapsed when displayed? */ + auto_collapse: boolean; + + /** Configurations for the body of this section */ + body_config: { column_order?: string[] }; + + /** Classification of the section */ + classification: string; + + /** Depth of the section */ + depth: number; + + /** Heuristic used to score result section */ + heuristic?: Heuristic; + + /** This is the type of data that the current section should be promoted to. */ + promote_to?: PromoteTo; + + /** List of safelisted tags */ + safelisted_tags: Record; + + /** List of tags associated to this section */ + tags: Tagging[]; + + /** Title of the section */ + title_text: string; +}; + +/** Result Body */ +export type ResultBody = { + /** Aggregate of the score for all heuristics */ + score: number; + + /** List of sections */ + sections: Section[]; +}; + +/** Service Milestones */ +export type Milestone = { + /** Date the service finished scanning */ + service_completed: string & Date; + + /** Date the service started scanning */ + service_started: string & Date; +}; + +/** File related to the Response */ +export type File = { + /** Allow file to be analysed during Dynamic Analysis even if Dynamic Recursion Prevention is enabled. */ + allow_dynamic_recursion: boolean; + + /** Classification of the file */ + classification: string; + + /** Description of the file */ + description: string; + + /** Is this an image used in an Image Result Section? */ + is_section_image: boolean; + + /** Name of the file */ + name: string; + + /** File relation to parent, if any.\
Values: `\"ROOT\", \"EXTRACTED\", \"INFORMATION\", \"DYNAMIC\", \"MEMDUMP\", \"DOWNLOADED\"` */ + parent_relation: string; + + /** SHA256 of the file */ + sha256: string; +}; + +/** Response Body of Result */ +export type ResponseBody = { + /** List of extracted files */ + extracted: File[]; + + /** Milestone block */ + milestones: Milestone; + + /** Context about the service */ + service_context?: string; + + /** Debug info about the service */ + service_debug_info?: string; + + /** Name of the service that scanned the file */ + service_name: string; + + /** Tool version of the service */ + service_tool_version?: string; + + /** Version of the service */ + service_version: string; + + /** List of supplementary files */ + supplementary: File[]; +}; + +/** Result Model */ +export type Result = { + /** Archiving timestamp (Deprecated) */ + archive_ts?: string & Date; + + /** Aggregate classification for the result */ + classification: string; + + /** Date at which the result object got created */ + created: string & Date; + + /** Use to not pass to other stages after this run */ + drop_file: boolean; + + /** Expiry timestamp */ + expiry_ts?: string & Date; + + /** Was loaded from the archive */ + from_archive: boolean; + + /** */ + id: string; + + /** Invalidate the current result cache creation */ + partial: boolean; + + /** The body of the response from the service */ + response: ResponseBody; + + /** The result body */ + result: ResultBody; + + /** SHA256 of the file the result object relates to */ + sha256: string; + + /** */ + size?: number; + + /** */ + type?: string; +}; + +export type ResultIndexed = Pick< + Result, + 'classification' | 'created' | 'drop_file' | 'from_archive' | 'id' | 'response' | 'result' | 'size' | 'type' +>; + +export type AlternateResult = Pick & { + response: Pick; +} & { result: Pick }; + +export type FileResult = Pick< + Result, + 'archive_ts' | 'classification' | 'created' | 'drop_file' | 'expiry_ts' | 'response' | 'result' | 'sha256' +> & { + section_hierarchy: SectionItem[]; +}; diff --git a/src/components/models/base/result_body.ts b/src/components/models/base/result_body.ts new file mode 100644 index 000000000..1ab1caf62 --- /dev/null +++ b/src/components/models/base/result_body.ts @@ -0,0 +1,149 @@ +export const BODY_FORMAT = [ + 'TEXT', + 'MEMORY_DUMP', + 'GRAPH_DATA', + 'URL', + 'JSON', + 'KEY_VALUE', + 'ORDERED_KEY_VALUE', + 'PROCESS_TREE', + 'TABLE', + 'IMAGE', + 'MULTI', + 'TIMELINE' +] as const; + +/** + * This is a "keys-only" representation of the BODY_FORMAT StringTable in + * assemblyline-v4-service/assemblyline_v4_service/common/result.py. + * Any updates here need to go in that StringTable also. + */ +export type BodyFormat = (typeof BODY_FORMAT)[number]; + +/** Text Body */ +export type TextBody = string; + +/** Memory Dump Body */ +export type MemDumpBody = string; + +/** Graph Body */ +export type GraphBody = { + type: 'colormap' | string; + data: { + domain: [number, number]; + values: number[]; + }; +}; + +/** URL Body */ +export type URLBody = + | { + url: string; + name: string; + } + | { + url: string; + name: string; + }[]; + +/** JSON Body */ +export type JSONBody = object; + +/** Key Value Body */ +export type KeyValueBody = Record; + +/** Ordered Key Value Body */ +export type OrderedKeyValueBody = Record; + +/** Process Tree Body */ +export type ProcessTreeBody = { + children: ProcessTreeBody[]; + command_line: string; + file_count: number; + network_count: number; + process_name: string; + process_pid: number; + registry_count: number; + safelisted: boolean; + signatures: { [signature: string]: number | any }; +}; + +/** Table Body */ +export type TableBody = Record[]; + +/** Image Body */ +export type ImageBody = { + img: { + sha256: string; + name: string; + description: string; + }; + thumb: { + sha256: string; + name: string; + description: string; + }; +}[]; + +export type Image = { + name: string; + description: string; + img: string; + thumb: string; +}; + +/** Timeline Body */ +export type TimelineBody = { + title: string; + content?: string; + score: number; + icon: 'HTML' | 'EXECUTABLE' | 'TEXT' | 'ZIP' | 'CODE' | 'IMAGE' | 'DOCUMENT' | 'UNKNOWN' | 'PROTECTED' | 'NETWORK'; + signatures: string[]; + opposite_content: string | null; +}; + +/** Multi Body */ +export type MultiBody = ( + | ['TEXT', TextBody] + | ['MEMORY_DUMP', MemDumpBody] + | ['GRAPH_DATA', GraphBody] + | ['URL', URLBody] + | ['JSON', JSONBody] + | ['KEY_VALUE', KeyValueBody] + | ['ORDERED_KEY_VALUE', OrderedKeyValueBody] + | ['PROCESS_TREE', ProcessTreeBody[]] + | ['TABLE', TableBody] + | ['TABLE', TableBody, { column_order?: string[] }] + | ['IMAGE', ImageBody] + | ['TIMELINE', TimelineBody[]] + | ['MULTI', MultiBody] + | ['DIVIDER', null] +)[]; + +export type TextBodyItem = { body_format: 'TEXT'; body: TextBody }; +export type MemDumpItem = { body_format: 'MEMORY_DUMP'; body: MemDumpBody }; +export type GraphItem = { body_format: 'GRAPH_DATA'; body: GraphBody }; +export type URLItem = { body_format: 'URL'; body: URLBody }; +export type JSONItem = { body_format: 'JSON'; body: JSONBody }; +export type KeyValueItem = { body_format: 'KEY_VALUE'; body: KeyValueBody }; +export type OrderedKeyValueItem = { body_format: 'ORDERED_KEY_VALUE'; body: OrderedKeyValueBody }; +export type ProcessTreeItem = { body_format: 'PROCESS_TREE'; body: ProcessTreeBody[] }; +export type TableItem = { body_format: 'TABLE'; body: TableBody }; +export type ImageItem = { body_format: 'IMAGE'; body: ImageBody }; +export type TimelineItem = { body_format: 'TIMELINE'; body: TimelineBody[] }; +export type MultiItem = { body_format: 'MULTI'; body: MultiBody }; + +/** Type of body in this section */ +export type SectionBody = + | TextBodyItem + | MemDumpItem + | GraphItem + | URLItem + | JSONItem + | KeyValueItem + | OrderedKeyValueItem + | ProcessTreeItem + | TableItem + | ImageItem + | TimelineItem + | MultiItem; diff --git a/src/components/models/base/retrohunt.ts b/src/components/models/base/retrohunt.ts new file mode 100644 index 000000000..09e349ad2 --- /dev/null +++ b/src/components/models/base/retrohunt.ts @@ -0,0 +1,142 @@ +export const INDEX_CATAGORIES = ['hot', 'archive', 'hot_and_archive'] as const; +export const PHASE = ['filtering', 'yara', 'finished'] as const; + +export type IndexCategory = (typeof INDEX_CATAGORIES)[number]; +export type Phase = (typeof PHASE)[number]; + +/** A search run on stored files. */ +export type Retrohunt = { + /** Defines the indices used for this retrohunt job */ + indices: IndexCategory; + + /** Classification for the retrohunt job */ + classification: string; + + /** Maximum classification of results in the search */ + search_classification: string; + + /** User who created this retrohunt job */ + creator: string; + + /** Human readable description of this retrohunt job */ + description: string; + + /** Expiry timestamp of this retrohunt job */ + expiry_ts?: string & Date; + + /** Earliest expiry group this search will include */ + start_group: number; + + /** Latest expiry group this search will include */ + end_group: number; + + /** Start time for the search. */ + created_time: string & Date; + + /** Start time for the search. */ + started_time: string & Date; + + /** Time that the search ended */ + completed_time?: string & Date; + + /** Unique code identifying this retrohunt job */ + id: string; + + /** Unique code identifying this retrohunt job */ + key: string; + + /** Text of filter query derived from yara signature */ + raw_query: string; + + /** Text of original yara signature run */ + yara_signature: string; + + /** List of error messages that occured during the search */ + errors: string[]; + + /** List of warning messages that occured during the search */ + warnings: string[]; + + /** Boolean that indicates if this retrohunt job is finished */ + finished: boolean; + + /** Indicates if the list of hits been truncated at some limit */ + truncated: boolean; + + // these are deprecated + archive_only: boolean; + code: string; + created: string | Date; + hits: string[]; + pending_candidates: number; + pending_indices: number; + phase: Phase; + progress: [number, number]; + percentage: number; + tags: Record; + total_errors: number; + total_hits: number; + total_indices: number; +}; + +/** A hit encountered during a retrohunt search. */ +export type RetrohuntHit = { + /** Unique code indentifying this hit */ + key: string; + + /** Classification string for the retrohunt job and results list */ + classification: string; + + /** Unique code indentifying this hit */ + id: string; + + /** SHA256 of the file this hit is related to */ + sha256: string; + + /** Expiry for this entry. */ + expiry_ts?: string; + + /** ID of the Retrohunt search run */ + search: string; +}; + +export type RetrohuntIndexed = Pick< + Retrohunt, + | 'classification' + | 'created_time' + | 'creator' + | 'description' + | 'end_group' + | 'finished' + | 'id' + | 'indices' + | 'key' + | 'search_classification' + | 'start_group' + | 'started_time' + | 'truncated' +>; + +export const DEFAULT_RETROHUNT: Partial = { + archive_only: false, + classification: null, + code: null, + created: '2020-01-01T00:00:00.000000Z', + creator: null, + description: '', + errors: [], + expiry_ts: null, + finished: false, + hits: [], + pending_candidates: 0, + pending_indices: 0, + phase: 'finished', + progress: [1, 1], + raw_query: null, + tags: {}, + total_errors: 0, + total_hits: 0, + total_indices: 0, + truncated: false, + yara_signature: '' +}; diff --git a/src/components/models/base/safelist.ts b/src/components/models/base/safelist.ts new file mode 100644 index 000000000..86acdd610 --- /dev/null +++ b/src/components/models/base/safelist.ts @@ -0,0 +1,98 @@ +export const SAFEHASH_TYPES = ['file', 'tag', 'signature'] as const; +export const SOURCE_TYPES = ['user', 'external'] as const; + +export type SafeHashType = (typeof SAFEHASH_TYPES)[number]; +export type SourceType = (typeof SOURCE_TYPES)[number]; + +/** Hashes of a safelisted file */ +export type Hashes = { + /** MD5 */ + md5?: string; + + /** SHA1 */ + sha1?: string; + + /** SHA256 */ + sha256?: string; +}; + +/** File Details */ +export type File = { + /** List of names seen for that file */ + name: string[]; + + /** Size of the file in bytes */ + size?: number; + + /** Type of file as identified by Assemblyline */ + type?: string; +}; + +/** Safelist source */ +export type Source = { + /** Classification of the source */ + classification: string; + + /** Name of the source */ + name: string; + + /** Reason for why file was safelisted */ + reason: string[]; + + /** Type of safelisting source */ + type: SourceType; +}; + +/** Tag associated to file */ +export type Tag = { + /** Tag type */ + type: string; + + /** Tag value */ + value: string; +}; + +/** Signature of a safelisted file */ +export type Signature = { + /** name of the signature */ + name: string; +}; + +/** Safelist Model */ +export type Safelist = { + /** Date when the safelisted hash was added */ + added: string & Date; + + /** Computed max classification for the safe hash */ + classification: string; + + /** Is safe hash enabled or not? */ + enabled: boolean; + + /** When does this item expire from the list? */ + expiry_ts?: string & Date; + + /** Information about the file */ + file?: File; + + /** List of hashes related to the safe hash */ + hashes: Hashes; + + /** ID of the Safelist */ + id: string; + + /** Signature related to the safe hash */ + signature: Signature; + + /** List of reasons why hash is safelisted */ + sources: Source[]; + + /** Information about the tag */ + tag?: Tag; + + /** Type of safe hash */ + type: SafeHashType; + + /** Last date when sources were added to the safe hash */ + updated: string & Date; +}; diff --git a/src/components/models/base/service.ts b/src/components/models/base/service.ts new file mode 100644 index 000000000..046947c6f --- /dev/null +++ b/src/components/models/base/service.ts @@ -0,0 +1,428 @@ +export const ACCESS_MODES = ['ReadWriteOnce', 'ReadWriteMany'] as const; +export const DEFAULT_SERVICE_SELECTED = [ + 'Filtering', + 'Antivirus', + 'Static Analysis', + 'Extraction', + 'Networking' +] as const; +export const OPERATING_SYSTEMS = ['windows', 'linux'] as const; +export const REGISTRY_TYPES = ['docker', 'harbor'] as const; +export const SUBMISSION_PARAM_TYPES = ['str', 'int', 'list', 'bool'] as const; +export const UPDATE_CHANNELS = ['stable', 'rc', 'beta', 'dev'] as const; +export const SIGNATURE_DELIMITERS = { + new_line: '\n', + double_new_line: '\n\n', + pipe: '|', + comma: ',', + space: ' ', + none: '', + file: '', + custom: '' +} as const; + +export type AccessMode = (typeof ACCESS_MODES)[number]; +export type DefaultServiceSelected = (typeof DEFAULT_SERVICE_SELECTED)[number]; +export type OperatingSystem = (typeof OPERATING_SYSTEMS)[number]; +export type RegistryType = (typeof REGISTRY_TYPES)[number]; +export type SubmissionParamType = (typeof SUBMISSION_PARAM_TYPES)[number]; +export type UpdateChannel = (typeof UPDATE_CHANNELS)[number]; +export type SignatureDelimiter = keyof typeof SIGNATURE_DELIMITERS; + +// TODO There is too much invalidation to make the multi_type_param work that should be necessary +/** + * Submission Parameters for Service + * @param default Default value (must match value in `value` field) + * @param hide Should this parameter be hidden? + * @param list List of values if `type: list` + * @param name Name of parameter + * @param type Type of parameter + * @param value Default value (must match value in `default` field) + */ +export type ServiceParameter = + | { + type: 'bool'; + hide: boolean | 'true' | 'false'; + name: string; + value: boolean | 'true' | 'false'; + default: boolean | 'true' | 'false'; + list?: string[]; + } + | { + type: 'int'; + hide: boolean | 'true' | 'false'; + name: string; + value: number; + default: number; + list?: string[]; + } + | { + type: 'str'; + hide: boolean | 'true' | 'false'; + name: string; + value: string; + default: string; + list?: string[]; + } + | { + type: 'list'; + hide: boolean | 'true' | 'false'; + name: string; + value: string; + default: string; + list: string[]; + }; + +// TODO check the default value +export const DEFAULT_SERVICE_PARAMETER: ServiceParameter = { + name: '', + type: 'bool', + default: 'false', + value: 'false', + list: [], + hide: 'false' +} as any; + +/** Default service specific settings */ +export type ServiceSpecification = { + /** Service name */ + name: string; + + /** Service parameters */ + params: ServiceParameter[]; +}; + +/** Selected services */ +export type SelectedService = { + /** Which category does this service belong to? */ + category?: DefaultServiceSelected; + + /** Does this service perform analysis outside of Assemblyline? */ + is_external?: boolean; + + /** Name of the service category */ + name?: DefaultServiceSelected; + + /** Is the category selected */ + selected?: boolean; + + /** List of services */ + services?: SelectedService[]; +}; + +/** Environment Variable Model */ +export type EnvironmentVariable = { + /** Name of Environment Variable */ + name: string; + + /** Value of Environment Variable */ + value: string; +}; + +export const DEFAULT_ENVIRONMENT_VARIABLE: EnvironmentVariable = { name: '', value: '' }; + +/** Docker Container Configuration */ +export type DockerConfig = { + /** Does the container have internet-access? */ + allow_internet_access: boolean; + + /** Command to run when container starts up. */ + command?: string[]; + + /** CPU allocation */ + cpu_cores: number; + + /** Additional environemnt variables for the container */ + environment: EnvironmentVariable[]; + + /** Complete name of the Docker image with tag, may include registry */ + image: string; + + /** What operating system does this container run under? */ + operating_system?: OperatingSystem; + + /** What ports of container to expose? */ + ports: string[]; + + /** Container RAM request */ + ram_mb_min: number; + + /** Container RAM limit */ + ram_mb: number; + + /** The password or token to use when pulling the image */ + registry_password?: string | string[]; + + /** The type of container registry */ + registry_type: RegistryType; + + /** The username to use when pulling the image */ + registry_username?: string; + + /** Service account to use for pods in kubernetes */ + service_account: string; +}; + +export const DEFAULT_DOCKER_CONFIG: DockerConfig = { + allow_internet_access: false, + command: null, + cpu_cores: 1, + environment: [], + image: '', + ports: [], + ram_mb_min: 128, + ram_mb: 512, + registry_password: '', + registry_type: 'docker', + registry_username: '', + service_account: '' +}; + +/** Container's Persistent Volume Configuration */ +export type PersistentVolume = { + /** Access mode for volume */ + access_mode: AccessMode; + + /** The amount of storage allocated for volume */ + capacity: string; + + /** Path into the container to mount volume */ + mount_path: string; + + /** Storage class used to create volume */ + storage_class: string; +}; + +export const DEFAULT_PERSISTENT_VOLUME: PersistentVolume = { + capacity: '', + mount_path: '', + storage_class: '', + access_mode: 'ReadWriteOnce' +}; + +/** Container's Dependency Configuration */ +export type DependencyConfig = { + /** Docker container configuration for dependency */ + container: DockerConfig; + + /** Should this dependency run as other core components? */ + run_as_core?: boolean; + + /** Volume configuration for dependency */ + volumes: Record; +}; + +export type SourceStatus = { + last_successful_update: string; + state: string; + message: string; + ts: string; +}; + +/** Update Source Configuration */ +export type UpdateSource = { + /** CA cert for source */ + ca_cert?: string; + + /** Default classification used in absence of one defined in files from source */ + default_classification: string; + + /** Branch to checkout from Git repository. */ + git_branch?: string; + + /** Headers */ + headers: EnvironmentVariable[]; + + /** Name of source */ + name: string; + + /** Password used to authenticate with source */ + password?: string; + + /** Pattern used to find files of interest from source */ + pattern?: string; + + /** Private key used to authenticate with source */ + private_key?: string; + + /** Proxy server for source */ + proxy?: string; + + /** Ignore SSL errors when reaching out to source? */ + ssl_ignore_errors: boolean; + + /** */ + status: SourceStatus; + + /** Synchronize signatures with remote source. Allows system to auto-disable signatures no longer found in source. */ + sync: boolean; + + /** URI to source */ + uri: string; + + /** Username used to authenticate with source */ + username?: string; +}; + +/** Update Configuration for Signatures */ +export type UpdateConfig = { + /** Custom delimiter definition */ + custom_delimiter?: string; + + /** Does the updater produce signatures? */ + generates_signatures: boolean; + + /** Delimiter used when given a list of signatures */ + signature_delimiter: SignatureDelimiter; + + /** List of external sources */ + sources: UpdateSource[]; + + /** Update check interval, in seconds */ + update_interval_seconds: number; + + /** Should the service wait for updates first? */ + wait_for_update: boolean; +}; + +/** Service Configuration */ +export type Service = { + /** Regex to accept files as identified by Assemblyline */ + accepts: string; + + /** Which category does this service belong to? */ + category: string; + + /** Classification of the service */ + classification: string; + + /** Service Configuration */ + config: Record; + + /** Default classification assigned to service results */ + default_result_classification: string; + + /** Dependency configuration for service */ + dependencies: Record; + + /** Description of service */ + description: string; + + /** Should the result cache be disabled for this service? */ + disable_cache: boolean; + + /** Docker configuration for service */ + docker_config: DockerConfig; + + /** Is the service enabled (by default)? */ + enabled: boolean; + + /** Service ID */ + id: string; + + /** Does this service perform analysis outside of Assemblyline? */ + is_external: boolean; + + /** How many licences is the service allowed to use? */ + licence_count: number; + + /** If more than this many jobs are queued for this service drop those over this limit. 0 is unlimited. */ + max_queue_length: number; + + /** The minimum number of service instances. Overrides Scaler's min_instances configuration. */ + min_instances?: number; + + /** This service watches these temporary keys for changes when partial results are produced. */ + monitored_keys: string[]; + + /** Name of service */ + name: string; + + /** Should the service be able to talk to core infrastructure or just service-server for tasking? */ + privileged: boolean; + + /** Regex to reject files as identified by Assemblyline */ + rejects?: string; + + /** Which execution stage does this service run in? */ + stage: string; + + /** Submission parameters of service */ + submission_params: ServiceParameter[]; + + /** Service task timeout, in seconds */ + timeout: number; + + /** What channel to watch for service updates? */ + update_channel: UpdateChannel; + + /** Update configuration for fetching external resources */ + update_config?: UpdateConfig; + + /** Does this service use submission metadata for analysis? */ + uses_metadata: boolean; + + /** Does this service use scores of tags from other services for analysis? */ + uses_tag_scores: boolean; + + /** Does this service use tags from other services for analysis? */ + uses_tags: boolean; + + /** Does this service use temp data from other services for analysis? */ + uses_temp_submission_data: boolean; + + /** Version of service */ + version: string; +}; + +export type ServiceUpdateData = { + auth: string; + image: string; + latest_tag: string; + update_available: boolean; + updating: boolean; +}; + +export type ServiceConstants = { + categories: string[]; + stages: string[]; +}; + +export type ServiceUpdates = { [service_name: string]: ServiceUpdateData }; + +export type ServiceIndexed = Pick< + Service, + | 'accepts' + | 'category' + | 'classification' + | 'description' + | 'enabled' + | 'is_external' + | 'name' + | 'privileged' + | 'rejects' + | 'stage' + | 'version' +>; + +export const DEFAULT_SOURCE: UpdateSource = { + ca_cert: '', + default_classification: '', + headers: [], + name: '', + password: '', + pattern: '', + private_key: '', + proxy: '', + ssl_ignore_errors: false, + uri: '', + username: '', + git_branch: '', + status: { + last_successful_update: '', + message: '', + state: '', + ts: '' + }, + sync: false +}; diff --git a/src/components/models/base/signature.ts b/src/components/models/base/signature.ts new file mode 100644 index 000000000..cedab4005 --- /dev/null +++ b/src/components/models/base/signature.ts @@ -0,0 +1,68 @@ +import { Statistic } from './statistic'; + +export const DEPLOYED_STATUSES = ['DEPLOYED', 'NOISY', 'DISABLED'] as const; +export const DRAFT_STATUSES = ['STAGING', 'TESTING'] as const; +export const STALE_STATUSES = ['INVALID'] as const; +export const RULE_STATUSES = [...DEPLOYED_STATUSES, ...DRAFT_STATUSES, ...STALE_STATUSES] as const; + +export type DeployedStatus = (typeof DEPLOYED_STATUSES)[number]; +export type DraftStatus = (typeof DRAFT_STATUSES)[number]; +export type StaleStatus = (typeof STALE_STATUSES)[number]; +export type RuleStatus = (typeof RULE_STATUSES)[number]; + +export type Signature = { + /** */ + classification: string; + + /** */ + data: string; + + id: string; + + /** */ + last_modified: string | Date; + + /** */ + name: string; + + /** */ + order: number; + + /** */ + revision: string; + + /** */ + signature_id?: string; + + /** */ + source: string; + + /** */ + state_change_date?: string | Date; + + /** */ + state_change_user?: string; + + /** */ + stats: Statistic; + + /** */ + status: RuleStatus; + + /** */ + type: string; +}; + +export type SignatureIndexed = Pick< + Signature, + | 'classification' + | 'id' + | 'last_modified' + | 'name' + | 'revision' + | 'signature_id' + | 'source' + | 'stats' + | 'status' + | 'type' +>; diff --git a/src/components/models/base/statistic.ts b/src/components/models/base/statistic.ts new file mode 100644 index 000000000..0eea66426 --- /dev/null +++ b/src/components/models/base/statistic.ts @@ -0,0 +1,23 @@ +/** Statistics Model */ +export type Statistic = { + /** Average of all stastical hits */ + avg: number; + + /** Count of statistical hits */ + count: number; + + /** Date of first hit of statistic */ + first_hit?: string | Date; + + /** Date of last hit of statistic */ + last_hit?: string | Date; + + /** Maximum value of all stastical hits */ + max: number; + + /** Minimum value of all stastical hits */ + min: number; + + /** Sum of all stastical hits */ + sum: number; +}; diff --git a/src/components/models/base/submission.ts b/src/components/models/base/submission.ts new file mode 100644 index 000000000..5b3b2e159 --- /dev/null +++ b/src/components/models/base/submission.ts @@ -0,0 +1,219 @@ +import { ParsedErrors } from './error'; + +export const SUBMISSION_STATES = ['failed', 'submitted', 'completed'] as const; + +export type SubmissionState = (typeof SUBMISSION_STATES)[number]; + +/** File Model of Submission */ +export type File = { + /** Name of the file */ + name: string; + + /** SHA256 hash of the file */ + sha256: string; + + /** Size of the file in bytes */ + size?: number; +}; + +/** Service Selection Scheme */ +export type ServiceSelection = { + /** List of excluded services */ + excluded: string[]; + + /** List of services to rescan when initial run scores as malicious */ + rescan: string[]; + + /** Add to service selection when resubmitting */ + resubmit: string[]; + + /** List of runtime excluded services */ + runtime_excluded: string[]; + + /** List of selected services */ + selected: string[]; +}; + +/** Submission Parameters */ +export type SubmissionParams = { + /** Does the submission automatically goes into the archive when completed? */ + auto_archive: boolean; + + /** Original classification of the submission */ + classification: string; + + /** Should a deep scan be performed? */ + deep_scan: boolean; + + /** When the submission is archived, should we delete it from hot storage right away? */ + delete_after_archive: boolean; + + /** Description of the submission */ + description: string; + + /** Should this submission generate an alert? */ + generate_alert: boolean; + + /** List of groups related to this scan */ + groups: string[]; + + /** Ignore the cached service results? */ + ignore_cache: boolean; + + /** Should we ignore dynamic recursion prevention? */ + ignore_dynamic_recursion_prevention: boolean; + + /** Should we ignore filtering services? */ + ignore_filtering: boolean; + + /** Ignore the file size limits? */ + ignore_size: boolean; + + /** Initialization for temporary submission data */ + initial_data?: string; + + /** Is the file submitted already known to be malicious? */ + malicious: boolean; + + /** Max number of extracted files */ + max_extracted: number; + + /** Max number of supplementary files */ + max_supplementary: number; + + /** Exempt from being dropped by ingester? */ + never_drop: boolean; + + /** Priority of the scan */ + priority: number; + + /** Should the submission do extra profiling? */ + profile: boolean; + + /** Parent submission ID */ + psid?: string; + + /** Does this submission count against quota? */ + quota_item: boolean; + + /** Service-specific parameters */ + service_spec: Record>; + + /** Service selection */ + services: ServiceSelection; + + /** User who submitted the file */ + submitter: string; + + /** Time, in days, to live for this submission */ + ttl: number; + + /** Type of submission */ + type: string; +}; + +/** Submission-Relevant Times */ +export type Times = { + /** Date at which the submission finished scanning */ + completed?: string & Date; + + /** Date at which the submission started scanning */ + submitted: string & Date; +}; + +/** Submission Verdict */ +export type Verdict = { + /** List of user that thinks this submission is malicious */ + malicious: string[]; + + /** List of user that thinks this submission is non-malicious */ + non_malicious: string[]; +}; + +export type Metadata = { + replay?: string; + [meta: string]: string; +}; + +/** Model of Submission */ +export type Submission = { + /** Archiving timestamp (Deprecated) */ + archive_ts?: string & Date; + + /** Document is present in the malware archive */ + archived: boolean; + + /** Classification of the submission */ + classification: string; + + /** Total number of errors in the submission */ + error_count: number; + + /** List of error keys */ + errors: string[]; + + /** Expiry timestamp */ + expiry_ts?: string & Date; + + /** Total number of files in the submission */ + file_count: number; + + /** List of files that were originally submitted */ + files: File[]; + + /** Was loaded from the archive */ + from_archive: boolean; + + /** Submission ID */ + id: string; + + /** Maximum score of all the files in the scan */ + max_score: number; + + /** Metadata associated to the submission */ + metadata: Metadata; + + /** Submission parameter details */ + params: SubmissionParams; + + /** List of result keys */ + results: string[]; + + /** */ + scan_key?: string; + + /** Submission ID */ + sid: string; + + /** Status of the submission */ + state: SubmissionState; + + /** Submission-specific times */ + times: Times; + + /** This document is going to be deleted as soon as it finishes */ + to_be_deleted: boolean; + + /** Malicious verdict details */ + verdict: Verdict; +}; + +export type SubmissionIndexed = Pick< + Submission, + | 'archived' + | 'classification' + | 'error_count' + | 'file_count' + | 'from_archive' + | 'id' + | 'max_score' + | 'params' + | 'sid' + | 'state' + | 'times' + | 'to_be_deleted' +>; + +export type ParsedSubmission = Submission & { + parsed_errors: ParsedErrors; +}; diff --git a/src/components/models/base/submission_summary.ts b/src/components/models/base/submission_summary.ts new file mode 100644 index 000000000..4e6cd82f9 --- /dev/null +++ b/src/components/models/base/submission_summary.ts @@ -0,0 +1,26 @@ +/** Submission Summary Model */ +export type SubmissionSummary = { + /** ATT&CK Matrix cache */ + attack_matrix: string; + + /** Classification of the cache */ + classification: string; + + /** Expiry timestamp */ + expiry_ts: string | Date; + + /** Has this cache entry been filtered? */ + filtered: boolean; + + /** Map of heuristic names to IDs */ + heuristic_name_map: string; + + /** All sections mapping to the heuristics */ + heuristic_sections: string; + + /** Heuristics cache */ + heuristics: string; + + /** Tags cache */ + tags: string; +}; diff --git a/src/components/models/base/submission_tree.ts b/src/components/models/base/submission_tree.ts new file mode 100644 index 000000000..fe158f63c --- /dev/null +++ b/src/components/models/base/submission_tree.ts @@ -0,0 +1,17 @@ +/** Submission Tree Model */ +export type SubmissionTree = { + /** Classification of the cache */ + classification: string; + + /** Expiry timestamp */ + expiry_ts: string | Date; + + /** Has this cache entry been filtered? */ + filtered: boolean; + + /** Tree of supplementary files */ + supplementary: string; + + /** File tree cache */ + tree: string; +}; diff --git a/src/components/models/base/tagging.ts b/src/components/models/base/tagging.ts new file mode 100644 index 000000000..be2e2ba16 --- /dev/null +++ b/src/components/models/base/tagging.ts @@ -0,0 +1,29 @@ +import { Verdict } from './alert'; + +/** + * Attack Matrix Model + * @param attack_id + * @param category + * @param Verdict + */ +export type Attack = [string, string, Verdict]; + +/** + * Signature Model + * @param name signature name + * @param h_type verdict + * @param safelisted is the signature safelisted + */ +export type Signature = [string, Verdict, boolean]; + +/** + * Tag Model + * @param name tag value + * @param h_type verdict + * @param safelisted is the tag safelisted + * @param classification tag classification + */ +export type Tag = [string, Verdict, boolean, string]; + +/** Tagging Model */ +export type Tagging = {}; diff --git a/src/components/models/base/user.ts b/src/components/models/base/user.ts new file mode 100644 index 000000000..1495ed1d6 --- /dev/null +++ b/src/components/models/base/user.ts @@ -0,0 +1,152 @@ +import { AppUser } from 'commons/components/app/AppUserService'; + +export const TYPES = [ + 'admin', + 'user', + 'signature_manager', + 'signature_importer', + 'viewer', + 'submitter', + 'custom' +] as const; + +export const ROLES = [ + 'administration', + 'alert_manage', + 'alert_view', + 'apikey_access', + 'archive_comment', + 'archive_download', + 'archive_manage', + 'archive_trigger', + 'archive_view', + 'badlist_manage', + 'badlist_view', + 'bundle_download', + 'external_query', + 'file_detail', + 'file_download', + 'file_purge', + 'heuristic_view', + 'obo_access', + 'replay_system', + 'replay_trigger', + 'retrohunt_run', + 'retrohunt_view', + 'safelist_manage', + 'safelist_view', + 'self_manage', + 'signature_download', + 'signature_import', + 'signature_manage', + 'signature_view', + 'submission_create', + 'submission_delete', + 'submission_manage', + 'submission_view', + 'workflow_manage', + 'workflow_view' +] as const; + +export const SCOPES = ['r', 'w', 'rw', 'c'] as const; +export const ACL_VALUES = ['R', 'W', 'E', 'C'] as const; + +export type Type = (typeof TYPES)[number]; +export type Role = (typeof ROLES)[number]; +export type Scope = (typeof SCOPES)[number]; +export type ACL = (typeof ACL_VALUES)[number]; + +/** Model for API keys */ +export type ApiKey = { + /** Access Control List for the API key */ + acl: ACL[]; + + /** BCrypt hash of the password for the apikey */ + password?: string; + + /** List of roles tied to the API key */ + roles: Role[]; +}; + +/** Model of Apps used of OBO (On Behalf Of) */ +export type Apps = { + /** Username allowed to impersonate the current user */ + client_id: string; + + /** DNS hostname for the server */ + netloc: string; + + /** Scope of access for the App token */ + scope: Scope; + + /** Name of the server that has access */ + server: string; + + /** List of roles tied to the App token */ + roles: Role[]; +}; + +/** Model of User */ +export type User = AppUser & { + /** Date the user agree with terms of service */ + agrees_with_tos?: string & Date; + + /** Maximum number of concurrent API requests */ + api_quota: number; + + /** Mapping of API keys */ + apikeys: Record; + + /** Applications with access to the account */ + apps: Record; + + /** Allowed to query on behalf of others? */ + can_impersonate: boolean; + + /** Maximum classification for the user */ + classification: string; + + /** User's LDAP DN */ + dn?: string; + + /** User's email address */ + email?: string; + + /** List of groups the user submits to */ + groups: string[]; + + /** Username */ + id: string; + + /** Is the user active? */ + is_active: boolean; + + /** Full name of the user */ + name: string; + + /** Secret key to generate one time passwords */ + otp_sk?: string; + + /** BCrypt hash of the user's password */ + password: string; + + /** Maximum number of concurrent submissions */ + submission_quota: number; + + /** Type of user */ + type: Type[]; + + /** Default roles for user */ + roles: Role[]; + + /** Map of security tokens */ + security_tokens: string[]; + + /** Username */ + uname: string; +}; + +export type UserIndexed = Pick< + User, + 'classification' | 'email' | 'groups' | 'id' | 'is_active' | 'name' | 'roles' | 'type' | 'uname' +>; diff --git a/src/components/models/base/user_avatar.ts b/src/components/models/base/user_avatar.ts new file mode 100644 index 000000000..2ad1396e2 --- /dev/null +++ b/src/components/models/base/user_avatar.ts @@ -0,0 +1,2 @@ +/** image string of the user's avatar */ +export type UserAvatar = string; diff --git a/src/components/models/base/user_settings.ts b/src/components/models/base/user_settings.ts new file mode 100644 index 000000000..00fd9ed34 --- /dev/null +++ b/src/components/models/base/user_settings.ts @@ -0,0 +1,63 @@ +import { SelectedService, ServiceSpecification } from './service'; + +export const ENCODINGS = ['cart', 'raw', 'zip'] as const; +export const VIEWS = ['report', 'details'] as const; + +export type Encoding = (typeof ENCODINGS)[number]; +export type View = (typeof VIEWS)[number]; + +export type UserSettings = { + /** Default submission classification */ + classification: string; + + /** Should a deep scan be performed? */ + deep_scan: boolean; + + /** List of sha256 sources to check by default */ + default_external_sources: string[]; + + /** Default user-defined password for creating password protected ZIPs when downloading files */ + default_zip_password: string; + + /** Default description */ + description: string; + + /** Default download encoding when downloading files */ + download_encoding: Encoding; + + /** Auto-expand section when score bigger then this */ + expand_min_score: number; + + /** Generate an alert? */ + generate_alert: boolean; + + /** Ignore service caching? */ + ignore_cache: boolean; + + /** Ignore dynamic recursion prevention? */ + ignore_dynamic_recursion_prevention: boolean; + + /** Ignore filtering services? */ + ignore_filtering: boolean; + + /** Is the file submitted already known to be malicious? */ + malicious: boolean; + + /** Default priority for the submissions */ + priority: number; + + /** Should the submission do extra profiling? */ + profile: boolean; + + /** Default service specific settings */ + service_spec: ServiceSpecification[]; + + /** Default service selection */ + services: SelectedService[]; + + /** Default view for completed submissions */ + submission_view: View; + + /** Default submission TTL, in days */ + ttl: number; +}; diff --git a/src/components/models/base/workflow.ts b/src/components/models/base/workflow.ts new file mode 100644 index 000000000..6520fb8c6 --- /dev/null +++ b/src/components/models/base/workflow.ts @@ -0,0 +1,83 @@ +export const PRIORITIES = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'] as const; +export const STATUSES = ['MALICIOUS', 'NON-MALICIOUS', 'ASSESS', 'TRIAGE'] as const; + +export type Priority = null | (typeof PRIORITIES)[number]; +export type Status = null | (typeof STATUSES)[number]; + +/** Model of Workflow */ +export type Workflow = { + /** Classification of the workflow */ + classification: string; + + /** Creation date of the workflow */ + creation_date: string & Date; + + /** UID of the creator of the workflow */ + creator: string; + + /** Priority applied by the workflow */ + description: string; + + /** UID of the last user to edit the workflow */ + edited_by: string; + + /** Is this workflow enabled? */ + enabled: boolean; + + /** Date of first hit on workflow */ + first_seen?: string & Date; + + /** Number of times there was a workflow hit */ + hit_count: number; + + /** ID of the workflow */ + id: string; + + /** Labels applied by the workflow */ + labels: string[]; + + /** Date of last edit on workflow */ + last_edit: string & Date; + + /** Date of last hit on workflow */ + last_seen?: string & Date; + + /** Name of the workflow */ + name: string; + + /** Which did this originate from? */ + origin?: string; + + /** */ + priority?: Priority; + + /** Query that the workflow runs */ + query: string; + + /** Status applied by the workflow */ + status?: Status; + + /** ID of the workflow */ + workflow_id?: string; +}; + +export type WorkflowIndexed = Pick< + Workflow, + | 'classification' + | 'creation_date' + | 'creator' + | 'edited_by' + | 'enabled' + | 'first_seen' + | 'hit_count' + | 'id' + | 'labels' + | 'last_edit' + | 'last_seen' + | 'name' + | 'origin' + | 'priority' + | 'query' + | 'status' + | 'workflow_id' +>; diff --git a/src/components/models/ui/file.ts b/src/components/models/ui/file.ts new file mode 100644 index 000000000..1ffdb6689 --- /dev/null +++ b/src/components/models/ui/file.ts @@ -0,0 +1,50 @@ +import { File as FileInfo, FileIndexed } from 'components/models/base/file'; +import { Verdict } from '../base/alert'; +import { Error } from '../base/error'; +import { AlternateResult, FileResult, ResultIndexed } from '../base/result'; +import { Attack, Signature, Tag } from '../base/tagging'; + +export const SIMILAR_TYPES = ['tlsh', 'ssdeep', 'vector'] as const; + +export type SimilarType = (typeof SIMILAR_TYPES)[number]; + +export type Alternates = { [serviceName: string]: AlternateResult[] }; +export type AttackMatrix = { [attack: string]: Attack[] }; +export type Childrens = { name: string; sha256: string }[]; +export type Heuristics = Record; +export type Metadata = { [level: string]: { [key: string]: any } }; +export type Tags = { [tag_name: string]: Tag[] }; + +export type File = { + alternates: Alternates; + attack_matrix: AttackMatrix; + childrens: Childrens; + classification: string; + emptys: FileResult[]; + errors: Error[]; + file_info: FileInfo; + heuristics: Heuristics; + metadata: Metadata; + parents: string[]; + results: FileResult[]; + signatures: Signature[]; + tags: Tags; +}; + +/** A single result item of the similar search */ +export type SimilarResult = { + /** Type of relationship used to finds thoses files */ + type: string; + + /** Value used to do the relation */ + value: string; + + /** Total files through this relation type */ + total: number; + + /** List of files hash */ + items: FileIndexed[] | ResultIndexed[]; +}; + +/** Find files related to the current files via TLSH, SSDEEP or Vectors */ +export type SimilarResults = SimilarResult[]; diff --git a/src/components/models/ui/help.ts b/src/components/models/ui/help.ts new file mode 100644 index 000000000..95fc4e79d --- /dev/null +++ b/src/components/models/ui/help.ts @@ -0,0 +1,30 @@ +import type { Core, Services, Submission, UI } from '../base/config'; + +export type Configuration = { + 'core.scaler.service_defaults.min_instances': Core['scaler']['service_defaults']['min_instances']; + 'core.ingester.default_max_extracted': Core['ingester']['default_max_extracted']; + 'core.ingester.default_max_supplementary': Core['ingester']['default_max_supplementary']; + 'services.categories': Services['categories']; + 'services.preferred_update_channel': Services['preferred_update_channel']; + 'services.stages': Services['stages']; + 'submission.default_max_extracted': Submission['default_max_extracted']; + 'submission.default_max_supplementary': Submission['default_max_supplementary']; + 'submission.dtl': Submission['dtl']; + 'submission.max_dtl': Submission['max_dtl']; + 'submission.max_extraction_depth': Submission['max_extraction_depth']; + 'submission.max_file_size': Submission['max_file_size']; + 'submission.max_metadata_length': Submission['max_metadata_length']; + 'submission.tag_types.attribution': Submission['tag_types']['attribution']; + 'submission.tag_types.behavior': Submission['tag_types']['behavior']; + 'submission.tag_types.ioc': Submission['tag_types']['ioc']; + 'submission.verdicts.info': Submission['verdicts']['info']; + 'submission.verdicts.suspicious': Submission['verdicts']['suspicious']; + 'submission.verdicts.highly_suspicious': Submission['verdicts']['highly_suspicious']; + 'submission.verdicts.malicious': Submission['verdicts']['malicious']; + 'ui.allow_raw_downloads': UI['allow_raw_downloads']; + 'ui.allow_zip_downloads': UI['allow_zip_downloads']; + 'ui.audit': UI['audit']; + 'ui.download_encoding': UI['download_encoding']; + 'ui.enforce_quota': UI['enforce_quota']; + 'ui.ingest_max_priority': UI['ingest_max_priority']; +}; diff --git a/src/components/models/ui/index.ts b/src/components/models/ui/index.ts new file mode 100644 index 000000000..c497b0c76 --- /dev/null +++ b/src/components/models/ui/index.ts @@ -0,0 +1,17 @@ +import { Role } from '../base/user'; + +export const METHODS = ['GET', 'POST', 'DELETE', 'PUT'] as const; + +export type Method = (typeof METHODS)[number]; + +export type Page = { + audit: boolean; + function: string; + methods: Method[]; + protected: boolean; + required_type: Role[]; + url: string; +}; + +/** Check if all pages have been protected by a login decorator */ +export type SiteMap = Page[]; diff --git a/src/components/models/ui/live.ts b/src/components/models/ui/live.ts new file mode 100644 index 000000000..29111f848 --- /dev/null +++ b/src/components/models/ui/live.ts @@ -0,0 +1,9 @@ +export const LIVE_STATUS = ['queued', 'processing', 'rescheduled'] as const; + +export type LiveStatus = (typeof LIVE_STATUS)[number]; + +/** Starts a watch queue to get live results */ +export type WatchQueue = { wq_id: string }; + +/** List outstanding services and the number of file each of them still have to process. */ +export type OutstandingServices = { [service: string]: number }; diff --git a/src/components/models/ui/search.ts b/src/components/models/ui/search.ts new file mode 100644 index 000000000..26210e9b8 --- /dev/null +++ b/src/components/models/ui/search.ts @@ -0,0 +1,87 @@ +/** Data Types based on the elastic definitions */ +export const FIELD_TYPES = [ + 'null', + 'boolean', + 'byte', + 'short', + 'integer', + 'long', + 'unsigned_long', + 'double', + 'float', + 'half_float', + 'scaled_float', + 'keyword', + 'text', + 'binary', + 'date', + 'ip', + 'version', + 'object', + 'nested' +] as const; + +export type FieldType = (typeof FIELD_TYPES)[number]; + +/** Search through specified index for a given query. Uses lucene search syntax for query. */ +export type SearchResult = { + /** List of results */ + items: Item[]; + + /** Offset in the result list */ + offset: number; + + /** Number of results returned */ + rows: number; + + /** Total results found */ + total: number; +}; + +/** Perform statistical analysis of an integer field to get its min, max, average and count values */ +export type StatResult = { + /** Number of times this field is seen */ + count: number; + + /** Minimum value */ + min: number; + + /** Maximum value */ + max: number; + + /** Average value */ + avg: number; + + /** Sum of all values */ + sum: number; +}; + +/** Generate an histogram based on a time or and int field using a specific gap size */ +export type HistogramResult = { [step: string]: number }; + +/** + * Perform field analysis on the selected field. (Also known as facetting in lucene) + This essentially counts the number of instances a field is seen with each specific values + where the documents matches the specified queries. + */ +export type FacetResult = { [step: string]: number }; + +export type FieldsResult = { + [field: string]: { + default?: boolean; + + /** Is the field indexed */ + indexed: boolean; + + /** Is the field a list */ + list: boolean; + + /** Is the field stored */ + stored: boolean; + + /** What type of data in the field */ + type: FieldType; + + name: string; + }; +}; diff --git a/src/components/models/ui/service.ts b/src/components/models/ui/service.ts new file mode 100644 index 000000000..7ac4cfb9f --- /dev/null +++ b/src/components/models/ui/service.ts @@ -0,0 +1,25 @@ +import { ErrorType } from '../base/error'; +import { StatResult } from './search'; + +/** Get statistics for a service */ +export type ServiceStats = { + error: Record; + file: { + extracted: Partial; + supplementary: Partial; + }; + heuristic: { [heuristicID: string]: number }; + result: { + count: number; + score: { + avg: number; + distribution: Record; + max: number; + min: number; + }; + }; + service: { + name: string; + version: string; + }; +}; diff --git a/src/components/models/ui/submission.ts b/src/components/models/ui/submission.ts new file mode 100644 index 000000000..4a18211d2 --- /dev/null +++ b/src/components/models/ui/submission.ts @@ -0,0 +1,92 @@ +import { Section } from '../base/result'; +import { AttackMatrix, Heuristics, Tags } from './file'; + +export type HeuristicNameMap = { [name: string]: string[] }; +export type HeuristicSection = { [heuristic: string]: Section[] }; +export type Sha256TagMap = { [sha256: string]: string[] }; +export type SubmissionTags = { + attribution: Tags; + behavior: Tags; + ioc: Tags; +}; + +/** + * Retrieve the executive summary of a given submission ID. This + * is a MAP of tags to sha256 combined with a list of generated Tags by summary type. + */ +export type SubmissionSummary = { + /** Attack matrix dictionary */ + attack_matrix: AttackMatrix; + + /** */ + classification: string; + + /** */ + filtered: boolean; + + /** Map of heuristic names to IDs */ + heuristic_name_map: HeuristicNameMap; + + /** Result section of the heuristic */ + heuristic_sections: HeuristicSection; + + /** Heuristics dictionary */ + heuristics: Heuristics; + + /** Map of TAGS to sha256 */ + map: Sha256TagMap; + + /** */ + partial: boolean; + + /** Dictionary of tags */ + tags: SubmissionTags; +}; + +export type Tree = { + /** Dictionary of children file blocks */ + children: { [sha256: string]: Tree }; + + /** List of possible names for the file */ + name: string[]; + + /** Score for the file */ + score: number; + + /** SHA256 of the file */ + sha256?: string; + + /** Size of the file */ + size?: number; + + /** Is the file truncated */ + truncated?: boolean; + + /** Type of the file */ + type: string; +}; + +/** + * Get the file hierarchy of a given Submission ID. This is an + * N deep recursive process but is limited to the max depth + * set in the system settings. + */ +export type SubmissionTree = { + /** Classification of the cache */ + classification: string; + + /** Expiry timestamp */ + expiry_ts: string | Date; + + /** Has this cache entry been filtered? */ + filtered: boolean; + + /** Missing files. Might be caused by the submissions still being processed */ + partial: boolean; + + /** */ + supplementary: string[]; + + /** Dictionary of children file blocks */ + tree: { [sha256: string]: Tree }; +}; diff --git a/src/components/models/ui/submission_report.ts b/src/components/models/ui/submission_report.ts new file mode 100644 index 000000000..b4637765d --- /dev/null +++ b/src/components/models/ui/submission_report.ts @@ -0,0 +1,78 @@ +import { Verdict } from '../base/alert'; +import { File } from '../base/file'; +import { PromoteTo } from '../base/result'; +import { SectionBody } from '../base/result_body'; +import { Submission } from '../base/submission'; +import { Tags } from './file'; +import { SubmissionSummary } from './submission'; + +/** */ +export type TPromotedSection = SectionBody & { + /** This is the type of data that the current section should be promoted to. */ + promote_to?: PromoteTo; +}; + +/** + * File that generated the attack + * @param name name of the file + * @param sha256 sha256 of the file + */ +export type TAttackFile = [string, string]; + +export type TAttack = { + h_type: Verdict; + files: TAttackFile[]; +}; + +export type TAttackMatrix = { [attack: string]: TAttack }; + +export type TSubmissionTags = { + attributions: Tags; + behaviors: Tags; + indicators_of_compromise: Tags; +}; + +/** + * Create a report for a submission based on its ID. + */ +export type TSubmissionReport = Pick< + Submission, + | 'archive_ts' + | 'archived' + | 'classification' + | 'error_count' + | 'expiry_ts' + | 'file_count' + | 'files' + | 'from_archive' + | 'max_score' + | 'metadata' + | 'scan_key' + | 'sid' + | 'state' + | 'times' + | 'to_be_deleted' + | 'verdict' +> & + Pick & { + /** ATT&CK Matrix object */ + attack_matrix: { [category: string]: TAttackMatrix }; + file_info: File; + file_tree: string; + + /** sha256 of the important files */ + important_files: string[]; + + params: Submission['params'] & { services: { errors: string[] } }; + + promoted_sections: TPromotedSection[]; + + /** Has the tree cache entry been filtered? */ + report_filtered: boolean; + + /** Missing files. Might be caused by the submissions still being processed */ + report_partial: boolean; + + /** Dictionary of tags */ + tags: TSubmissionTags; + }; diff --git a/src/components/models/ui/user.ts b/src/components/models/ui/user.ts new file mode 100644 index 000000000..9e2c1754b --- /dev/null +++ b/src/components/models/ui/user.ts @@ -0,0 +1,99 @@ +import { ClassificationDefinition } from 'helpers/classificationParser'; +import { Configuration } from '../base/config'; +import { Role, Type, User } from '../base/user'; +import { UserSettings } from '../base/user_settings'; + +export type Field = { + name: string; + indexed: boolean; + stored: boolean; + type: string; + default: boolean; + list: boolean; +}; + +export type IndexDefinition = { [field: string]: Field }; + +/** Search indexes definitions */ +export type Indexes = { + alert: IndexDefinition; + badlist: IndexDefinition; + file: IndexDefinition; + heuristic: IndexDefinition; + result: IndexDefinition; + retrohunt: IndexDefinition; + safelist: IndexDefinition; + signature: IndexDefinition; + submission: IndexDefinition; + workflow: IndexDefinition; +}; + +export type SystemMessage = { + user: string; + title: string; + severity: 'success' | 'info' | 'warning' | 'error'; + message: string; +}; + +export interface CustomUser extends User { + // Al specific props + // agrees_with_tos: boolean; + // classification: string; + default_view?: string; + dynamic_group: string | null; + // groups: string[]; + // is_active: boolean; + // roles: string[]; +} + +/** + * User information and System Configuration + * @summary Response of the /whoami/ route + * @description Return the currently logged in user as well as the system configuration + * */ +export type WhoAmI = { + /** Date the user agreed with TOS */ + agrees_with_tos: string; + + /** Avatar data block */ + avatar: string; + + /** Classification definition block */ + c12nDef: ClassificationDefinition; + + /** Classification of the user */ + classification: string; + + /** Configuration block */ + configuration: Configuration; + + /** Email of the user */ + email: string; + + /** Groups the user if member of */ + groups: string[]; + + /** Search indexes definitions */ + indexes: Indexes; + + /** Is the user active */ + is_active: boolean; + + /** Is the user an admin */ + is_admin: boolean; + + /** Name of the user */ + name: string; + + /** Roles the user is member of */ + roles: Role[]; + + /** User Settings configuration */ + settings: UserSettings; + + /** Types the user is */ + type: Type[]; + + /** Username of the current user */ + username: string; +}; diff --git a/src/components/models/utils/color.ts b/src/components/models/utils/color.ts new file mode 100644 index 000000000..f92a8317f --- /dev/null +++ b/src/components/models/utils/color.ts @@ -0,0 +1 @@ +export type PossibleColor = 'default' | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'; diff --git a/src/components/routes/account.tsx b/src/components/routes/account.tsx index a49c7e812..47c6f3023 100644 --- a/src/components/routes/account.tsx +++ b/src/components/routes/account.tsx @@ -1,5 +1,5 @@ import useAppUser from 'commons/components/app/hooks/useAppUser'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import User from 'components/routes/user'; export default function Account() { diff --git a/src/components/routes/admin.tsx b/src/components/routes/admin.tsx index 8181edf13..a8f21f82e 100644 --- a/src/components/routes/admin.tsx +++ b/src/components/routes/admin.tsx @@ -1,8 +1,8 @@ import useAppConfigs from 'commons/components/app/hooks/useAppConfigs'; import useAppUser from 'commons/components/app/hooks/useAppUser'; import PageCenter from 'commons/components/pages/PageCenter'; -import { CustomUser } from 'components/hooks/useMyUser'; import LinkGrid from 'components/layout/linkgrid'; +import { CustomUser } from 'components/models/ui/user'; import { Navigate } from 'react-router'; export default function Admin() { diff --git a/src/components/routes/admin/actions.tsx b/src/components/routes/admin/actions.tsx index 88e56e3ac..cff5e7891 100644 --- a/src/components/routes/admin/actions.tsx +++ b/src/components/routes/admin/actions.tsx @@ -16,7 +16,7 @@ import PageFullSize from 'commons/components/pages/PageFullSize'; import { useEffectOnce } from 'commons/components/utils/hooks/useEffectOnce'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import { RouterPrompt } from 'components/visual/RouterPrompt'; import { useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/routes/admin/error_detail.tsx b/src/components/routes/admin/error_detail.tsx index b137c7137..128715b95 100644 --- a/src/components/routes/admin/error_detail.tsx +++ b/src/components/routes/admin/error_detail.tsx @@ -11,7 +11,8 @@ import useAppUser from 'commons/components/app/hooks/useAppUser'; import PageCenter from 'commons/components/pages/PageCenter'; import useClipboard from 'commons/components/utils/hooks/useClipboard'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { Error } from 'components/models/base/error'; +import { CustomUser } from 'components/models/ui/user'; import { DEFAULT_TAB, TAB_OPTIONS } from 'components/routes/file/viewer'; import FileDownloader from 'components/visual/FileDownloader'; import 'moment/locale/fr'; @@ -22,20 +23,15 @@ import Moment from 'react-moment'; import { Navigate } from 'react-router'; import { Link, useLocation, useParams } from 'react-router-dom'; -export type Error = { - created: string; - id: string; - response: { - message: string; - service_debug_info: string; - service_name: string; - service_tool_version: string; - service_version: number | string; - status: string; - }; - sha256: string; - type: string; -}; +const useStyles = makeStyles(theme => ({ + clipboardIcon: { + marginRight: theme.spacing(1), + '&:hover': { + cursor: 'pointer', + transform: 'scale(1.1)' + } + } +})); type ParamProps = { key?: string; @@ -48,16 +44,6 @@ type ErrorDetailProps = { error_key?: string; }; -const useStyles = makeStyles(theme => ({ - clipboardIcon: { - marginRight: theme.spacing(1), - '&:hover': { - cursor: 'pointer', - transform: 'scale(1.1)' - } - } -})); - export const ErrorDetail = ({ error_key }: ErrorDetailProps) => { const { t, i18n } = useTranslation(['adminErrorViewer']); const classes = useStyles(); diff --git a/src/components/routes/admin/error_viewer.tsx b/src/components/routes/admin/error_viewer.tsx index e5af40798..fd70a2374 100644 --- a/src/components/routes/admin/error_viewer.tsx +++ b/src/components/routes/admin/error_viewer.tsx @@ -10,7 +10,9 @@ import PageHeader from 'commons/components/pages/PageHeader'; import { useEffectOnce } from 'commons/components/utils/hooks/useEffectOnce'; import useDrawer from 'components/hooks/useDrawer'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { Error } from 'components/models/base/error'; +import { SearchResult } from 'components/models/ui/search'; +import { CustomUser } from 'components/models/ui/user'; import { ChipList } from 'components/visual/ChipList'; import Histogram from 'components/visual/Histogram'; import LineGraph from 'components/visual/LineGraph'; @@ -48,13 +50,6 @@ const useStyles = makeStyles(theme => ({ } })); -type ErrorResults = { - items: any[]; - offset: number; - rows: number; - total: number; -}; - const DEFAULT_TC = '4d'; const TC_MAP = { @@ -84,7 +79,7 @@ export default function ErrorViewer() { const { t } = useTranslation(['adminErrorViewer']); const [pageSize] = useState(PAGE_SIZE); const [searching, setSearching] = useState(false); - const [errorResults, setErrorResults] = useState(null); + const [errorResults, setErrorResults] = useState>(null); const classes = useStyles(); const navigate = useNavigate(); const [query, setQuery] = useState(null); diff --git a/src/components/routes/admin/identify.tsx b/src/components/routes/admin/identify.tsx index 8cacb3891..c78923137 100644 --- a/src/components/routes/admin/identify.tsx +++ b/src/components/routes/admin/identify.tsx @@ -5,7 +5,7 @@ import useAppUser from 'commons/components/app/hooks/useAppUser'; import PageFullSize from 'commons/components/pages/PageFullSize'; import { useEffectOnce } from 'commons/components/utils/hooks/useEffectOnce'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import { RouterPrompt } from 'components/visual/RouterPrompt'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/routes/admin/service_detail.tsx b/src/components/routes/admin/service_detail.tsx index fb5d20848..84d05c4e0 100644 --- a/src/components/routes/admin/service_detail.tsx +++ b/src/components/routes/admin/service_detail.tsx @@ -20,7 +20,8 @@ import PageCenter from 'commons/components/pages/PageCenter'; import { useEffectOnce } from 'commons/components/utils/hooks/useEffectOnce'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { Service as ServiceData, ServiceConstants } from 'components/models/base/service'; +import { CustomUser } from 'components/models/ui/user'; import ConfirmationDialog from 'components/visual/ConfirmationDialog'; import CustomChip from 'components/visual/CustomChip'; import Empty from 'components/visual/Empty'; @@ -35,127 +36,6 @@ import ServiceGeneral from './service_detail/general'; import ServiceParams from './service_detail/parameters'; import ServiceUpdater from './service_detail/updater'; -type ServiceProps = { - name?: string | null; - onDeleted?: () => void; - onUpdated?: () => void; -}; - -type ParamProps = { - svc: string; -}; - -export type Volume = { - capacity: string; - mount_path: string; - storage_class: string; - access_mode: 'ReadWriteOnce' | 'ReadWriteMany'; -}; - -export type Environment = { - name: string; - value: string; -}; - -export type Container = { - allow_internet_access: boolean; - command: string[]; - cpu_cores: number; - environment: Environment[]; - image: string; - ports: string[]; - ram_mb: number; - ram_mb_min: number; - registry_password: string; - registry_username: string; - registry_type: 'docker' | 'harbor'; - service_account?: string; -}; - -export type SubmissionParams = { - default: string | boolean | number; - name: string; - type: 'int' | 'bool' | 'str' | 'list'; - value: string | boolean | number; - list?: string[]; - hide?: boolean; -}; - -export type SourceStatus = { - last_successful_update: string; - state: string; - message: string; - ts: string; -}; - -export type Source = { - ca_cert: string; - default_classification: string; - headers: Environment[]; - name: string; - password: string; - pattern: string; - private_key: string; - proxy: string; - ssl_ignore_errors: boolean; - uri: string; - username: string; - git_branch: string; - status: SourceStatus; - sync: boolean; -}; - -type UpdateConfig = { - generates_signatures: boolean; - method: 'run' | 'build'; - run_options: Container; - sources: Source[]; - update_interval_seconds: number; - wait_for_update: boolean; - signature_delimiter: 'new_line' | 'double_new_line' | 'pipe' | 'comma' | 'space' | 'none' | 'file' | 'custom'; - custom_delimiter: string; -}; - -export type ServiceDetail = { - accepts: string; - category: string; - classification: string; - config: { - [name: string]: string; - }; - default_result_classification: string; - dependencies: { - [name: string]: { - container: Container; - volumes: { - [name: string]: Volume; - }; - }; - }; - description: string; - disable_cache: boolean; - docker_config: Container; - enabled: boolean; - is_external: boolean; - licence_count: number; - min_instances?: number; - max_queue_length: number; - name: string; - privileged: boolean; - rejects: string; - stage: string; - submission_params: SubmissionParams[]; - timeout: number; - update_channel: 'dev' | 'beta' | 'rc' | 'stable'; - update_config: UpdateConfig; - version: string; -}; - -export type ServiceConstants = { - categories: string[]; - stages: string[]; -}; - const useStyles = makeStyles(() => ({ buttonProgress: { position: 'absolute', @@ -166,27 +46,40 @@ const useStyles = makeStyles(() => ({ } })); +const TAB_TYPES = ['general', 'docker', 'updater', 'params'] as const; +type TabType = (typeof TAB_TYPES)[number]; + +type ParamProps = { + svc: string; +}; + +type ServiceProps = { + name?: string | null; + onDeleted?: () => void; + onUpdated?: () => void; +}; + function Service({ name, onDeleted, onUpdated }: ServiceProps) { - const { svc } = useParams(); const { t } = useTranslation(['adminServices']); - const [service, setService] = useState(null); - const [serviceDefault, setServiceDefault] = useState(null); + const theme = useTheme(); + const navigate = useNavigate(); + const classes = useStyles(); + const { svc } = useParams(); + const { apiCall } = useMyAPI(); + const { user: currentUser } = useAppUser(); + const { showSuccessMessage } = useMySnackbar(); + + const [service, setService] = useState(null); + const [serviceDefault, setServiceDefault] = useState(null); const [serviceVersion, setServiceVersion] = useState(null); const [serviceGeneralError, setServiceGeneralError] = useState(false); const [overallError, setOverallError] = useState(false); const [constants, setConstants] = useState(null); const [versions, setVersions] = useState(null); - const [tab, setTab] = useState('general'); - const [modified, setModified] = useState(false); - const [buttonLoading, setButtonLoading] = useState(false); - const theme = useTheme(); - const [deleteDialog, setDeleteDialog] = useState(false); - const { user: currentUser } = useAppUser(); - const { showSuccessMessage } = useMySnackbar(); - const navigate = useNavigate(); - const classes = useStyles(); - - const { apiCall } = useMyAPI(); + const [tab, setTab] = useState('general'); + const [modified, setModified] = useState(false); + const [buttonLoading, setButtonLoading] = useState(false); + const [deleteDialog, setDeleteDialog] = useState(false); function saveService() { apiCall({ diff --git a/src/components/routes/admin/service_detail/container.tsx b/src/components/routes/admin/service_detail/container.tsx index d2b328bd0..8b4e2907e 100644 --- a/src/components/routes/admin/service_detail/container.tsx +++ b/src/components/routes/admin/service_detail/container.tsx @@ -14,18 +14,18 @@ import { Typography, useTheme } from '@mui/material'; +import { Service } from 'components/models/base/service'; import 'moment/locale/fr'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { ServiceDetail } from '../service_detail'; import ContainerCard from './container_card'; import ContainerDialog from './container_dialog'; import ResetButton from './reset_button'; type ServiceContainerProps = { - service: ServiceDetail; - defaults: ServiceDetail; - setService: (value: ServiceDetail) => void; + service: Service; + defaults: Service; + setService: (value: Service) => void; setModified: (value: boolean) => void; }; diff --git a/src/components/routes/admin/service_detail/container_card.tsx b/src/components/routes/admin/service_detail/container_card.tsx index e6a79e42e..401ee766c 100644 --- a/src/components/routes/admin/service_detail/container_card.tsx +++ b/src/components/routes/admin/service_detail/container_card.tsx @@ -1,10 +1,10 @@ import { Card, Grid, Tooltip, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import { DockerConfig, PersistentVolume } from 'components/models/base/service'; import 'moment/locale/fr'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { CgSmartphoneChip, CgSmartphoneRam } from 'react-icons/cg'; -import { Container, Volume } from '../service_detail'; import ContainerDialog from './container_dialog'; const useStyles = makeStyles(theme => ({ @@ -33,11 +33,11 @@ const useStyles = makeStyles(theme => ({ })); type ContainerCardProps = { - container: Container; - defaults: Container; + container: DockerConfig; + defaults: DockerConfig; name?: string; - volumes?: { [name: string]: Volume }; - onChange: (newContainer: Container, name?: string, newVolumes?: { [name: string]: Volume }) => void; + volumes?: { [name: string]: PersistentVolume }; + onChange: (newContainer: DockerConfig, name?: string, newVolumes?: { [name: string]: PersistentVolume }) => void; }; const WrappedContainerCard = ({ container, defaults, name, volumes, onChange }: ContainerCardProps) => { @@ -94,7 +94,9 @@ const WrappedContainerCard = ({ container, defaults, name, volumes, onChange }: {container.service_account && ( <> - {`${t('container.card.service_account')}:`} + {`${t( + 'container.card.service_account' + )}:`} {container.service_account} diff --git a/src/components/routes/admin/service_detail/container_dialog.tsx b/src/components/routes/admin/service_detail/container_dialog.tsx index df0ef1222..9e148dcae 100644 --- a/src/components/routes/admin/service_detail/container_dialog.tsx +++ b/src/components/routes/admin/service_detail/container_dialog.tsx @@ -19,29 +19,29 @@ import { useTheme } from '@mui/material'; import FormControl from '@mui/material/FormControl'; +import { + DEFAULT_DOCKER_CONFIG, + DEFAULT_ENVIRONMENT_VARIABLE, + DEFAULT_PERSISTENT_VOLUME, + DockerConfig, + EnvironmentVariable, + PersistentVolume +} from 'components/models/base/service'; import 'moment/locale/fr'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Container, Volume } from '../service_detail'; import ResetButton from './reset_button'; -type EnvironmentVar = { - name: string; - value: string; -}; - type EnvironmentProps = { - envVar?: EnvironmentVar; - onAdd?: (envVar: EnvironmentVar) => void; - onUpdate?: (envVar: EnvironmentVar) => void; - onDelete?: (envVar: EnvironmentVar) => void; + envVar?: EnvironmentVariable; + onAdd?: (envVar: EnvironmentVariable) => void; + onUpdate?: (envVar: EnvironmentVariable) => void; + onDelete?: (envVar: EnvironmentVariable) => void; }; -const DEFAULT_ENV = { name: '', value: '' }; - const WrappedEnvironment = ({ envVar, onAdd, onUpdate, onDelete }: EnvironmentProps) => { const { t } = useTranslation(['adminServices']); - const [tempEnvVar, setTempEnvVar] = useState(DEFAULT_ENV); + const [tempEnvVar, setTempEnvVar] = useState(DEFAULT_ENVIRONMENT_VARIABLE); const theme = useTheme(); const handleValueUpdate = event => { @@ -50,7 +50,7 @@ const WrappedEnvironment = ({ envVar, onAdd, onUpdate, onDelete }: EnvironmentPr const addEnvironment = () => { onAdd(tempEnvVar); - setTempEnvVar(DEFAULT_ENV); + setTempEnvVar(DEFAULT_ENVIRONMENT_VARIABLE); }; const handleNameChange = event => { @@ -150,27 +150,20 @@ const Environment = React.memo(WrappedEnvironment); type VolumeControlProps = { name?: string; - vol?: Volume; - onAdd?: (name: string, vol: Volume) => void; + vol?: PersistentVolume; + onAdd?: (name: string, vol: PersistentVolume) => void; onDelete?: (name: string) => void; }; -const DEFAULT_VOL: Volume = { - capacity: '', - mount_path: '', - storage_class: '', - access_mode: 'ReadWriteOnce' -}; - const WrappedVolumeControl = ({ name, vol, onAdd, onDelete }: VolumeControlProps) => { const { t } = useTranslation(['adminServices']); - const [tempVol, setTempVol] = useState(DEFAULT_VOL); + const [tempVol, setTempVol] = useState(DEFAULT_PERSISTENT_VOLUME); const [tempName, setTempName] = useState(''); const theme = useTheme(); const addVolume = () => { onAdd(tempName, tempVol); - setTempVol(DEFAULT_VOL); + setTempVol(DEFAULT_PERSISTENT_VOLUME); setTempName(''); }; @@ -294,29 +287,14 @@ WrappedVolumeControl.defaultProps = { const VolumeControl = React.memo(WrappedVolumeControl); -const DEFAULT_CONTAINER: Container = { - allow_internet_access: false, - command: null, - cpu_cores: 1, - environment: [], - image: '', - ports: [], - ram_mb: 512, - ram_mb_min: 128, - registry_password: '', - registry_username: '', - registry_type: 'docker', - service_account: '' -}; - type ContainerDialogProps = { open: boolean; setOpen: (value: boolean) => void; - container?: Container; - defaults?: Container; + container?: DockerConfig; + defaults?: DockerConfig; name?: string; - volumes?: { [name: string]: Volume }; - onSave: (newContainer: Container, name?: string, newVolumes?: { [name: string]: Volume }) => void; + volumes?: { [name: string]: PersistentVolume }; + onSave: (newContainer: DockerConfig, name?: string, newVolumes?: { [name: string]: PersistentVolume }) => void; }; const WrappedContainerDialog = ({ @@ -330,7 +308,7 @@ const WrappedContainerDialog = ({ }: ContainerDialogProps) => { const { t } = useTranslation(['adminServices']); const [modified, setModified] = useState(false); - const [tempContainer, setTempContainer] = useState(container || DEFAULT_CONTAINER); + const [tempContainer, setTempContainer] = useState(container || DEFAULT_DOCKER_CONFIG); const [tempName, setTempName] = useState(name); const [tempVolumes, setTempVolumes] = useState(volumes); const theme = useTheme(); @@ -344,7 +322,7 @@ const WrappedContainerDialog = ({ const handleClose = () => { setOpen(false); setModified(false); - setTempContainer(container || DEFAULT_CONTAINER); + setTempContainer(container || DEFAULT_DOCKER_CONFIG); setTempName(name); setTempVolumes(volumes); }; diff --git a/src/components/routes/admin/service_detail/general.tsx b/src/components/routes/admin/service_detail/general.tsx index ee6897868..686d22b4f 100644 --- a/src/components/routes/admin/service_detail/general.tsx +++ b/src/components/routes/admin/service_detail/general.tsx @@ -13,19 +13,19 @@ import { import FormControl from '@mui/material/FormControl'; import InputAdornment from '@mui/material/InputAdornment'; import useALContext from 'components/hooks/useALContext'; +import { Service, ServiceConstants } from 'components/models/base/service'; import Classification from 'components/visual/Classification'; import 'moment/locale/fr'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { ServiceConstants, ServiceDetail } from '../service_detail'; import ResetButton from './reset_button'; type ServiceGeneralProps = { - service: ServiceDetail; - defaults: ServiceDetail; + service: Service; + defaults: Service; constants: ServiceConstants; versions: string[]; - setService: (value: ServiceDetail) => void; + setService: (value: Service) => void; setModified: (value: boolean) => void; setError: (value: boolean) => void; }; diff --git a/src/components/routes/admin/service_detail/multi_type_config.tsx b/src/components/routes/admin/service_detail/multi_type_config.tsx index 63d5fdd5e..c4541f48c 100644 --- a/src/components/routes/admin/service_detail/multi_type_config.tsx +++ b/src/components/routes/admin/service_detail/multi_type_config.tsx @@ -2,20 +2,21 @@ import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'; import RemoveCircleOutlineOutlinedIcon from '@mui/icons-material/RemoveCircleOutlineOutlined'; import { Grid, IconButton, MenuItem, Select, TextField, Tooltip, useTheme } from '@mui/material'; import FormControl from '@mui/material/FormControl'; +import { Service } from 'components/models/base/service'; import 'moment/locale/fr'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import ReactJson from 'react-json-view'; type ServiceConfig = { - name: string; - value: any; + name: keyof Service['config']; + value: Service['config'][any]; }; type ExtendedServiceConfig = { - name: string; + name: keyof Service['config']; type: 'bool' | 'int' | 'list' | 'str' | 'json'; - value: any; + value: Service['config'][any]; }; type MultiTypeConfigProps = { diff --git a/src/components/routes/admin/service_detail/multi_type_param.tsx b/src/components/routes/admin/service_detail/multi_type_param.tsx index 31aaab5a9..9d747770a 100644 --- a/src/components/routes/admin/service_detail/multi_type_param.tsx +++ b/src/components/routes/admin/service_detail/multi_type_param.tsx @@ -15,40 +15,22 @@ import { useTheme } from '@mui/material'; import FormControl from '@mui/material/FormControl'; +import { DEFAULT_SERVICE_PARAMETER, ServiceParameter } from 'components/models/base/service'; import 'moment/locale/fr'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { SubmissionParams } from '../service_detail'; - -type SimpleSubmissionParams = { - name: string; - type: 'int' | 'bool' | 'str' | 'list'; - default: string; - value: string; - list: string[]; - hide: string; -}; type MultiTypeParamProps = { - param?: SubmissionParams; + param?: ServiceParameter; id?: number; - onAdd?: (param: SubmissionParams) => void; - onUpdate?: (param: SubmissionParams, id: number) => void; + onAdd?: (param: ServiceParameter) => void; + onUpdate?: (param: ServiceParameter, id: number) => void; onDelete?: (id: number) => void; }; -const DEFAULT_USER_PARAM: SimpleSubmissionParams = { - name: '', - type: 'bool', - default: 'false', - value: 'false', - list: [], - hide: 'false' -}; - const WrappedMultiTypeParam = ({ param, id, onAdd, onUpdate, onDelete }: MultiTypeParamProps) => { const { t } = useTranslation(['adminServices']); - const [tempUserParams, setTempUserParams] = useState(DEFAULT_USER_PARAM); + const [tempUserParams, setTempUserParams] = useState(DEFAULT_SERVICE_PARAMETER); const theme = useTheme(); const handleSubmissionParamUpdate = event => { @@ -94,13 +76,13 @@ const WrappedMultiTypeParam = ({ param, id, onAdd, onUpdate, onDelete }: MultiTy } else { onAdd({ ...tempUserParams, - default: parseInt(tempUserParams.default) || 0, - value: parseInt(tempUserParams.default) || 0, + default: parseInt(tempUserParams.default as unknown as string) || 0, + value: parseInt(tempUserParams.default as unknown as string) || 0, hide: tempUserParams.hide === 'true' }); } - setTempUserParams(DEFAULT_USER_PARAM); + setTempUserParams(DEFAULT_SERVICE_PARAMETER); }; const handleSPNameChange = event => { diff --git a/src/components/routes/admin/service_detail/parameters.tsx b/src/components/routes/admin/service_detail/parameters.tsx index 5504b163a..4be464334 100644 --- a/src/components/routes/admin/service_detail/parameters.tsx +++ b/src/components/routes/admin/service_detail/parameters.tsx @@ -1,14 +1,14 @@ import { Grid, Typography, useTheme } from '@mui/material'; +import { Service } from 'components/models/base/service'; import 'moment/locale/fr'; import { useCallback, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; -import { ServiceDetail } from '../service_detail'; import MultiTypeConfig from './multi_type_config'; import MultiTypeParam from './multi_type_param'; type ServiceParamsProps = { - service: ServiceDetail; - setService: (value: ServiceDetail) => void; + service: Service; + setService: (value: Service) => void; setModified: (value: boolean) => void; }; diff --git a/src/components/routes/admin/service_detail/reset_button.tsx b/src/components/routes/admin/service_detail/reset_button.tsx index f2bcd661d..a5b86955e 100644 --- a/src/components/routes/admin/service_detail/reset_button.tsx +++ b/src/components/routes/admin/service_detail/reset_button.tsx @@ -1,31 +1,32 @@ import { Button, Tooltip, useTheme } from '@mui/material'; +import { DockerConfig, Service, UpdateConfig } from 'components/models/base/service'; import 'moment/locale/fr'; -import React from 'react'; +import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -type ResetButtonProps = { - service: any; - defaults: any; +type Props = { + service: DockerConfig | Service | UpdateConfig; + defaults: DockerConfig | Service | UpdateConfig; field: string | string[]; reset: () => void; }; -const WrappedResetButton = ({ service, defaults, field, reset }: ResetButtonProps) => { +const WrappedResetButton = ({ service, defaults, field, reset }: Props) => { const { t } = useTranslation(['adminServices']); const theme = useTheme(); - const getValue = (obj, fieldname) => { + const getValue = useCallback((obj, fieldname) => { const val = obj[fieldname] || null; return Array.isArray(val) ? JSON.stringify(val) : val; - }; + }, []); - const hasChanges = () => { + const hasChanges = useCallback(() => { if (Array.isArray(field)) { return field.some(elem => getValue(service, elem) !== getValue(defaults, elem)); } else { return getValue(service, field) !== getValue(defaults, field); } - }; + }, [defaults, field, getValue, service]); return service && defaults && hasChanges() ? ( diff --git a/src/components/routes/admin/service_detail/source_dialog.tsx b/src/components/routes/admin/service_detail/source_dialog.tsx index e0834b690..a4ce57c20 100644 --- a/src/components/routes/admin/service_detail/source_dialog.tsx +++ b/src/components/routes/admin/service_detail/source_dialog.tsx @@ -1,42 +1,20 @@ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, useTheme } from '@mui/material'; import useALContext from 'components/hooks/useALContext'; +import { DEFAULT_SOURCE, UpdateSource } from 'components/models/base/service'; import { SourceDetail } from 'components/routes/manage/signature_sources_details'; import 'moment/locale/fr'; import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Source } from '../service_detail'; -const DEFAULT_SOURCE: Source = { - ca_cert: '', - default_classification: '', - headers: [], - name: '', - password: '', - pattern: '', - private_key: '', - proxy: '', - ssl_ignore_errors: false, - uri: '', - username: '', - git_branch: '', - status: { - last_successful_update: '', - message: '', - state: '', - ts: '' - }, - sync: false -}; - -type SourceDialogProps = { +type Props = { open: boolean; setOpen: (value: boolean) => void; - source?: Source; - defaults?: Source; - onSave: (newSource: Source) => void; + source?: UpdateSource; + defaults?: UpdateSource; + onSave: (newSource: UpdateSource) => void; }; -const WrappedSourceDialog = ({ open, setOpen, source, defaults, onSave }: SourceDialogProps) => { +const WrappedSourceDialog = ({ open, setOpen, source, defaults, onSave }: Props) => { const { t } = useTranslation(['adminServices']); const [modified, setModified] = useState(false); const { c12nDef } = useALContext(); diff --git a/src/components/routes/admin/service_detail/updater.tsx b/src/components/routes/admin/service_detail/updater.tsx index aa1c2e998..68eb88170 100644 --- a/src/components/routes/admin/service_detail/updater.tsx +++ b/src/components/routes/admin/service_detail/updater.tsx @@ -18,18 +18,18 @@ import { useTheme } from '@mui/material'; import FormControl from '@mui/material/FormControl'; +import { Service, UpdateSource } from 'components/models/base/service'; import { SourceCard } from 'components/routes/manage/signature_sources'; import 'moment/locale/fr'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { ServiceDetail, Source } from '../service_detail'; import ResetButton from './reset_button'; import SourceDialog from './source_dialog'; type ServiceUpdaterProps = { - service: ServiceDetail; - defaults: ServiceDetail; - setService: (value: ServiceDetail) => void; + service: Service; + defaults: Service; + setService: (value: Service) => void; setModified: (value: boolean) => void; }; @@ -58,8 +58,8 @@ const marks = [ const ServiceUpdater = ({ service, defaults, setService, setModified }: ServiceUpdaterProps) => { const { t } = useTranslation(['adminServices']); - const [dialog, setDialog] = useState(false); - const [editDialog, setEditDialog] = useState(false); + const [dialog, setDialog] = useState(false); + const [editDialog, setEditDialog] = useState(false); const [editedSourceID, setEditedSourceID] = useState(-1); const theme = useTheme(); @@ -136,7 +136,7 @@ const ServiceUpdater = ({ service, defaults, setService, setModified }: ServiceU }); }; - const findDefaults = (curSource: Source) => { + const findDefaults = (curSource: UpdateSource) => { if (defaults && defaults.update_config && defaults.update_config.sources) { return defaults.update_config.sources.find(element => { if (curSource.name === element.name) { diff --git a/src/components/routes/admin/service_review.tsx b/src/components/routes/admin/service_review.tsx index 0098d849c..5eeec2bae 100644 --- a/src/components/routes/admin/service_review.tsx +++ b/src/components/routes/admin/service_review.tsx @@ -1,21 +1,36 @@ import ArrowDownwardOutlinedIcon from '@mui/icons-material/ArrowDownwardOutlined'; import ArrowUpwardOutlinedIcon from '@mui/icons-material/ArrowUpwardOutlined'; import ErrorOutlineOutlinedIcon from '@mui/icons-material/ErrorOutlineOutlined'; -import { Grid, IconButton, MenuItem, Select, Skeleton, Tooltip, Typography, useTheme } from '@mui/material'; +import { + Grid, + IconButton, + MenuItem, + Select, + Skeleton, + Tooltip, + Typography, + TypographyProps, + useTheme +} from '@mui/material'; import FormControl from '@mui/material/FormControl'; import useAppUser from 'commons/components/app/hooks/useAppUser'; import PageFullWidth from 'commons/components/pages/PageFullWidth'; import { useEffectOnce } from 'commons/components/utils/hooks/useEffectOnce'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { Service as ServiceData } from 'components/models/base/service'; +import { ServiceStats as ServiceStatsData } from 'components/models/ui/service'; +import { CustomUser } from 'components/models/ui/user'; import LineGraph from 'components/visual/LineGraph'; import { getVersionQuery } from 'helpers/utils'; import 'moment/locale/fr'; -import { useEffect, useState } from 'react'; +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Navigate } from 'react-router'; import { Link, useLocation } from 'react-router-dom'; +// TODO: version doesn't seem to be set correctly +type ServiceStats = ServiceStatsData & { version: string }; + function getDescendantProp(obj, desc) { if (obj == null) return null; @@ -24,7 +39,14 @@ function getDescendantProp(obj, desc) { return obj; } -function DiffNumber({ stats, comp, field, variant = 'h4' as 'h4' }) { +type DiffNumberProps = { + stats: ServiceStats; + comp: ServiceStats; + field: string; + variant?: TypographyProps['variant']; +}; + +function DiffNumber({ stats, comp, field, variant = 'h4' as 'h4' }: DiffNumberProps) { const prop1 = getDescendantProp(stats, field); const prop2 = getDescendantProp(comp, field); const v1 = Math.round(prop1); @@ -44,7 +66,15 @@ function DiffNumber({ stats, comp, field, variant = 'h4' as 'h4' }) { ); } -function Counter({ stats, comp, field, titleVariant = 'h6' as 'h6', numberVariant = 'h4' as 'h4' }) { +type CounterProps = { + stats: ServiceStats; + comp: ServiceStats; + field: string; + titleVariant?: TypographyProps['variant']; + numberVariant?: TypographyProps['variant']; +}; + +function Counter({ stats, comp, field, titleVariant = 'h6' as 'h6', numberVariant = 'h4' as 'h4' }: CounterProps) { const { t } = useTranslation(['adminServiceReview']); const theme = useTheme(); return ( @@ -57,7 +87,13 @@ function Counter({ stats, comp, field, titleVariant = 'h6' as 'h6', numberVarian ); } -function ServiceDetail({ stats, comp, show }) { +type ServiceDetailProps = { + stats: ServiceStats; + comp: ServiceStats; + show?: boolean; +}; + +function ServiceDetail({ stats, comp, show }: ServiceDetailProps) { const { t } = useTranslation(['adminServiceReview']); const theme = useTheme(); return ( @@ -125,7 +161,15 @@ function ServiceDetail({ stats, comp, show }) { ); } -function VersionSelector({ possibleVersions, selectedService, version, setVersion, except }) { +type VersionSelectorProps = { + possibleVersions: string[]; + selectedService: string; + version: string; + setVersion: Dispatch>; + except: string; +}; + +function VersionSelector({ possibleVersions, selectedService, version, setVersion, except }: VersionSelectorProps) { const theme = useTheme(); const { t } = useTranslation(['adminServiceReview']); return selectedService && possibleVersions ? ( @@ -165,13 +209,13 @@ export default function ServiceReview() { const defaultVersion1 = params.get('v1') || ''; const defaultVersion2 = params.get('v2') || ''; - const [services, setServices] = useState(null); - const [selectedService, setSelectedService] = useState(defaultSelectedService); - const [possibleVersions, setPossibleVersions] = useState(null); - const [version1, setVersion1] = useState(defaultVersion1); - const [version2, setVersion2] = useState(defaultVersion2); - const [stats1, setStats1] = useState(null); - const [stats2, setStats2] = useState(null); + const [services, setServices] = useState(null); + const [selectedService, setSelectedService] = useState(defaultSelectedService); + const [possibleVersions, setPossibleVersions] = useState(null); + const [version1, setVersion1] = useState(defaultVersion1); + const [version2, setVersion2] = useState(defaultVersion2); + const [stats1, setStats1] = useState(null); + const [stats2, setStats2] = useState(null); const { apiCall } = useMyAPI(); const { user: currentUser } = useAppUser(); @@ -185,7 +229,7 @@ export default function ServiceReview() { useEffect(() => { if (selectedService !== '' && version1) { setStats1(null); - apiCall({ + apiCall({ url: `/api/v4/service/stats/${selectedService}/?version=${version1}`, onSuccess: api_data => { setStats1(api_data.api_response); @@ -198,7 +242,7 @@ export default function ServiceReview() { useEffect(() => { if (selectedService !== '' && version2) { setStats2(null); - apiCall({ + apiCall({ url: `/api/v4/service/stats/${selectedService}/?version=${version2}`, onSuccess: api_data => { setStats2(api_data.api_response); @@ -210,7 +254,7 @@ export default function ServiceReview() { useEffect(() => { if (selectedService !== '') { - apiCall({ + apiCall({ url: `/api/v4/service/versions/${selectedService}/`, onSuccess: api_data => { setPossibleVersions(api_data.api_response); @@ -222,7 +266,7 @@ export default function ServiceReview() { useEffectOnce(() => { if (currentUser.is_admin) { - apiCall({ + apiCall({ url: '/api/v4/service/all/', onSuccess: api_data => { setServices(api_data.api_response.map(srv => srv.name)); diff --git a/src/components/routes/admin/services.tsx b/src/components/routes/admin/services.tsx index bb81938ea..2e449e341 100644 --- a/src/components/routes/admin/services.tsx +++ b/src/components/routes/admin/services.tsx @@ -25,13 +25,14 @@ import useALContext from 'components/hooks/useALContext'; import useDrawer from 'components/hooks/useDrawer'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { CustomUser } from 'components/hooks/useMyUser'; -import Service from 'components/routes/admin/service_detail'; +import { ServiceIndexed, ServiceUpdates } from 'components/models/base/service'; +import { CustomUser } from 'components/models/ui/user'; +import ServiceDetail from 'components/routes/admin/service_detail'; import ConfirmationDialog from 'components/visual/ConfirmationDialog'; import FileDownloader from 'components/visual/FileDownloader'; -import ServiceTable, { ServiceResult } from 'components/visual/SearchResult/service'; +import { JSONFeedItem, useNotificationFeed } from 'components/visual/Notification/useNotificationFeed'; +import ServiceTable from 'components/visual/SearchResult/service'; import NewServiceTable from 'components/visual/ServiceManagement/NewServiceTable'; -import { JSONFeedItem, useNotificationFeed } from 'components/visual/ServiceManagement/useNotificationFeed'; import 'moment/locale/fr'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -39,30 +40,33 @@ import { Navigate, useLocation, useNavigate } from 'react-router'; export default function Services() { const { t } = useTranslation(['adminServices']); - const [serviceResults, setServiceResults] = useState(null); - const [updates, setUpdates] = useState(null); - const [open, setOpen] = useState(false); - const [openRestore, setOpenRestore] = useState(false); - const [restoreConfirmation, setRestoreConfirmation] = useState(false); - const [waitingDialog, setWaitingDialog] = useState(false); - const [manifest, setManifest] = useState(''); - const [restore, setRestore] = useState(''); - const { showSuccessMessage, showInfoMessage, showErrorMessage } = useMySnackbar(); const theme = useTheme(); - const { apiCall } = useMyAPI(); - const { user: currentUser } = useAppUser(); - const { setGlobalDrawer, closeGlobalDrawer, globalDrawerOpened } = useDrawer(); const location = useLocation(); const navigate = useNavigate(); - const isXL = useMediaQuery(theme.breakpoints.only('xl')); const { configuration } = useALContext(); const { fetchJSONNotifications } = useNotificationFeed(); + const { apiCall } = useMyAPI(); + const { user: currentUser } = useAppUser(); + const { showSuccessMessage, showInfoMessage, showErrorMessage } = useMySnackbar(); + const { setGlobalDrawer, closeGlobalDrawer, globalDrawerOpened } = useDrawer(); + + const [serviceResults, setServiceResults] = useState(null); + const [updates, setUpdates] = useState(null); + const [open, setOpen] = useState(false); + const [openRestore, setOpenRestore] = useState(false); + const [restoreConfirmation, setRestoreConfirmation] = useState(false); + const [waitingDialog, setWaitingDialog] = useState(false); + const [manifest, setManifest] = useState(''); + const [restore, setRestore] = useState(''); const [serviceFeeds, setServiceFeeds] = useState(null); const [availableServices, setAvailableServices] = useState(null); const [installingServices, setInstallingServices] = useState([]); + const lastInstallingServices = useRef([]); const installingServicesTimeout = useRef(null); + const isXL = useMediaQuery(theme.breakpoints.only('xl')); + const handleAddService = () => { apiCall({ method: 'PUT', @@ -77,10 +81,10 @@ export default function Services() { }); }; - const closeServiceDialog = () => { + const closeServiceDialog = useCallback(() => { setManifest(''); setOpen(false); - }; + }, []); const handleRestore = () => { apiCall({ @@ -113,11 +117,11 @@ export default function Services() { } const reload = useCallback(() => { - apiCall({ + apiCall({ url: '/api/v4/service/all/', onSuccess: api_data => setServiceResults(api_data.api_response) }); - apiCall({ + apiCall({ url: '/api/v4/service/updates/', onSuccess: api_data => setUpdates(api_data.api_response) }); @@ -125,7 +129,7 @@ export default function Services() { }, []); const pollInstalling = useCallback(first => { - apiCall({ + apiCall({ url: '/api/v4/service/installing/', onSuccess: api_data => { if (first) { @@ -172,15 +176,15 @@ export default function Services() { const updateAll = useCallback( () => { - apiCall({ + apiCall<{ updated: string[]; updating: string[] }>({ url: '/api/v4/service/update_all/', - onSuccess: resp => { + onSuccess: api_data => { const newUpdates = { ...updates }; - for (const srv of resp.api_response.updating) { + for (const srv of api_data.api_response.updating) { newUpdates[srv] = { ...newUpdates[srv], updating: true }; } - for (const srv of resp.api_response.updated) { + for (const srv of api_data.api_response.updated) { delete newUpdates[srv]; } setUpdates(newUpdates); @@ -224,7 +228,7 @@ export default function Services() { useEffect(() => { if (location.hash) { - setGlobalDrawer(); + setGlobalDrawer(); } else { closeGlobalDrawer(); } diff --git a/src/components/routes/admin/site_map.tsx b/src/components/routes/admin/site_map.tsx index 9e856cd5e..003a7595e 100644 --- a/src/components/routes/admin/site_map.tsx +++ b/src/components/routes/admin/site_map.tsx @@ -19,7 +19,10 @@ import useAppUser from 'commons/components/app/hooks/useAppUser'; import PageFullWidth from 'commons/components/pages/PageFullWidth'; import { useEffectOnce } from 'commons/components/utils/hooks/useEffectOnce'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { Role } from 'components/models/base/user'; +import { SiteMap as TSiteMap } from 'components/models/ui'; +import { CustomUser } from 'components/models/ui/user'; +import { PossibleColor } from 'components/models/utils/color'; import CustomChip from 'components/visual/CustomChip'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -44,11 +47,12 @@ const StyledTableCell = withStyles((theme: Theme) => export default function SiteMap() { const { t } = useTranslation(['adminSiteMap']); const theme = useTheme(); - const [siteMap, setSiteMap] = useState(null); const { apiCall } = useMyAPI(); const { user: currentUser } = useAppUser(); - const reqMapColor = { + const [siteMap, setSiteMap] = useState(null); + + const reqMapColor: Record = { signature_import: 'success', signature_manage: 'info', signature_view: 'default', @@ -77,7 +81,13 @@ export default function SiteMap() { file_detail: 'default', file_download: 'warning', replay_trigger: 'warning', - replay_system: 'info' + replay_system: 'warning', + archive_comment: 'default', + external_query: 'default', + file_purge: 'default', + heuristic_view: 'default', + retrohunt_run: 'default', + retrohunt_view: 'default' }; useEffectOnce(() => { diff --git a/src/components/routes/admin/tag_safelist.tsx b/src/components/routes/admin/tag_safelist.tsx index ceec09bea..6c8d0d884 100644 --- a/src/components/routes/admin/tag_safelist.tsx +++ b/src/components/routes/admin/tag_safelist.tsx @@ -16,7 +16,7 @@ import PageFullSize from 'commons/components/pages/PageFullSize'; import { useEffectOnce } from 'commons/components/utils/hooks/useEffectOnce'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import { RouterPrompt } from 'components/visual/RouterPrompt'; import { useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -28,15 +28,17 @@ loader.config({ paths: { vs: '/cdn/monaco_0.35.0/vs' } }); export default function AdminTagSafelist() { const { t, i18n } = useTranslation(['adminTagSafelist']); const theme = useTheme(); - const containerEL = useRef(); - const containerDialogEL = useRef(); - const [tagSafelist, setTagSafelist] = useState(null); - const [originalTagSafelist, setOriginalTagSafelist] = useState(null); - const [open, setOpen] = useState(false); - const { showSuccessMessage } = useMySnackbar(); const { apiCall } = useMyAPI(); const { user: currentUser } = useAppUser(); const { isDark: isDarkTheme } = useAppTheme(); + const { showSuccessMessage } = useMySnackbar(); + + const [tagSafelist, setTagSafelist] = useState(null); + const [originalTagSafelist, setOriginalTagSafelist] = useState(null); + const [open, setOpen] = useState(false); + + const containerEL = useRef(); + const containerDialogEL = useRef(); useEffectOnce(() => { if (currentUser.is_admin) { diff --git a/src/components/routes/admin/users.tsx b/src/components/routes/admin/users.tsx index 335d2e8d4..1ff3d69db 100644 --- a/src/components/routes/admin/users.tsx +++ b/src/components/routes/admin/users.tsx @@ -25,6 +25,8 @@ import { useEffectOnce } from 'commons/components/utils/hooks/useEffectOnce'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; +import { User } from 'components/models/base/user'; +import { SearchResult } from 'components/models/ui/search'; import Classification from 'components/visual/Classification'; import CustomChip from 'components/visual/CustomChip'; import SearchBar from 'components/visual/SearchBar/search-bar'; @@ -40,19 +42,6 @@ import { Navigate, useNavigate } from 'react-router'; import { useLocation } from 'react-router-dom'; const PAGE_SIZE = 25; -const DEFAULT_USER = { - avatar: null, - groups: ['USERS'], - is_active: true, - type: ['user'], - classification: null, - email: '', - name: '', - new_pass: '', - uname: '', - api_quota: 10, - submission_quota: 5 -}; const useStyles = makeStyles(theme => ({ searchresult: { @@ -78,41 +67,63 @@ const useStyles = makeStyles(theme => ({ } })); -type User = { +// type User = { +// avatar: string; +// email: string; +// groups: string[]; +// is_active: boolean; +// type: string[]; +// classification: string; +// name: string; +// new_pass: string; +// uname: string; +// api_quota: number; +// submission_quota: number; +// roles?: string[]; +// }; + +type UserData = Partial & { avatar: string; - email: string; - groups: string[]; - is_active: boolean; - type: string[]; - classification: string; - name: string; new_pass: string; - uname: string; - api_quota: number; - submission_quota: number; - roles?: string[]; +}; + +const DEFAULT_USER: UserData = { + avatar: null, + groups: ['USERS'], + is_active: true, + type: ['user'], + classification: null, + email: '', + name: '', + new_pass: '', + uname: '', + api_quota: 10, + submission_quota: 5 }; export default function Users() { const { t } = useTranslation(['adminUsers']); - const [pageSize] = useState(PAGE_SIZE); - const [searching, setSearching] = useState(false); - const [drawer, setDrawer] = useState(false); - const [buttonLoading, setButtonLoading] = useState(false); - const { user: currentUser, c12nDef, configuration } = useALContext(); - const [userResults, setUserResults] = useState(null); - const [newUser, setNewUser] = useState(DEFAULT_USER); + const theme = useTheme(); + const classes = useStyles(); const location = useLocation(); - const [query, setQuery] = useState(null); - const { showSuccessMessage } = useMySnackbar(); const navigate = useNavigate(); - const theme = useTheme(); const { apiCall } = useMyAPI(); - const classes = useStyles(); - const upMD = useMediaQuery(theme.breakpoints.up('md')); - const [suggestions, setSuggestions] = useState(DEFAULT_SUGGESTION); + const { showSuccessMessage } = useMySnackbar(); + const { user: currentUser, c12nDef, configuration } = useALContext(); + + const [userResults, setUserResults] = useState>(null); + const [newUser, setNewUser] = useState(DEFAULT_USER); + const [pageSize] = useState(PAGE_SIZE); + const [query, setQuery] = useState(null); + const [suggestions, setSuggestions] = useState(DEFAULT_SUGGESTION); + const [buttonLoading, setButtonLoading] = useState(false); + const [drawer, setDrawer] = useState(false); + const [searching, setSearching] = useState(false); + const filterValue = useRef(''); + const upMD = useMediaQuery(theme.breakpoints.up('md')); + const closeDrawer = () => { setDrawer(false); }; diff --git a/src/components/routes/alerts/alert-chip-list-detailed.tsx b/src/components/routes/alerts/alert-chip-list-detailed.tsx index cf9378fe8..f6f006a16 100644 --- a/src/components/routes/alerts/alert-chip-list-detailed.tsx +++ b/src/components/routes/alerts/alert-chip-list-detailed.tsx @@ -1,7 +1,7 @@ import { useTheme } from '@mui/material'; +import { DetailedItem } from 'components/models/base/alert'; import CustomChip from 'components/visual/CustomChip'; import React from 'react'; -import { DetailedItem } from './hooks/useAlerts'; type AlertListChipProps = { items: DetailedItem[]; diff --git a/src/components/routes/alerts/alert-details.tsx b/src/components/routes/alerts/alert-details.tsx index 4c3c2b408..93c890a53 100644 --- a/src/components/routes/alerts/alert-details.tsx +++ b/src/components/routes/alerts/alert-details.tsx @@ -23,8 +23,9 @@ import PageFullWidth from 'commons/components/pages/PageFullWidth'; import useClipboard from 'commons/components/utils/hooks/useClipboard'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; -import { AlertItem, DetailedItem, detailedItemCompare } from 'components/routes/alerts/hooks/useAlerts'; +import { Alert as AlertData, DetailedItem } from 'components/models/base/alert'; +import { CustomUser } from 'components/models/ui/user'; +import { detailedItemCompare } from 'components/routes/alerts/hooks/useAlerts'; import { ActionableChipList } from 'components/visual/ActionableChipList'; import { ActionableCustomChipProps } from 'components/visual/ActionableCustomChip'; import ActionableText from 'components/visual/ActionableText'; @@ -68,7 +69,7 @@ const useStyles = makeStyles(theme => ({ type AlertDetailsProps = { id?: string; - alert?: AlertItem; + alert?: AlertData; }; type AutoHideChipListProps = { @@ -136,7 +137,7 @@ const WrappedAlertDetails: React.FC = ({ id, alert }) => { const { apiCall } = useMyAPI(); const { c12nDef } = useALContext(); const { copy } = useClipboard(); - const [item, setItem] = useState(null); + const [item, setItem] = useState(null); const { id: paramId } = useParams<{ id: string }>(); const { configuration } = useALContext(); const { user: currentUser } = useAppUser(); diff --git a/src/components/routes/alerts/alert-list-item-actions.tsx b/src/components/routes/alerts/alert-list-item-actions.tsx index 07c09275d..7840ac5ba 100644 --- a/src/components/routes/alerts/alert-list-item-actions.tsx +++ b/src/components/routes/alerts/alert-list-item-actions.tsx @@ -12,7 +12,8 @@ import { Badge, SpeedDial, SpeedDialAction, SpeedDialIcon, Typography, useMediaQ import makeStyles from '@mui/styles/makeStyles'; import useAppUser from 'commons/components/app/hooks/useAppUser'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { Alert } from 'components/models/base/alert'; +import { CustomUser } from 'components/models/ui/user'; import ConfirmationDialog from 'components/visual/ConfirmationDialog'; import SearchQuery from 'components/visual/SearchBar/search-query'; import { getValueFromPath } from 'helpers/utils'; @@ -22,13 +23,12 @@ import { BiNetworkChart } from 'react-icons/bi'; import { Link } from 'react-router-dom'; import AlertEventsTable from './alert-events'; import { AlertDrawerState } from './alerts'; -import { AlertItem } from './hooks/useAlerts'; import usePromiseAPI from './hooks/usePromiseAPI'; export type PossibleVerdict = 'malicious' | 'non_malicious'; interface AlertListItemActionsProps { - item: AlertItem; + item: Alert; index: number; currentQuery: SearchQuery; setDrawer: (state: AlertDrawerState) => void; diff --git a/src/components/routes/alerts/alert-list-item.tsx b/src/components/routes/alerts/alert-list-item.tsx index 6b2e2c805..ba5b731d8 100644 --- a/src/components/routes/alerts/alert-list-item.tsx +++ b/src/components/routes/alerts/alert-list-item.tsx @@ -4,7 +4,8 @@ import PersonOutlineOutlinedIcon from '@mui/icons-material/PersonOutlineOutlined import VerifiedUserOutlinedIcon from '@mui/icons-material/VerifiedUserOutlined'; import { Grid, Tooltip, useTheme } from '@mui/material'; import useALContext from 'components/hooks/useALContext'; -import { AlertItem, detailedItemCompare } from 'components/routes/alerts/hooks/useAlerts'; +import { Alert } from 'components/models/base/alert'; +import { detailedItemCompare } from 'components/routes/alerts/hooks/useAlerts'; import { ChipList } from 'components/visual/ChipList'; import CustomChip from 'components/visual/CustomChip'; import Verdict from 'components/visual/Verdict'; @@ -20,7 +21,7 @@ import AlertPriority from './alert-priority'; import AlertStatus from './alert-status'; type AlertListItemProps = { - item: AlertItem; + item: Alert; }; const WrappedAlertListItem: React.FC = ({ item }) => { diff --git a/src/components/routes/alerts/alerts-filters-favorites.tsx b/src/components/routes/alerts/alerts-filters-favorites.tsx index 33969c6bd..aa97b7650 100644 --- a/src/components/routes/alerts/alerts-filters-favorites.tsx +++ b/src/components/routes/alerts/alerts-filters-favorites.tsx @@ -15,7 +15,7 @@ import makeStyles from '@mui/styles/makeStyles'; import useAppUser from 'commons/components/app/hooks/useAppUser'; import useALContext from 'components/hooks/useALContext'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import { ChipList } from 'components/visual/ChipList'; import Classification from 'components/visual/Classification'; import ConfirmationDialog from 'components/visual/ConfirmationDialog'; diff --git a/src/components/routes/alerts/alerts-workflow-actions.tsx b/src/components/routes/alerts/alerts-workflow-actions.tsx index f3ce7623f..c42cf2145 100644 --- a/src/components/routes/alerts/alerts-workflow-actions.tsx +++ b/src/components/routes/alerts/alerts-workflow-actions.tsx @@ -3,6 +3,7 @@ import Autocomplete from '@mui/material/Autocomplete'; import SearchQuery, { SearchFilter } from 'components/visual/SearchBar/search-query'; import React, { SyntheticEvent, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { AlertState } from './alerts'; import AlertsFiltersSelected from './alerts-filters-selected'; const POSSIBLE_STATUS = ['ASSESS', 'MALICIOUS', 'NON-MALICIOUS']; @@ -18,12 +19,12 @@ const DEFAULT_LABELS = [ 'PENDING' ]; -interface AlertsWorkflowActionsProps { +type AlertsWorkflowActionsProps = { searchQuery: SearchQuery; - alert: any; + alert: AlertState; labelFilters: SearchFilter[]; onApplyBtnClick: (status: string, selectedPriority: string, selectedLabels: string[]) => void; -} +}; const AlertsWorkflowActions: React.FC = ({ searchQuery, diff --git a/src/components/routes/alerts/alerts.tsx b/src/components/routes/alerts/alerts.tsx index d2aa7b31f..4baf4da61 100644 --- a/src/components/routes/alerts/alerts.tsx +++ b/src/components/routes/alerts/alerts.tsx @@ -29,7 +29,8 @@ import PageFullWidth from 'commons/components/pages/PageFullWidth'; import PageHeader from 'commons/components/pages/PageHeader'; import useDrawer from 'components/hooks/useDrawer'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { Alert, AlertUpdate } from 'components/models/base/alert'; +import { CustomUser } from 'components/models/ui/user'; import InformativeAlert from 'components/visual/InformativeAlert'; import SearchBar from 'components/visual/SearchBar/search-bar'; import SearchQuery, { SearchQueryFilters } from 'components/visual/SearchBar/search-query'; @@ -52,32 +53,34 @@ import AlertsFilters from './alerts-filters'; import AlertsFiltersFavorites from './alerts-filters-favorites'; import AlertsFiltersSelected from './alerts-filters-selected'; import AlertsWorkflowActions from './alerts-workflow-actions'; -import useAlerts, { AlertItem } from './hooks/useAlerts'; +import useAlerts from './hooks/useAlerts'; import useFavorites from './hooks/useFavorites'; import usePromiseAPI from './hooks/usePromiseAPI'; // Default size of a page to be used by the useAlert hook when fetching next load of data // when scrolling has hit threshold. -const PAGE_SIZE = 50; +const PAGE_SIZE = 50 as const; -export const LOCAL_STORAGE = 'alert.search'; +export const LOCAL_STORAGE = 'alert.search' as const; -export interface AlertDrawerState { +export type AlertState = { + index: number; + alert_id: string; + priority: string; + status: string; + labels: string[]; +}; + +export type AlertDrawerState = { open: boolean; type: 'filter' | 'favorites' | 'actions' | 'sort'; actionData?: { query: SearchQuery; - alert?: { - index: number; - alert_id: string; - priority: string; - status: string; - labels: string[]; - }; + alert?: AlertState; }; -} +}; -const ALERT_SIMPLELIST_ID = 'al.alerts.simplelist'; +const ALERT_SIMPLELIST_ID = 'al.alerts.simplelist' as const; // Just indicates whether there are any filters currently set.. const hasFilters = (filters: SearchQueryFilters): boolean => { @@ -230,7 +233,7 @@ const Alerts: React.FC = () => { }; const onVerdictComplete = useCallback( - (index: number, item: AlertItem, verdict: PossibleVerdict) => { + (index: number, item: Alert, verdict: PossibleVerdict) => { const changes = { verdict: { ...item.verdict } }; if (verdict === 'malicious') { if (changes.verdict.malicious.indexOf(currentUser.username) === -1) { @@ -254,7 +257,7 @@ const Alerts: React.FC = () => { ); const onTakeOwnershipComplete = useCallback( - (index: number, item: AlertItem) => { + (index: number, item: Alert) => { const changes = { owner: currentUser.username }; updateAlert(index, changes); window.dispatchEvent(new CustomEvent('alertUpdate', { detail: { id: item.id, changes } })); @@ -264,7 +267,7 @@ const Alerts: React.FC = () => { // Handler for when an item of the InfiniteList is selected const onItemSelected = useCallback( - (item: AlertItem, index: number) => { + (item: Alert, index: number) => { if (item !== null && item !== undefined) { if (isLGDown) { // Unfocus the simple list so the drawer does not try to refocus it when closing... @@ -339,7 +342,7 @@ const Alerts: React.FC = () => { status: selectedStatus || alert.status, priority: selectedPriority || alert.priority, label: [...Array.from(new Set([...alert.labels, ...selectedLabels]))] - }; + } as AlertUpdate; updateAlert(alert.index, changes); window.dispatchEvent(new CustomEvent('alertUpdate', { detail: { id: alert.alert_id, changes } })); } else { @@ -350,7 +353,7 @@ const Alerts: React.FC = () => { }; // Memoized callback to render one line-item of the list.... - const onRenderListRow = useCallback((item: AlertItem) => , []); + const onRenderListRow = useCallback((item: Alert) => , []); // const onDrawerClose = () => { @@ -358,13 +361,13 @@ const Alerts: React.FC = () => { }; // Handler for when the cursor on the list changes via keybaord event. - const onListCursorChanges = (item: AlertItem, index: number) => { + const onListCursorChanges = (item: Alert, index: number) => { onItemSelected(item, index); }; // Handler to render the action buttons of each list item. const onRenderListActions = useCallback( - (item: AlertItem, index: number) => + (item: Alert, index: number) => currentUser.roles.includes('submission_view') || currentUser.roles.includes('alert_manage') || item.group_count ? ( diff --git a/src/components/routes/alerts/hooks/useAlerts.ts b/src/components/routes/alerts/hooks/useAlerts.ts index 73db3e611..02010c0aa 100644 --- a/src/components/routes/alerts/hooks/useAlerts.ts +++ b/src/components/routes/alerts/hooks/useAlerts.ts @@ -1,8 +1,8 @@ -import { LineItem } from 'commons/addons/lists/item/ListItemBase'; import useAppUser from 'commons/components/app/hooks/useAppUser'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; -import { ALField, CustomUser } from 'components/hooks/useMyUser'; +import { Alert, AlertUpdate, DetailedItem } from 'components/models/base/alert'; +import { CustomUser, Field } from 'components/models/ui/user'; import SearchQuery, { SearchFilter, SearchFilterType } from 'components/visual/SearchBar/search-query'; import { safeFieldValue, verdictRank } from 'helpers/utils'; import { useCallback, useEffect, useReducer, useState } from 'react'; @@ -16,125 +16,19 @@ const DEFAULT_STATE = { alerts: [] }; -export interface Screenshots { - name: string; - description: string; - img: string; - thumb: string; -} - -export interface AlertFile { - md5: string; - name: string; - sha1: string; - sha256: string; - size: number; - type: string; - screenshots: Screenshots[]; -} - -export interface AlertUpdateItem { - label?: string[]; - owner?: string; - priority?: string; - status?: string; - verdict?: { - malicious: string[]; - non_malicious: string[]; - }; -} - -export interface DetailedItem { - subtype: string; - type: string; - value: string; - verdict: 'safe' | 'info' | 'suspicious' | 'malicious'; -} - -export interface WorkflowEvent { - entity_name: string; - entity_type: 'user' | 'workflow'; - entity_id: string; - labels?: string[]; - status?: string; - priority?: string; - ts: string; -} - -export interface AlertItem extends LineItem { - al: { - attrib: string[]; - av: string[]; - behavior: string[]; - detailed: { - attack_category: DetailedItem[]; - attack_pattern: DetailedItem[]; - attrib: DetailedItem[]; - av: DetailedItem[]; - behavior: DetailedItem[]; - domain: DetailedItem[]; - heuristic: DetailedItem[]; - ip: DetailedItem[]; - uri: DetailedItem[]; - yara: DetailedItem[]; - }; - domain: string[]; - domain_dynamic: string[]; - domain_static: string[]; - ip: string[]; - ip_dynamic: string[]; - ip_static: string[]; - uri: string[]; - uri_dynamic: string[]; - uri_static: string[]; - request_end_time: string[]; - score: number; - yara: string[]; - }; - alert_id: string; - attack: { - category: string[]; - pattern: string[]; - }; - extended_scan: string; - classification: string; - file: AlertFile; - filtered: boolean; - label: string[]; - group_count: number; - heuristic: { name: string[] }; - hint_owner: boolean; - metadata: { - [key: string]: any; - }[]; - owner: string; - priority: string; - reporting_ts: string; - sid: string; - status: string; - ts: string; - type: string; - verdict: { - malicious: string[]; - non_malicious: string[]; - }; - events: WorkflowEvent[]; - workflow_completed: boolean; -} - // The Custom Hook API. interface UsingAlerts { loading: boolean; - fields: ALField[]; + fields: Field[]; total: number; countedTotal: number; - alerts: AlertItem[]; + alerts: Alert[]; searchQuery: SearchQuery; labelFilters: SearchFilter[]; priorityFilters: SearchFilter[]; statusFilters: SearchFilter[]; valueFilters: SearchFilter[]; - updateAlert: (alertIndex: number, alertChanges: AlertUpdateItem) => void; + updateAlert: (alertIndex: number, alertChanges: AlertUpdate) => void; updateQuery: (query: SearchQuery) => void; onLoad: (onComplete?: (success: boolean) => void) => void; onLoadMore: (onComplete?: (success: boolean) => void) => void; @@ -144,7 +38,7 @@ interface AlertState { loading: boolean; total: number; countedTotal: number; - alerts: AlertItem[]; + alerts: Alert[]; } interface AlertMessage { @@ -152,10 +46,10 @@ interface AlertMessage { loading?: boolean; total?: number; countedTotal?: number; - alerts?: AlertItem[]; + alerts?: Alert[]; updateAlert?: { alertIndex: number; - alertChanges: AlertUpdateItem; + alertChanges: AlertUpdate; }; } @@ -209,7 +103,7 @@ export default function useAlerts(pageSize: number): UsingAlerts { const { user: currentUser } = useAppUser(); const { indexes: fieldIndexes } = useALContext(); const [searchQuery, setSearchQuery] = useState(null); - const [fields, setFields] = useState([]); + const [fields, setFields] = useState([]); const [statusFilters, setStatusFilters] = useState([]); const [priorityFilters, setPriorityFilters] = useState([]); const [labelFilters, setLabelFilters] = useState([]); @@ -232,7 +126,7 @@ export default function useAlerts(pageSize: number): UsingAlerts { }, [searchQuery]); // Hook API: update an alert in the list - const updateAlert = useCallback((alertIndex: number, alertChanges: AlertUpdateItem) => { + const updateAlert = useCallback((alertIndex: number, alertChanges: AlertUpdate) => { setState({ action: 'update', updateAlert: { alertIndex, alertChanges } }); }, []); diff --git a/src/components/routes/alerts/hooks/usePromiseAPI.tsx b/src/components/routes/alerts/hooks/usePromiseAPI.tsx index d075c05da..78452d8d8 100644 --- a/src/components/routes/alerts/hooks/usePromiseAPI.tsx +++ b/src/components/routes/alerts/hooks/usePromiseAPI.tsx @@ -1,12 +1,12 @@ /* eslint-disable react-hooks/exhaustive-deps */ import useMyAPI from 'components/hooks/useMyAPI'; +import { Alert } from 'components/models/base/alert'; import SearchQuery from 'components/visual/SearchBar/search-query'; import { useCallback } from 'react'; -import { AlertItem } from './useAlerts'; // Specification interface of this hook. export interface UsingPromiseAPI { - fetchAlert: (alertId: string) => Promise; + fetchAlert: (alertId: string) => Promise; onApplyWorkflowAction: ( query: SearchQuery, selectedStatus: string, @@ -23,8 +23,8 @@ export default function usePromiseAPI(): UsingPromiseAPI { const { apiCall } = useMyAPI(); // Hook API: fetch the alert for the specified alert_id. - const fetchAlert = async (alertId: string): Promise => - new Promise((resolve, reject) => { + const fetchAlert = async (alertId: string): Promise => + new Promise((resolve, reject) => { const url = `/api/v4/alert/${alertId}/`; apiCall({ url, diff --git a/src/components/routes/archive.tsx b/src/components/routes/archive.tsx index 162291b83..6dd8b9501 100644 --- a/src/components/routes/archive.tsx +++ b/src/components/routes/archive.tsx @@ -11,7 +11,9 @@ import useALContext from 'components/hooks/useALContext'; import useDrawer from 'components/hooks/useDrawer'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import ArchiveDetail, { FileInfo } from 'components/routes/archive/detail'; +import { FileIndexed } from 'components/models/base/file'; +import { FacetResult, FieldsResult, HistogramResult, SearchResult } from 'components/models/ui/search'; +import ArchiveDetail from 'components/routes/archive/detail'; import { ChipList } from 'components/visual/ChipList'; import Histogram from 'components/visual/Histogram'; import LineGraph from 'components/visual/LineGraph'; @@ -51,12 +53,12 @@ const useStyles = makeStyles(theme => ({ } })); -type FileResults = { - items: FileInfo[]; - offset: number; - rows: number; - total: number; -}; +// type FileResults = { +// items: FileInfo[]; +// offset: number; +// rows: number; +// total: number; +// }; const PAGE_SIZE = 25; @@ -111,10 +113,10 @@ export default function MalwareArchive() { const { showErrorMessage } = useMySnackbar(); const { closeGlobalDrawer, setGlobalDrawer, globalDrawerOpened } = useDrawer(); - const [fileResults, setFileResults] = useState(null); + const [fileResults, setFileResults] = useState>(null); const [query, setQuery] = useState(null); const [parsedQuery, setParsedQuery] = useState(null); - const [histogram, setHistogram] = useState(null); + const [histogram, setHistogram] = useState(null); const [types, setTypes] = useState>(null); const [labels, setLabels] = useState>(null); const [searching, setSearching] = useState(false); @@ -170,7 +172,7 @@ export default function MalwareArchive() { q.add('filters', TC_MAP[tc]); } - apiCall({ + apiCall>({ url: `/api/v4/search/file/?${q.toString()}`, onSuccess: api_data => setFileResults(api_data.api_response), onFailure: api_data => showErrorMessage(api_data.api_error_message), @@ -178,16 +180,16 @@ export default function MalwareArchive() { onFinalize: () => setSearching(false) }); - apiCall({ + apiCall({ url: `/api/v4/search/histogram/file/seen.last/?start=${START_MAP[tc]}&end=now&gap=${ GAP_MAP[tc] }&mincount=0&${q.toString(['rows', 'offset', 'sort', 'track_total_hits'])}`, onSuccess: api_data => setHistogram(api_data.api_response) }); - apiCall({ + apiCall({ url: `/api/v4/search/facet/file/labels/?${q.toString(['rows', 'offset', 'sort', 'track_total_hits'])}`, - onSuccess: ({ api_response }: { api_response: Record }) => + onSuccess: ({ api_response }) => setLabels( Object.fromEntries( Object.keys(api_response) @@ -197,9 +199,9 @@ export default function MalwareArchive() { ) }); - apiCall({ + apiCall({ url: `/api/v4/search/facet/file/type/?${q.toString(['rows', 'offset', 'sort', 'track_total_hits'])}`, - onSuccess: ({ api_response }: { api_response: Record }) => + onSuccess: ({ api_response }) => setTypes( Object.fromEntries( Object.keys(api_response) @@ -257,7 +259,7 @@ export default function MalwareArchive() { }, [location.hash]); useEffect(() => { - apiCall({ + apiCall({ url: '/api/v4/search/fields/file/', onSuccess: api_data => { setSuggestions([ diff --git a/src/components/routes/archive/detail.tsx b/src/components/routes/archive/detail.tsx index b7661faf8..6674b97bd 100644 --- a/src/components/routes/archive/detail.tsx +++ b/src/components/routes/archive/detail.tsx @@ -4,18 +4,16 @@ import PageFullSize from 'commons/components/pages/PageFullSize'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; +import { Section } from 'components/models/base/result'; +import type { File } from 'components/models/ui/file'; import { ArchiveBanner, ArchivedTagSection, CommentSection, LabelSection, - Signature, - SimilarSection, - Tag + SimilarSection } from 'components/visual/ArchiveDetail'; import Classification from 'components/visual/Classification'; -import { Comments } from 'components/visual/CommentCard'; -import { Error } from 'components/visual/ErrorCard'; import AttackSection from 'components/visual/FileDetail/attacks'; import ChildrenSection from 'components/visual/FileDetail/childrens'; import Detection from 'components/visual/FileDetail/detection'; @@ -30,7 +28,7 @@ import URIIdentificationSection from 'components/visual/FileDetail/uriIdent'; import { ASCIISection, HexSection, ImageSection, StringsSection } from 'components/visual/FileViewer'; import CodeSection from 'components/visual/FileViewer/code_summary'; import InformativeAlert from 'components/visual/InformativeAlert'; -import { AlternateResult, emptyResult, Result } from 'components/visual/ResultCard'; +import { emptyResult } from 'components/visual/ResultCard'; import { TabContainer } from 'components/visual/TabContainer'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -40,80 +38,6 @@ import ForbiddenPage from '../403'; import NotFoundPage from '../404'; import AISummarySection from '../submission/detail/ai_summary'; -export type FileInfo = { - archive_ts: string; - ascii: string; - classification: string; - comments: Comments; - entropy: number; - expiry_ts: string | null; - from_archive: boolean; - hex: string; - is_section_image: boolean; - is_supplementary: boolean; - labels: string[]; - label_categories?: { - attribution: string[]; - technique: string[]; - info: string[]; - }; - magic: string; - md5: string; - mime: string; - seen: { - count: number; - first: string; - last: string; - }; - sha1: string; - sha256: string; - size: number; - ssdeep: string; - tlsh: string; - type: string; - uri_info?: { - fragment?: string; - hostname: string; - netloc: string; - params?: string; - password?: string; - path?: string; - port: number; - query?: string; - scheme: string; - uri: string; - username?: string; - }; -}; - -export type File = { - alternates: { - [serviceName: string]: AlternateResult[]; - }; - attack_matrix: { - [category: string]: string[][]; - }; - childrens: { - name: string; - sha256: string; - }[]; - emptys: Result[]; - errors: Error[]; - file_info: FileInfo; - heuristics: { - [category: string]: string[][]; - }; - metadata: { - [level: string]: { - [key: string]: any; - }; - }; - parents: string[]; - results: Result[]; - signatures: Signature[]; - tags: Record; -}; - type Params = { id?: string; tab?: string; @@ -136,9 +60,9 @@ const WrappedArchiveDetail: React.FC = ({ sha256: propSha256, force = fal const { showErrorMessage } = useMySnackbar(); const { user: currentUser, c12nDef, configuration } = useALContext(); - const [file, setFile] = useState(null); - const [promotedSections, setPromotedSections] = useState([]); - const [codeAllowed, setCodeAllowed] = useState(false); + const [file, setFile] = useState(null); + const [promotedSections, setPromotedSections] = useState([]); + const [codeAllowed, setCodeAllowed] = useState(false); const inDrawer = useMemo(() => (propSha256 ? true : paramSha256 ? false : null), [paramSha256, propSha256]); const sha256 = useMemo(() => paramSha256 || propSha256, [paramSha256, propSha256]); @@ -156,7 +80,7 @@ const WrappedArchiveDetail: React.FC = ({ sha256: propSha256, force = fal useEffect(() => { if (!sha256) return; - apiCall({ + apiCall({ url: `/api/v4/file/result/${sha256}/?archive_only=true`, onEnter: () => setFile(null), onSuccess: api_data => { @@ -235,7 +159,7 @@ const WrappedArchiveDetail: React.FC = ({ sha256: propSha256, force = fal tabs={{ details: { label: t('details'), - content: ( + inner: ( <> {file?.file_info?.type.startsWith('uri/') ? ( = ({ sha256: propSha256, force = fal }, detection: { label: t('detection'), - content: + inner: file && file.results.length === 0 && Object.keys(file.heuristics).length === 0 && @@ -296,7 +220,7 @@ const WrappedArchiveDetail: React.FC = ({ sha256: propSha256, force = fal }, tags: { label: t('tags'), - content: ( + inner: ( = ({ sha256: propSha256, force = fal }, relations: { label: t('relations'), - content: ( + inner: ( <> = ({ sha256: propSha256, force = fal }, ascii: { label: t('ascii'), - content: ( - - ) + inner: }, code: { label: t('code'), - content: , + inner: , disabled: isMdUp || !codeAllowed }, - strings: { label: t('strings'), content: }, - hex: { label: t('hex'), content: }, + strings: { label: t('strings'), inner: }, + hex: { label: t('hex'), inner: }, image: { label: t('image'), disabled: !file?.file_info?.is_section_image, - content: + inner: }, community: { label: t('community'), - content: ( + inner: ( <> [] = SCOPES.filter(s => s !== 'c'); export default function AppRegistration() { const location = useLocation(); @@ -30,7 +31,7 @@ export default function AppRegistration() { let roles = []; if (rolesP) { - roles = rolesP.split(',').filter(r => configuration.user.roles.includes(r)); + roles = (rolesP.split(',') as Role[]).filter(r => configuration.user.roles.includes(r)); } return ( diff --git a/src/components/routes/dashboard.tsx b/src/components/routes/dashboard.tsx index 4e5ecc144..8113125d9 100644 --- a/src/components/routes/dashboard.tsx +++ b/src/components/routes/dashboard.tsx @@ -9,7 +9,7 @@ import PageFullScreen from 'commons/components/pages/PageFullScreen'; import { useEffectOnce } from 'commons/components/utils/hooks/useEffectOnce'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import ArcGauge from 'components/visual/ArcGauge'; import CustomChip from 'components/visual/CustomChip'; import React, { useEffect, useReducer, useState } from 'react'; diff --git a/src/components/routes/file/detail.tsx b/src/components/routes/file/detail.tsx index 99c307b62..f3a6541f1 100644 --- a/src/components/routes/file/detail.tsx +++ b/src/components/routes/file/detail.tsx @@ -1,6 +1,6 @@ import useAppUser from 'commons/components/app/hooks/useAppUser'; import PageCenter from 'commons/components/pages/PageCenter'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import FileDetail from 'components/visual/FileDetail'; import { useParams } from 'react-router-dom'; import ForbiddenPage from '../403'; diff --git a/src/components/routes/file/viewer.tsx b/src/components/routes/file/viewer.tsx index 99ee1fb1e..8f0fc862a 100644 --- a/src/components/routes/file/viewer.tsx +++ b/src/components/routes/file/viewer.tsx @@ -8,7 +8,7 @@ import useAppUser from 'commons/components/app/hooks/useAppUser'; import PageFullSize from 'commons/components/pages/PageFullSize'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import ForbiddenPage from 'components/routes/403'; import FileDownloader from 'components/visual/FileDownloader'; import { ASCIISection, HexSection, ImageSection, StringsSection } from 'components/visual/FileViewer'; @@ -149,7 +149,7 @@ const WrappedFileViewer: React.FC = () => { tabs={{ ascii: { label: t('ascii'), - content: ( + inner: (
@@ -157,7 +157,7 @@ const WrappedFileViewer: React.FC = () => { }, code: { label: t('code'), - content: ( + inner: (
@@ -166,7 +166,7 @@ const WrappedFileViewer: React.FC = () => { }, strings: { label: t('strings'), - content: ( + inner: (
@@ -174,7 +174,7 @@ const WrappedFileViewer: React.FC = () => { }, hex: { label: t('hex'), - content: ( + inner: (
@@ -183,7 +183,7 @@ const WrappedFileViewer: React.FC = () => { image: { label: t('image'), disabled: !imageAllowed, - content: ( + inner: (
diff --git a/src/components/routes/help.tsx b/src/components/routes/help.tsx index 86f826379..05da29e72 100644 --- a/src/components/routes/help.tsx +++ b/src/components/routes/help.tsx @@ -1,8 +1,8 @@ import useAppConfigs from 'commons/components/app/hooks/useAppConfigs'; import useAppUser from 'commons/components/app/hooks/useAppUser'; import PageCenter from 'commons/components/pages/PageCenter'; -import { CustomUser } from 'components/hooks/useMyUser'; import LinkGrid from 'components/layout/linkgrid'; +import { CustomUser } from 'components/models/ui/user'; export default function Help() { const { preferences: layout } = useAppConfigs(); diff --git a/src/components/routes/manage/badlist.tsx b/src/components/routes/manage/badlist.tsx index 993fed394..409930495 100644 --- a/src/components/routes/manage/badlist.tsx +++ b/src/components/routes/manage/badlist.tsx @@ -11,7 +11,7 @@ import PageHeader from 'commons/components/pages/PageHeader'; import useALContext from 'components/hooks/useALContext'; import useDrawer from 'components/hooks/useDrawer'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import SearchBar from 'components/visual/SearchBar/search-bar'; import { DEFAULT_SUGGESTION } from 'components/visual/SearchBar/search-textfield'; import SimpleSearchQuery from 'components/visual/SearchBar/simple-search-query'; diff --git a/src/components/routes/manage/heuristic_detail.tsx b/src/components/routes/manage/heuristic_detail.tsx index 0ebd8130a..084f988a0 100644 --- a/src/components/routes/manage/heuristic_detail.tsx +++ b/src/components/routes/manage/heuristic_detail.tsx @@ -5,7 +5,7 @@ import useAppUser from 'commons/components/app/hooks/useAppUser'; import PageCenter from 'commons/components/pages/PageCenter'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import Classification from 'components/visual/Classification'; import Histogram from 'components/visual/Histogram'; import ResultsTable from 'components/visual/SearchResult/results'; diff --git a/src/components/routes/manage/heuristics.tsx b/src/components/routes/manage/heuristics.tsx index e43338682..a6d94e01e 100644 --- a/src/components/routes/manage/heuristics.tsx +++ b/src/components/routes/manage/heuristics.tsx @@ -7,7 +7,7 @@ import PageHeader from 'commons/components/pages/PageHeader'; import useALContext from 'components/hooks/useALContext'; import useDrawer from 'components/hooks/useDrawer'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import SearchBar from 'components/visual/SearchBar/search-bar'; import { DEFAULT_SUGGESTION } from 'components/visual/SearchBar/search-textfield'; import SimpleSearchQuery from 'components/visual/SearchBar/simple-search-query'; diff --git a/src/components/routes/manage/safelist.tsx b/src/components/routes/manage/safelist.tsx index 2996b3fab..29db7cf0b 100644 --- a/src/components/routes/manage/safelist.tsx +++ b/src/components/routes/manage/safelist.tsx @@ -11,7 +11,7 @@ import PageHeader from 'commons/components/pages/PageHeader'; import useALContext from 'components/hooks/useALContext'; import useDrawer from 'components/hooks/useDrawer'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import SearchBar from 'components/visual/SearchBar/search-bar'; import { DEFAULT_SUGGESTION } from 'components/visual/SearchBar/search-textfield'; import SimpleSearchQuery from 'components/visual/SearchBar/simple-search-query'; diff --git a/src/components/routes/manage/signature_sources.tsx b/src/components/routes/manage/signature_sources.tsx index 03303426e..b37261c1f 100644 --- a/src/components/routes/manage/signature_sources.tsx +++ b/src/components/routes/manage/signature_sources.tsx @@ -28,7 +28,8 @@ import useALContext from 'components/hooks/useALContext'; import useDrawer from 'components/hooks/useDrawer'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { UpdateSource } from 'components/models/base/service'; +import { CustomUser } from 'components/models/ui/user'; import Classification from 'components/visual/Classification'; import ConfirmationDialog from 'components/visual/ConfirmationDialog'; import { RouterPrompt } from 'components/visual/RouterPrompt'; @@ -38,7 +39,6 @@ import { DiGitBranch } from 'react-icons/di'; import Moment from 'react-moment'; import { Link } from 'react-router-dom'; import ForbiddenPage from '../403'; -import { Source } from '../admin/service_detail'; import { SourceDetail } from './signature_sources_details'; const useStyles = makeStyles(theme => ({ @@ -115,7 +115,7 @@ const useStyles = makeStyles(theme => ({ } })); -const DEFAULT_SOURCE: Source = { +const DEFAULT_SOURCE: UpdateSource = { ca_cert: '', default_classification: '', headers: [], @@ -137,8 +137,8 @@ const DEFAULT_SOURCE: Source = { sync: false }; -const isSourceUpdating = (source: Source) => source.status.state === 'UPDATING'; -const queueSourceUpdate = (source: Source) => ({ +const isSourceUpdating = (source: UpdateSource) => source.status.state === 'UPDATING'; +const queueSourceUpdate = (source: UpdateSource) => ({ ...source, status: { ...source.status, state: 'UPDATING', message: 'Queued for update..' } }); diff --git a/src/components/routes/manage/signature_sources_details.tsx b/src/components/routes/manage/signature_sources_details.tsx index 386999d58..131d95a9c 100644 --- a/src/components/routes/manage/signature_sources_details.tsx +++ b/src/components/routes/manage/signature_sources_details.tsx @@ -3,11 +3,11 @@ import RemoveCircleOutlineOutlinedIcon from '@mui/icons-material/RemoveCircleOut import { Checkbox, FormControlLabel, Grid, IconButton, TextField, Typography, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import useALContext from 'components/hooks/useALContext'; +import { EnvironmentVariable } from 'components/models/base/service'; import Classification from 'components/visual/Classification'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import Moment from 'react-moment'; -import { Environment } from '../admin/service_detail'; import ResetButton from '../admin/service_detail/reset_button'; const useStyles = makeStyles(theme => ({ @@ -23,7 +23,7 @@ const useStyles = makeStyles(theme => ({ } })); -const DEFAULT_HEADER: Environment = { +const DEFAULT_HEADER: EnvironmentVariable = { name: '', value: '' }; @@ -39,7 +39,7 @@ const WrappedSourceDetail = ({ const { t, i18n } = useTranslation(['manageSignatureSources']); const theme = useTheme(); const { c12nDef } = useALContext(); - const [tempHeader, setTempHeader] = useState({ ...DEFAULT_HEADER }); + const [tempHeader, setTempHeader] = useState({ ...DEFAULT_HEADER }); const classes = useStyles(); const handleURIChange = event => { diff --git a/src/components/routes/manage/signatures.tsx b/src/components/routes/manage/signatures.tsx index 9561c483a..afb9065f9 100644 --- a/src/components/routes/manage/signatures.tsx +++ b/src/components/routes/manage/signatures.tsx @@ -10,7 +10,7 @@ import PageHeader from 'commons/components/pages/PageHeader'; import useALContext from 'components/hooks/useALContext'; import useDrawer from 'components/hooks/useDrawer'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import FileDownloader from 'components/visual/FileDownloader'; import SearchBar from 'components/visual/SearchBar/search-bar'; import { DEFAULT_SUGGESTION } from 'components/visual/SearchBar/search-textfield'; diff --git a/src/components/routes/manage/workflow_detail.tsx b/src/components/routes/manage/workflow_detail.tsx index 688b54400..3fdf05594 100644 --- a/src/components/routes/manage/workflow_detail.tsx +++ b/src/components/routes/manage/workflow_detail.tsx @@ -29,7 +29,7 @@ import PageCenter from 'commons/components/pages/PageCenter'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import Classification from 'components/visual/Classification'; import ConfirmationDialog from 'components/visual/ConfirmationDialog'; import Histogram from 'components/visual/Histogram'; diff --git a/src/components/routes/manage/workflows.tsx b/src/components/routes/manage/workflows.tsx index 70c5700e6..2c06824d3 100644 --- a/src/components/routes/manage/workflows.tsx +++ b/src/components/routes/manage/workflows.tsx @@ -10,7 +10,7 @@ import PageHeader from 'commons/components/pages/PageHeader'; import useALContext from 'components/hooks/useALContext'; import useDrawer from 'components/hooks/useDrawer'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import SearchBar from 'components/visual/SearchBar/search-bar'; import { DEFAULT_SUGGESTION } from 'components/visual/SearchBar/search-textfield'; import SimpleSearchQuery from 'components/visual/SearchBar/simple-search-query'; diff --git a/src/components/routes/retrohunt/create.tsx b/src/components/routes/retrohunt/create.tsx index f8aaa69c9..b353569c2 100644 --- a/src/components/routes/retrohunt/create.tsx +++ b/src/components/routes/retrohunt/create.tsx @@ -16,7 +16,7 @@ import useALContext from 'components/hooks/useALContext'; import useDrawer from 'components/hooks/useDrawer'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import ForbiddenPage from 'components/routes/403'; import { RetrohuntResult } from 'components/routes/retrohunt'; import Classification from 'components/visual/Classification'; diff --git a/src/components/routes/retrohunt/detail.tsx b/src/components/routes/retrohunt/detail.tsx index 1c4c34678..389afe3ba 100644 --- a/src/components/routes/retrohunt/detail.tsx +++ b/src/components/routes/retrohunt/detail.tsx @@ -22,10 +22,13 @@ import PageFullSize from 'commons/components/pages/PageFullSize'; import useALContext from 'components/hooks/useALContext'; import useDrawer from 'components/hooks/useDrawer'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { FileIndexed } from 'components/models/base/file'; +import { Retrohunt } from 'components/models/base/retrohunt'; +import { SearchResult } from 'components/models/ui/search'; +import { CustomUser } from 'components/models/ui/user'; import ForbiddenPage from 'components/routes/403'; import NotFoundPage from 'components/routes/404'; -import { RetrohuntPhase, RetrohuntResult } from 'components/routes/retrohunt'; +import { RetrohuntPhase } from 'components/routes/retrohunt'; import RetrohuntErrors from 'components/routes/retrohunt/errors'; import { ChipList } from 'components/visual/ChipList'; import Classification from 'components/visual/Classification'; @@ -46,7 +49,6 @@ import MonacoEditor from 'components/visual/MonacoEditor'; import SearchBar from 'components/visual/SearchBar/search-bar'; import { DEFAULT_SUGGESTION } from 'components/visual/SearchBar/search-textfield'; import SimpleSearchQuery from 'components/visual/SearchBar/simple-search-query'; -import { FileResult } from 'components/visual/SearchResult/files'; import SearchResultCount from 'components/visual/SearchResultCount'; import SteppedProgress from 'components/visual/SteppedProgress'; import { safeFieldValue } from 'helpers/utils'; @@ -116,14 +118,7 @@ const useStyles = makeStyles(theme => ({ } })); -type RetrohuntHitResult = { - items: FileResult[]; - offset: number; - rows: number; - total: number; -}; - -type ParamProps = { +type Params = { code: string; }; @@ -161,11 +156,11 @@ function WrappedRetrohuntDetail({ code: propCode = null, isDrawer = false }: Pro const { indexes } = useALContext(); const { c12nDef, configuration } = useALContext(); - const { code: paramCode } = useParams(); + const { code: paramCode } = useParams(); const { user: currentUser } = useAppUser(); - const [retrohunt, setRetrohunt] = useState(null); - const [hitResults, setHitResults] = useState(null); + const [retrohunt, setRetrohunt] = useState(null); + const [hitResults, setHitResults] = useState>(null); const [isErrorOpen, setIsErrorOpen] = useState(false); const [typeDataSet, setTypeDataSet] = useState<{ [k: string]: number }>(null); const [isReloading, setIsReloading] = useState(true); @@ -174,7 +169,7 @@ function WrappedRetrohuntDetail({ code: propCode = null, isDrawer = false }: Pro const filterValue = useRef(''); const timer = useRef(false); - const DEFAULT_RETROHUNT = useMemo( + const DEFAULT_RETROHUNT = useMemo>( () => ({ archive_only: false, classification: c12nDef.UNRESTRICTED, @@ -322,7 +317,7 @@ function WrappedRetrohuntDetail({ code: propCode = null, isDrawer = false }: Pro ); const handleHitRowClick = useCallback( - (file: FileResult) => { + (file: FileIndexed) => { if (isDrawer) navigate(`/file/detail/${file.sha256}${location.hash}`); else navigate(`${location.pathname}${location.search}#${file.sha256}`); }, diff --git a/src/components/routes/retrohunt/errors.tsx b/src/components/routes/retrohunt/errors.tsx index a25d49c79..621cef62e 100644 --- a/src/components/routes/retrohunt/errors.tsx +++ b/src/components/routes/retrohunt/errors.tsx @@ -17,8 +17,8 @@ import makeStyles from '@mui/styles/makeStyles'; import useAppUser from 'commons/components/app/hooks/useAppUser'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; -import { RetrohuntResult } from 'components/routes/retrohunt'; +import { Retrohunt } from 'components/models/base/retrohunt'; +import { CustomUser } from 'components/models/ui/user'; import { DivTable, DivTableBody, @@ -62,7 +62,7 @@ type RetrohuntErrorResult = { }; type Prop = { - retrohunt?: RetrohuntResult; + retrohunt?: Retrohunt; open?: boolean; onClose?: () => void; }; diff --git a/src/components/routes/search.tsx b/src/components/routes/search.tsx index 5a2c4240d..da3dd8589 100644 --- a/src/components/routes/search.tsx +++ b/src/components/routes/search.tsx @@ -9,6 +9,15 @@ import PageHeader from 'commons/components/pages/PageHeader'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; +import type { AlertIndexed } from 'components/models/base/alert'; +import type { FileIndexed } from 'components/models/base/file'; +import type { ResultIndexed } from 'components/models/base/result'; +import type { RetrohuntIndexed } from 'components/models/base/retrohunt'; +import type { SignatureIndexed } from 'components/models/base/signature'; +import type { SubmissionIndexed } from 'components/models/base/submission'; +import { Role } from 'components/models/base/user'; +import type { SearchResult } from 'components/models/ui/search'; +import { Indexes } from 'components/models/ui/user'; import Empty from 'components/visual/Empty'; import SearchBar from 'components/visual/SearchBar/search-bar'; import { DEFAULT_SUGGESTION } from 'components/visual/SearchBar/search-textfield'; @@ -22,7 +31,7 @@ import SignaturesTable from 'components/visual/SearchResult/signatures'; import SubmissionsTable from 'components/visual/SearchResult/submissions'; import SearchResultCount from 'components/visual/SearchResultCount'; import { searchResultsDisplay } from 'helpers/utils'; -import { useEffect, useRef, useState } from 'react'; +import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router'; import { Link, useLocation, useParams } from 'react-router-dom'; @@ -59,26 +68,21 @@ const useStyles = makeStyles((theme: Theme) => }) ); -type SearchProps = { +type SearchIndexes = Pick; + +type Props = { index?: string | null; }; -type ParamProps = { +type Params = { id: string; }; -type SearchResults = { - items: any[]; - offset: number; - rows: number; - total: number; -}; - -function Search({ index }: SearchProps) { - const { id } = useParams(); +function Search({ index }: Props) { + const { id } = useParams(); const { t } = useTranslation(['search']); - const [pageSize] = useState(PAGE_SIZE); - const [searching, setSearching] = useState(false); + const [pageSize] = useState(PAGE_SIZE); + const [searching, setSearching] = useState(false); const { indexes, user: currentUser, configuration } = useALContext(); const location = useLocation(); const navigate = useNavigate(); @@ -92,17 +96,17 @@ function Search({ index }: SearchProps) { const downSM = useMediaQuery(theme.breakpoints.down('md')); // Result lists - const [submissionResults, setSubmissionResults] = useState(null); - const [fileResults, setFileResults] = useState(null); - const [resultResults, setResultResults] = useState(null); - const [signatureResults, setSignatureResults] = useState(null); - const [alertResults, setAlertResults] = useState(null); - const [retrohuntResults, setRetrohuntResults] = useState(null); + const [submissionResults, setSubmissionResults] = useState>(null); + const [fileResults, setFileResults] = useState>(null); + const [resultResults, setResultResults] = useState>(null); + const [signatureResults, setSignatureResults] = useState>(null); + const [alertResults, setAlertResults] = useState>(null); + const [retrohuntResults, setRetrohuntResults] = useState>(null); // Current index const currentIndex = index || id; - const stateMap = { + const stateMap: Record>>> = { submission: setSubmissionResults, file: setFileResults, result: setResultResults, @@ -111,7 +115,7 @@ function Search({ index }: SearchProps) { retrohunt: setRetrohuntResults }; - const resMap = { + const resMap: Record> = { submission: submissionResults, file: fileResults, result: resultResults, @@ -120,7 +124,7 @@ function Search({ index }: SearchProps) { retrohunt: retrohuntResults }; - const permissionMap = { + const permissionMap: Record = { submission: 'submission_view', file: 'submission_view', result: 'submission_view', diff --git a/src/components/routes/settings.tsx b/src/components/routes/settings.tsx index b88687216..e6f1547d3 100644 --- a/src/components/routes/settings.tsx +++ b/src/components/routes/settings.tsx @@ -32,6 +32,7 @@ import useMySnackbar from 'components/hooks/useMySnackbar'; import ExternalSources from 'components/layout/externalSources'; import ServiceSpec from 'components/layout/serviceSpec'; import ServiceTree from 'components/layout/serviceTree'; +import { UserSettings } from 'components/models/base/user_settings'; import Classification from 'components/visual/Classification'; import { RouterPrompt } from 'components/visual/RouterPrompt'; import React, { memo, useState } from 'react'; @@ -67,6 +68,10 @@ const useStyles = makeStyles(theme => ({ } })); +const DRAWER_TYPES = ['ttl', 'view', 'encoding', 'score'] as const; + +type DrawerType = (typeof DRAWER_TYPES)[number]; + function Skel() { return (
@@ -99,23 +104,24 @@ const ClickRow = ({ children, enabled, onClick, chevron = false, ...other }) => function Settings() { const { t } = useTranslation(['settings']); const theme = useTheme(); - const [drawerType, setDrawerType] = useState(null); - const [drawerOpen, setDrawerOpen] = useState(false); - const [settings, setSettings] = useState(null); - const [modified, setModified] = useState(false); - const [editable, setEditable] = useState(false); - const [buttonLoading, setButtonLoading] = useState(false); + const classes = useStyles(); + const { apiCall } = useMyAPI(); const { user: currentUser, c12nDef, configuration } = useALContext(); const { showErrorMessage, showSuccessMessage } = useMySnackbar(); + + const [settings, setSettings] = useState(null); + const [drawerType, setDrawerType] = useState(null); + const [drawerOpen, setDrawerOpen] = useState(false); + const [modified, setModified] = useState(false); + const [editable, setEditable] = useState(false); + const [buttonLoading, setButtonLoading] = useState(false); + const sp1 = theme.spacing(1); const sp2 = theme.spacing(2); const sp4 = theme.spacing(4); const sp6 = theme.spacing(6); - const isXS = useMediaQuery(theme.breakpoints.only('xs')); - - const { apiCall } = useMyAPI(); - const classes = useStyles(); + const isXS = useMediaQuery(theme.breakpoints.only('xs')); const setParam = (service_idx, param_idx, p_value) => { if (settings) { @@ -244,7 +250,7 @@ function Settings() { } } - function toggleDrawer(type) { + function toggleDrawer(type: DrawerType) { if (settings) { setDrawerType(type); setDrawerOpen(true); @@ -256,7 +262,7 @@ function Settings() { setEditable(currentUser.is_admin || currentUser.roles.includes('self_manage')); // Load user on start - apiCall({ + apiCall({ url: `/api/v4/user/settings/${currentUser.username}/`, onSuccess: api_data => { setSettings(api_data.api_response); diff --git a/src/components/routes/statistics/heuristics.tsx b/src/components/routes/statistics/heuristics.tsx index da107295c..56df90e4b 100644 --- a/src/components/routes/statistics/heuristics.tsx +++ b/src/components/routes/statistics/heuristics.tsx @@ -9,6 +9,7 @@ import { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import HeuristicDetail from '../manage/heuristic_detail'; +// TODO: what is this and is this used? export default function StatisticsSignatures() { const { t } = useTranslation(['statisticsHeuristics']); const { apiCall } = useMyAPI(); diff --git a/src/components/routes/statistics/signatures.tsx b/src/components/routes/statistics/signatures.tsx index 7292a4c00..e559c05a2 100644 --- a/src/components/routes/statistics/signatures.tsx +++ b/src/components/routes/statistics/signatures.tsx @@ -9,6 +9,7 @@ import { useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import SignatureDetail from '../manage/signature_detail'; +// TODO: what is this and is this used? export default function StatisticsSignatures() { const { t } = useTranslation(['statisticsSignatures']); const { apiCall } = useMyAPI(); diff --git a/src/components/routes/submission/detail.tsx b/src/components/routes/submission/detail.tsx index 970d1b0df..2323472b2 100644 --- a/src/components/routes/submission/detail.tsx +++ b/src/components/routes/submission/detail.tsx @@ -36,6 +36,11 @@ import useDrawer from 'components/hooks/useDrawer'; import useHighlighter from 'components/hooks/useHighlighter'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; +import { ParsedErrors } from 'components/models/base/error'; +import { ParsedSubmission, Submission } from 'components/models/base/submission'; +import { Configuration } from 'components/models/ui/help'; +import { LiveStatus, OutstandingServices, WatchQueue } from 'components/models/ui/live'; +import { SubmissionSummary, SubmissionTags, SubmissionTree } from 'components/models/ui/submission'; import Classification from 'components/visual/Classification'; import ConfirmationDialog from 'components/visual/ConfirmationDialog'; import FileDetail from 'components/visual/FileDetail'; @@ -48,7 +53,7 @@ import React, { useCallback, useEffect, useReducer, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useLocation, useNavigate } from 'react-router'; import { Link, useParams } from 'react-router-dom'; -import io from 'socket.io-client'; +import io, { Socket } from 'socket.io-client'; import ForbiddenPage from '../403'; import HeuristicDetail from '../manage/heuristic_detail'; import AISummarySection from './detail/ai_summary'; @@ -59,9 +64,9 @@ import InfoSection from './detail/info'; import MetaSection from './detail/meta'; import TagSection from './detail/tags'; -const NAMESPACE = '/live_submission'; -const MESSAGE_TIMEOUT = 5000; -const OUTSTANDING_TRIGGER_COUNT = 4; +const NAMESPACE = '/live_submission' as const; +const MESSAGE_TIMEOUT = 5000 as const; +const OUTSTANDING_TRIGGER_COUNT = 4 as const; type ParamProps = { id: string; @@ -95,44 +100,46 @@ const incrementReducer = (old: number, increment: number) => { function WrappedSubmissionDetail() { const { t } = useTranslation(['submissionDetail']); - const { id, fid } = useParams(); const theme = useTheme(); - const [submission, setSubmission] = useState(null); - const [summary, setSummary] = useState(null); - const [tree, setTree] = useState(null); - const [filtered, setFiltered] = useState(false); - const [partial, setPartial] = useState(false); - const [watchQueue, setWatchQueue] = useState(null); - const [liveResultKeys, setLiveResultKeys] = useReducer(messageReducer, []); - const [liveErrorKeys, setLiveErrorKeys] = useReducer(messageReducer, []); - const [processedKeys, setProcessedKeys] = useReducer(messageReducer, []); - const [liveResults, setLiveResults] = useReducer(resultReducer, null); - const [configuration, setConfiguration] = useState(null); - const [liveErrors, setLiveErrors] = useState(null); - const [liveTagMap, setLiveTagMap] = useState(null); - const [outstanding, setOutstanding] = useState(null); - const [loadTrigger, incrementLoadTrigger] = useReducer(incrementReducer, 0); - const [liveStatus, setLiveStatus] = useState<'queued' | 'processing' | 'rescheduled'>('queued'); - const [socket, setSocket] = useState(null); - const [loadInterval, setLoadInterval] = useState(null); - const [lastSuccessfulTrigger, setLastSuccessfulTrigger] = useState(0); - const [deleteDialog, setDeleteDialog] = useState(false); - const [waitingDialog, setWaitingDialog] = useState(false); - const [resubmitAnchor, setResubmitAnchor] = useState(null); - const { apiCall } = useMyAPI(); - const sp4 = theme.spacing(4); - const { showSuccessMessage } = useMySnackbar(); const location = useLocation(); const navigate = useNavigate(); + const { apiCall } = useMyAPI(); + const { showSuccessMessage } = useMySnackbar(); const { user: currentUser, c12nDef, configuration: systemConfig } = useALContext(); const { setHighlightMap } = useHighlighter(); const { setGlobalDrawer, globalDrawerOpened } = useDrawer(); + const { id, fid } = useParams(); + + const [submission, setSubmission] = useState(null); + const [summary, setSummary] = useState(null); + const [tree, setTree] = useState(null); + const [filtered, setFiltered] = useState(false); + const [partial, setPartial] = useState(false); + const [watchQueue, setWatchQueue] = useState(null); + const [configuration, setConfiguration] = useState(null); + const [liveErrors, setLiveErrors] = useState(null); + const [liveTagMap, setLiveTagMap] = useState(null); + const [outstanding, setOutstanding] = useState(null); + const [liveStatus, setLiveStatus] = useState('queued'); + const [socket, setSocket] = useState(null); + const [loadInterval, setLoadInterval] = useState(null); + const [lastSuccessfulTrigger, setLastSuccessfulTrigger] = useState(0); + const [deleteDialog, setDeleteDialog] = useState(false); + const [waitingDialog, setWaitingDialog] = useState(false); + const [resubmitAnchor, setResubmitAnchor] = useState(null); const [baseFiles, setBaseFiles] = useState([]); + const [liveResultKeys, setLiveResultKeys] = useReducer(messageReducer, []); + const [liveErrorKeys, setLiveErrorKeys] = useReducer(messageReducer, []); + const [processedKeys, setProcessedKeys] = useReducer(messageReducer, []); + const [liveResults, setLiveResults] = useReducer(resultReducer, null); + const [loadTrigger, incrementLoadTrigger] = useReducer(incrementReducer, 0); + + const sp4 = theme.spacing(4); const popoverOpen = Boolean(resubmitAnchor); const updateLiveSumary = (results: object) => { - const tempSummary = summary !== null ? { ...summary } : { tags: {}, heuristics: {}, attack_matrix: {} }; + const tempSummary: any = summary !== null ? { ...summary } : { tags: {}, heuristics: {}, attack_matrix: {} }; const tempTagMap = liveTagMap !== null ? { ...liveTagMap } : {}; Object.entries(results).forEach(([resultKey, result]) => { @@ -273,9 +280,9 @@ function WrappedSubmissionDetail() { } } }); - setLiveTagMap(tempTagMap); - setHighlightMap(tempTagMap); - setSummary(tempSummary); + setLiveTagMap(tempTagMap as any); + setHighlightMap(tempTagMap as any); + setSummary(tempSummary as any); }; const updateLiveFileTree = (results: object) => { @@ -367,7 +374,7 @@ function WrappedSubmissionDetail() { setTree(tempTree); }; - const getParsedErrors = errorList => { + const getParsedErrors = useCallback((errorList: string[]): ParsedErrors => { const aggregated = errors => { const out = { depth: [], @@ -432,14 +439,18 @@ function WrappedSubmissionDetail() { return { aggregated: aggregated(errorList), - listed: errorList + listed: errorList, + services: [] }; - }; - - const parseSubmissionErrors = currentSubmission => ({ - ...currentSubmission, - parsed_errors: getParsedErrors(currentSubmission.errors) - }); + }, []); + + const parseSubmissionErrors = useCallback( + (current: Submission): ParsedSubmission => ({ + ...current, + parsed_errors: getParsedErrors(current.errors) + }), + [getParsedErrors] + ); const resetLiveMode = useCallback(() => { if (socket) { @@ -589,13 +600,13 @@ function WrappedSubmissionDetail() { useEffect(() => { if (currentUser.roles.includes('submission_view')) { - apiCall({ + apiCall({ url: '/api/v4/help/configuration/', onSuccess: api_data => { setConfiguration(api_data.api_response); } }); - apiCall({ + apiCall({ url: `/api/v4/submission/${id}/`, onSuccess: api_data => { setSubmission(parseSubmissionErrors(api_data.api_response)); @@ -610,7 +621,7 @@ function WrappedSubmissionDetail() { if (submission.state === 'completed') { if (socket) setNotifyFavicon(); resetLiveMode(); - apiCall({ + apiCall({ url: `/api/v4/submission/summary/${id}/`, onSuccess: summ_data => { setHighlightMap(summ_data.api_response.map); @@ -623,7 +634,7 @@ function WrappedSubmissionDetail() { } } }); - apiCall({ + apiCall({ url: `/api/v4/submission/tree/${id}/`, onSuccess: tree_data => { setTree(tree_data.api_response.tree); @@ -660,10 +671,10 @@ function WrappedSubmissionDetail() { useEffect(() => { if (liveStatus === 'processing') { - apiCall({ + apiCall({ url: `/api/v4/live/setup_watch_queue/${id}/`, - onSuccess: summ_data => { - setWatchQueue(summ_data.api_response.wq_id); + onSuccess: api_data => { + setWatchQueue(api_data.api_response.wq_id); }, onFailure: () => { setLiveStatus('queued'); @@ -853,7 +864,7 @@ function WrappedSubmissionDetail() { // eslint-disable-next-line no-console console.debug('LIVE :: Finding out oustanding services...'); - apiCall({ + apiCall({ url: `/api/v4/live/outstanding_services/${id}/`, onSuccess: api_data => { let newLiveStatus: 'processing' | 'rescheduled' | 'queued' = 'processing' as 'processing'; @@ -871,7 +882,7 @@ function WrappedSubmissionDetail() { if (liveStatus !== 'processing' && newLiveStatus !== 'processing') { // eslint-disable-next-line no-console console.debug('LIVE :: Checking if the submission is completed...'); - apiCall({ + apiCall({ url: `/api/v4/submission/${id}/`, onSuccess: submission_api_data => { if (submission_api_data.api_response.state === 'completed') { @@ -1113,7 +1124,7 @@ function WrappedSubmissionDetail() { )} {systemConfig.ui.allow_replay && currentUser.roles.includes('replay_trigger') && ( - + diff --git a/src/components/routes/submission/detail/attack.tsx b/src/components/routes/submission/detail/attack.tsx index cfb9be226..df9bff7f4 100644 --- a/src/components/routes/submission/detail/attack.tsx +++ b/src/components/routes/submission/detail/attack.tsx @@ -3,6 +3,7 @@ import ExpandMore from '@mui/icons-material/ExpandMore'; import { Collapse, Divider, Grid, Skeleton, Typography, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import useHighlighter from 'components/hooks/useHighlighter'; +import { AttackMatrix } from 'components/models/ui/file'; import Attack from 'components/visual/Attack'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -20,7 +21,7 @@ const useStyles = makeStyles(theme => ({ })); type AttackSectionProps = { - attack_matrix: any; + attack_matrix: AttackMatrix; force?: boolean; }; diff --git a/src/components/routes/submission/detail/errors.tsx b/src/components/routes/submission/detail/errors.tsx index c39faf3df..ca6b89d96 100644 --- a/src/components/routes/submission/detail/errors.tsx +++ b/src/components/routes/submission/detail/errors.tsx @@ -2,6 +2,7 @@ import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import { Button, Collapse, Divider, Link as MaterialLink, Typography, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import { ParsedErrors } from 'components/models/base/error'; import { getErrorTypeFromKey, getHashFromKey, getServiceFromKey } from 'helpers/errors'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -15,20 +16,7 @@ const useStyles = makeStyles(theme => ({ type ErrorSectionProps = { sid: string; - parsed_errors: { - aggregated: { - depth: string[]; - files: string[]; - retry: string[]; - down: string[]; - busy: string[]; - preempted: string[]; - exception: string[]; - unknown: string[]; - }; - listed: string[]; - services: string[]; - }; + parsed_errors: ParsedErrors; }; const WrappedErrorSection: React.FC = ({ sid, parsed_errors }) => { diff --git a/src/components/routes/submission/detail/file_tree.tsx b/src/components/routes/submission/detail/file_tree.tsx index 570050259..56e5ac0e7 100644 --- a/src/components/routes/submission/detail/file_tree.tsx +++ b/src/components/routes/submission/detail/file_tree.tsx @@ -6,6 +6,7 @@ import { Box, Collapse, Divider, IconButton, Skeleton, Tooltip, Typography, useT import makeStyles from '@mui/styles/makeStyles'; import useHighlighter from 'components/hooks/useHighlighter'; import useSafeResults from 'components/hooks/useSafeResults'; +import { SubmissionTree } from 'components/models/ui/submission'; import Verdict from 'components/visual/Verdict'; import React, { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; @@ -36,36 +37,6 @@ const useStyles = makeStyles(theme => ({ } })); -type FileItemProps = { - children: { - [key: string]: FileItemProps; - }; - name: string[]; - score: number; - sha256: string; - size: number; - truncated: boolean; - type: string; -}; - -type FileTreeProps = { - tree: { - [key: string]: FileItemProps; - }; - sid: string; - force: boolean; - defaultForceShown: Array; -}; - -type FileTreeSectionProps = { - tree: { - [key: string]: FileItemProps; - }; - sid: string; - baseFiles: string[]; - force?: boolean; -}; - const isVisible = (curItem, forcedShown, isHighlighted, showSafeResults) => (curItem.score < 0 && !showSafeResults) || curItem.score > 0 || @@ -74,6 +45,13 @@ const isVisible = (curItem, forcedShown, isHighlighted, showSafeResults) => (curItem.children && Object.values(curItem.children).some(c => isVisible(c, forcedShown, isHighlighted, showSafeResults))); +type FileTreeSectionProps = { + tree: SubmissionTree['tree']; + sid: string; + baseFiles: string[]; + force?: boolean; +}; + const WrappedFileTreeSection: React.FC = ({ tree, sid, baseFiles, force = false }) => { const { t } = useTranslation(['submissionDetail']); const [open, setOpen] = React.useState(true); @@ -120,6 +98,13 @@ const WrappedFileTreeSection: React.FC = ({ tree, sid, bas ); }; +type FileTreeProps = { + tree: SubmissionTree['tree']; + sid: string; + force: boolean; + defaultForceShown: Array; +}; + const WrappedFileTree: React.FC = ({ tree, sid, defaultForceShown, force = false }) => { const { t } = useTranslation('submissionDetail'); const theme = useTheme(); diff --git a/src/components/routes/submission/detail/heuristics.tsx b/src/components/routes/submission/detail/heuristics.tsx index 8b576c6ad..853b6c7ac 100644 --- a/src/components/routes/submission/detail/heuristics.tsx +++ b/src/components/routes/submission/detail/heuristics.tsx @@ -3,6 +3,7 @@ import ExpandMore from '@mui/icons-material/ExpandMore'; import { Collapse, Divider, Grid, Skeleton, Typography, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import useHighlighter from 'components/hooks/useHighlighter'; +import { Heuristics } from 'components/models/ui/file'; import Heuristic from 'components/visual/Heuristic'; import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; @@ -19,11 +20,11 @@ const useStyles = makeStyles(theme => ({ } })); -type HeuristicSectionProps = { - heuristics: any; +type Props = { + heuristics: Heuristics; }; -const WrappedHeuristicSection: React.FC = ({ heuristics }) => { +const WrappedHeuristicSection: React.FC = ({ heuristics }) => { const { t } = useTranslation(['submissionDetail']); const [open, setOpen] = React.useState(true); const theme = useTheme(); diff --git a/src/components/routes/submission/detail/info.tsx b/src/components/routes/submission/detail/info.tsx index 6f0f0c116..3710e8a79 100644 --- a/src/components/routes/submission/detail/info.tsx +++ b/src/components/routes/submission/detail/info.tsx @@ -4,6 +4,7 @@ import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandMore from '@mui/icons-material/ExpandMore'; import { Collapse, Divider, Grid, Skeleton, Typography, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import { ParsedSubmission } from 'components/models/base/submission'; import Priority from 'components/visual/Priority'; import Verdict from 'components/visual/Verdict'; import React, { Fragment, useMemo } from 'react'; @@ -22,11 +23,11 @@ const useStyles = makeStyles(theme => ({ } })); -type InfoSectionProps = { - submission: any; +type Props = { + submission: ParsedSubmission; }; -const WrappedInfoSection: React.FC = ({ submission }) => { +const WrappedInfoSection: React.FC = ({ submission }) => { const { t } = useTranslation(['submissionDetail']); const [open, setOpen] = React.useState(true); const theme = useTheme(); diff --git a/src/components/routes/submission/detail/meta.tsx b/src/components/routes/submission/detail/meta.tsx index e02089e59..0bd6cb55a 100644 --- a/src/components/routes/submission/detail/meta.tsx +++ b/src/components/routes/submission/detail/meta.tsx @@ -3,6 +3,7 @@ import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import { Button, Collapse, Divider, Grid, Skeleton, Typography, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import useALContext from 'components/hooks/useALContext'; +import { Metadata } from 'components/models/base/submission'; import ActionableText from 'components/visual/ActionableText'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -33,12 +34,12 @@ const useStyles = makeStyles(theme => ({ } })); -type MetaSectionProps = { - metadata: any; +type Props = { + metadata: Metadata; classification: string; }; -const WrappedMetaSection: React.FC = ({ metadata, classification }) => { +const WrappedMetaSection: React.FC = ({ metadata, classification }) => { const { t } = useTranslation(['submissionDetail']); const theme = useTheme(); const classes = useStyles(); diff --git a/src/components/routes/submission/detail/tags.tsx b/src/components/routes/submission/detail/tags.tsx index fd6d1e188..3169d03ae 100644 --- a/src/components/routes/submission/detail/tags.tsx +++ b/src/components/routes/submission/detail/tags.tsx @@ -3,8 +3,9 @@ import ExpandMore from '@mui/icons-material/ExpandMore'; import { Collapse, Divider, Grid, Typography, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import useSafeResults from 'components/hooks/useSafeResults'; +import { Tags } from 'components/models/ui/file'; import AutoHideTagList from 'components/visual/AutoHideTagList'; -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; const useStyles = makeStyles(theme => ({ @@ -24,20 +25,22 @@ const useStyles = makeStyles(theme => ({ } })); -type TagSectionProps = { +type Props = { tag_group: string; - tags: any; + tags: Tags; force?: boolean; }; -const WrappedTagSection: React.FC = ({ tag_group, tags, force = false }) => { +const WrappedTagSection: React.FC = ({ tag_group, tags, force = false }) => { const { t } = useTranslation(['submissionDetail']); - const [open, setOpen] = React.useState(true); const theme = useTheme(); const classes = useStyles(); - const sp2 = theme.spacing(2); const { showSafeResults } = useSafeResults(); - const [tagUnsafeMap, setTagUnsafeMap] = React.useState({}); + + const [tagUnsafeMap, setTagUnsafeMap] = useState({}); + const [open, setOpen] = useState(true); + + const sp2 = theme.spacing(2); useEffect(() => { if (tags) { diff --git a/src/components/routes/submission/report.tsx b/src/components/routes/submission/report.tsx index 8a443e416..68f513371 100644 --- a/src/components/routes/submission/report.tsx +++ b/src/components/routes/submission/report.tsx @@ -13,7 +13,8 @@ import { useEffectOnce } from 'commons/components/utils/hooks/useEffectOnce'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { TSubmissionReport } from 'components/models/ui/submission_report'; +import { CustomUser } from 'components/models/ui/user'; import Classification from 'components/visual/Classification'; import { filterObject } from 'helpers/utils'; import { useCallback, useEffect, useState } from 'react'; @@ -66,10 +67,10 @@ export default function SubmissionReport() { const navigate = useNavigate(); const { apiCall } = useMyAPI(); const theme = useTheme(); - const [report, setReport] = useState(null); - const [originalReport, setOriginalReport] = useState(null); - const [showInfoContent, setShowInfoContent] = useState(false); - const [useAIReport, setUseAIReport] = useState(false); + const [report, setReport] = useState(null); + const [originalReport, setOriginalReport] = useState(null); + const [showInfoContent, setShowInfoContent] = useState(false); + const [useAIReport, setUseAIReport] = useState(false); const cleanupReport = useCallback(() => { const recursiveFileTreeCleanup = (tree, impFiles) => { @@ -137,7 +138,7 @@ export default function SubmissionReport() { setReport({ ...originalReport, attack_matrix: tempMatrix, - heuristics: tempHeur, + heuristics: tempHeur as any, heuristic_sections: tempHeurSec, important_files: tempImpFiles, tags: tempTags diff --git a/src/components/routes/submission/report/attack.tsx b/src/components/routes/submission/report/attack.tsx index 5796eb449..650069add 100644 --- a/src/components/routes/submission/report/attack.tsx +++ b/src/components/routes/submission/report/attack.tsx @@ -1,5 +1,6 @@ import { Divider, Skeleton, Typography, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import { TAttackMatrix, TSubmissionReport } from 'components/models/ui/submission_report'; import TextVerdict from 'components/visual/TextVerdict'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -38,7 +39,12 @@ const useStyles = makeStyles(theme => ({ } })); -function AttackMatrixBlock({ attack, items }) { +type AttackMatrixBlockProps = { + attack: string; + items: TAttackMatrix; +}; + +function AttackMatrixBlock({ attack, items }: AttackMatrixBlockProps) { const classes = useStyles(); return (
@@ -73,7 +79,11 @@ function AttackMatrixSkel() { ); } -function WrappedAttack({ report }) { +type AttackProps = { + report: TSubmissionReport; +}; + +function WrappedAttack({ report }: AttackProps) { const { t } = useTranslation(['submissionReport']); const classes = useStyles(); diff --git a/src/components/routes/submission/report/attribution_banner.tsx b/src/components/routes/submission/report/attribution_banner.tsx index 8baff8900..2a10e5777 100644 --- a/src/components/routes/submission/report/attribution_banner.tsx +++ b/src/components/routes/submission/report/attribution_banner.tsx @@ -5,6 +5,7 @@ import VerifiedUserOutlinedIcon from '@mui/icons-material/VerifiedUserOutlined'; import { Grid, Skeleton, useMediaQuery, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import useALContext from 'components/hooks/useALContext'; +import { TSubmissionReport } from 'components/models/ui/submission_report'; import Verdict from 'components/visual/Verdict'; import VerdictGauge from 'components/visual/VerdictGauge'; import React from 'react'; @@ -30,7 +31,11 @@ const useStyles = makeStyles(theme => ({ } })); -export function WrappedAttributionBanner({ report }) { +type Props = { + report: TSubmissionReport; +}; + +export function WrappedAttributionBanner({ report }: Props) { const { t } = useTranslation(['submissionReport']); const theme = useTheme(); const classes = useStyles(); diff --git a/src/components/routes/submission/report/file_tree.tsx b/src/components/routes/submission/report/file_tree.tsx index f295b20e5..32f281bac 100644 --- a/src/components/routes/submission/report/file_tree.tsx +++ b/src/components/routes/submission/report/file_tree.tsx @@ -1,5 +1,6 @@ import { Divider, Skeleton, Typography } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import { TSubmissionReport } from 'components/models/ui/submission_report'; import Verdict from 'components/visual/Verdict'; import { bytesToSize } from 'helpers/utils'; import React from 'react'; @@ -36,7 +37,12 @@ const useStyles = makeStyles(theme => ({ } })); -function FileTree({ tree, important_files }) { +type Props = { + tree: TSubmissionReport['file_tree']; + important_files: TSubmissionReport['important_files']; +}; + +function FileTree({ tree, important_files }: Props) { const classes = useStyles(); return tree && important_files ? ( diff --git a/src/components/routes/submission/report/general_info.tsx b/src/components/routes/submission/report/general_info.tsx index 60b26eb6f..6257d6976 100644 --- a/src/components/routes/submission/report/general_info.tsx +++ b/src/components/routes/submission/report/general_info.tsx @@ -1,5 +1,6 @@ import { Divider, Grid, Skeleton, Typography, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import { TSubmissionReport } from 'components/models/ui/submission_report'; import React from 'react'; import { useTranslation } from 'react-i18next'; import Moment from 'react-moment'; @@ -26,7 +27,11 @@ const useStyles = makeStyles(theme => ({ } })); -function WrappedGeneralInformation({ report }) { +type Props = { + report: TSubmissionReport; +}; + +function WrappedGeneralInformation({ report }: Props) { const { t } = useTranslation(['submissionReport']); const theme = useTheme(); const classes = useStyles(); diff --git a/src/components/routes/submission/report/heuristics.tsx b/src/components/routes/submission/report/heuristics.tsx index eff682144..6075c6e4e 100644 --- a/src/components/routes/submission/report/heuristics.tsx +++ b/src/components/routes/submission/report/heuristics.tsx @@ -1,6 +1,8 @@ import { Divider, Skeleton, Typography, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import clsx from 'clsx'; +import { Verdict } from 'components/models/base/alert'; +import { TSubmissionReport } from 'components/models/ui/submission_report'; import ResultSection from 'components/visual/ResultCard/result_section'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -58,7 +60,15 @@ const useStyles = makeStyles(theme => ({ } })); -function HeuristicsList({ verdict, items, sections, name_map, force = false }) { +type HeuristicsListProps = { + verdict: Verdict; + items: TSubmissionReport['heuristics'][Verdict]; + sections: TSubmissionReport['heuristic_sections']; + name_map: TSubmissionReport['heuristic_name_map']; + force?: boolean; +}; + +function HeuristicsList({ verdict, items, sections, name_map, force = false }: HeuristicsListProps) { const classes = useStyles(); const theme = useTheme(); const classMap = { @@ -120,7 +130,11 @@ function HeuristicsListSkel() { ); } -function WrappedHeuristics({ report }) { +type Props = { + report: TSubmissionReport; +}; + +function WrappedHeuristics({ report }: Props) { const { t } = useTranslation(['submissionReport']); const classes = useStyles(); diff --git a/src/components/routes/submission/report/metadata.tsx b/src/components/routes/submission/report/metadata.tsx index de611951c..71abb1ada 100644 --- a/src/components/routes/submission/report/metadata.tsx +++ b/src/components/routes/submission/report/metadata.tsx @@ -3,6 +3,7 @@ import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import { Button, Collapse, Divider, Skeleton, Typography, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import useALContext from 'components/hooks/useALContext'; +import { TSubmissionReport } from 'components/models/ui/submission_report'; import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -43,7 +44,11 @@ const useStyles = makeStyles(theme => ({ } })); -function WrappedMetadata({ report }) { +type Props = { + report: TSubmissionReport; +}; + +function WrappedMetadata({ report }: Props) { const { t } = useTranslation(['submissionReport']); const { configuration } = useALContext(); const theme = useTheme(); diff --git a/src/components/routes/submission/report/tags.tsx b/src/components/routes/submission/report/tags.tsx index b15dd8821..4e25be6c0 100644 --- a/src/components/routes/submission/report/tags.tsx +++ b/src/components/routes/submission/report/tags.tsx @@ -1,5 +1,6 @@ import { Divider, Grid, Typography, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import { TSubmissionReport } from 'components/models/ui/submission_report'; import TextVerdict from 'components/visual/TextVerdict'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -30,7 +31,12 @@ const useStyles = makeStyles(theme => ({ } })); -function TagTable({ group, items }) { +type TagTableProps = { + group: string; + items: boolean; +}; + +function TagTable({ group, items }: TagTableProps) { const { t } = useTranslation(['submissionReport']); const theme = useTheme(); const orderedItems = {}; @@ -93,7 +99,11 @@ function TagTable({ group, items }) { ) : null; } -function WrappedTags({ report }) { +type Props = { + report: TSubmissionReport; +}; + +function WrappedTags({ report }: Props) { return ( report && ( <> diff --git a/src/components/routes/submissions.tsx b/src/components/routes/submissions.tsx index 177b2a936..b5b99044a 100644 --- a/src/components/routes/submissions.tsx +++ b/src/components/routes/submissions.tsx @@ -8,11 +8,13 @@ import PageFullWidth from 'commons/components/pages/PageFullWidth'; import PageHeader from 'commons/components/pages/PageHeader'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; +import { SubmissionIndexed } from 'components/models/base/submission'; +import { SearchResult } from 'components/models/ui/search'; import SearchBar from 'components/visual/SearchBar/search-bar'; import { DEFAULT_SUGGESTION } from 'components/visual/SearchBar/search-textfield'; import SimpleSearchQuery from 'components/visual/SearchBar/simple-search-query'; import SearchPager from 'components/visual/SearchPager'; -import SubmissionsTable, { SubmissionResult } from 'components/visual/SearchResult/submissions'; +import SubmissionsTable from 'components/visual/SearchResult/submissions'; import SearchResultCount from 'components/visual/SearchResultCount'; import { safeFieldValue } from 'helpers/utils'; import 'moment/locale/fr'; @@ -34,32 +36,28 @@ const useStyles = makeStyles(theme => ({ } })); -type SearchResults = { - items: SubmissionResult[]; - offset: number; - rows: number; - total: number; -}; - export default function Submissions() { const { t } = useTranslation(['submissions']); - const [pageSize] = useState(PAGE_SIZE); - const [submissionResults, setSubmissionResults] = useState(null); - const [searching, setSearching] = useState(false); - const { user: currentUser, indexes } = useALContext(); - const navigate = useNavigate(); - const { apiCall } = useMyAPI(); const theme = useTheme(); + const classes = useStyles(); + const navigate = useNavigate(); const location = useLocation(); + const { apiCall } = useMyAPI(); + const { user: currentUser, indexes } = useALContext(); + + const [pageSize] = useState(PAGE_SIZE); + const [submissionResults, setSubmissionResults] = useState>(null); + const [searching, setSearching] = useState(false); const [query, setQuery] = useState(null); - const upMD = useMediaQuery(theme.breakpoints.up('md')); - const filterValue = useRef(''); - const classes = useStyles(); const [suggestions] = useState([ ...Object.keys(indexes.submission).filter(name => indexes.submission[name].indexed), ...DEFAULT_SUGGESTION ]); + const filterValue = useRef(''); + + const upMD = useMediaQuery(theme.breakpoints.up('md')); + const onClear = () => { navigate(location.pathname); }; @@ -88,16 +86,12 @@ export default function Submissions() { if (query && currentUser.roles.includes('submission_view')) { query.set('rows', pageSize); query.set('offset', 0); - apiCall({ + apiCall>({ method: 'POST', url: '/api/v4/search/submission/', body: query.getParams(), - onSuccess: api_data => { - setSubmissionResults(api_data.api_response); - }, - onFinalize: () => { - setSearching(false); - } + onSuccess: api_data => setSubmissionResults(api_data.api_response), + onFinalize: () => setSearching(false) }); } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/components/routes/submit.tsx b/src/components/routes/submit.tsx index 8cca77178..cac6b0a68 100644 --- a/src/components/routes/submit.tsx +++ b/src/components/routes/submit.tsx @@ -31,6 +31,7 @@ import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; import ServiceSpec from 'components/layout/serviceSpec'; import ServiceTree from 'components/layout/serviceTree'; +import { UserSettings } from 'components/models/base/user_settings'; import Classification from 'components/visual/Classification'; import ConfirmationDialog from 'components/visual/ConfirmationDialog'; import FileDropper from 'components/visual/FileDropper'; @@ -81,37 +82,41 @@ const useStyles = makeStyles(theme => ({ })); const Submit: React.FC = () => { - const { apiCall } = useMyAPI(); const { t, i18n } = useTranslation(['submit']); + const { apiCall } = useMyAPI(); const theme = useTheme(); const classes = useStyles(); - const { user: currentUser, c12nDef, configuration } = useALContext(); - const [uuid, setUUID] = useState(null); - const [flow, setFlow] = useState(null); - const [settings, setSettings] = useState(null); - const [uploadProgress, setUploadProgress] = useState(null); - const [validate, setValidate] = useState(false); - const [validateCB, setValidateCB] = useState(null); - const [allowClick, setAllowClick] = useState(true); - const [file, setFile] = useState(null); - const downSM = useMediaQuery(theme.breakpoints.down('md')); - const md = useMediaQuery(theme.breakpoints.only('md')); - const { showErrorMessage, showSuccessMessage, closeSnackbar } = useMySnackbar(); const navigate = useNavigate(); const location = useLocation(); - const sp1 = theme.spacing(1); - const sp2 = theme.spacing(2); - const sp4 = theme.spacing(4); + const banner = useAppBanner(); + const { user: currentUser, c12nDef, configuration } = useALContext(); + const { showErrorMessage, showSuccessMessage, closeSnackbar } = useMySnackbar(); + const state: SubmitState = location.state as SubmitState; const urlHashTitle = configuration.ui.allow_url_submissions ? 'URL/SHA256' : 'SHA256'; const urlInputText = urlHashTitle + t('urlHash.input_suffix'); - const [urlHash, setUrlHash] = useState(state ? state.hash : ''); + + const [allowClick, setAllowClick] = useState(true); + const classification = useState(state ? state.c12n : null)[0]; + const [file, setFile] = useState(null); + const [flow, setFlow] = useState(null); + const [settings, setSettings] = useState(null); const [submissionMetadata, setSubmissionMetadata] = useState(state ? state.metadata : undefined); - const [urlHashHasError, setUrlHashHasError] = useState(false); + const [uploadProgress, setUploadProgress] = useState(null); const [urlAutoselection, setUrlAutoselection] = useState(false); + const [urlHash, setUrlHash] = useState(state ? state.hash : ''); + const [urlHashHasError, setUrlHashHasError] = useState(false); + const [uuid, setUUID] = useState(null); + const [validate, setValidate] = useState(false); + const [validateCB, setValidateCB] = useState(null); const [value, setValue] = useState(state ? state.tabContext : '0'); - const classification = useState(state ? state.c12n : null)[0]; - const banner = useAppBanner(); + + const sp1 = theme.spacing(1); + const sp2 = theme.spacing(2); + const sp4 = theme.spacing(4); + + const downSM = useMediaQuery(theme.breakpoints.down('md')); + const md = useMediaQuery(theme.breakpoints.only('md')); const handleChange = (event, newValue) => { setValue(newValue); @@ -408,7 +413,7 @@ const Submit: React.FC = () => { ); // Load user on start - apiCall({ + apiCall({ url: `/api/v4/user/settings/${currentUser.username}/`, onSuccess: api_data => { setSettings(api_data.api_response); diff --git a/src/components/routes/user/api_keys.tsx b/src/components/routes/user/api_keys.tsx index be8222404..577361c04 100644 --- a/src/components/routes/user/api_keys.tsx +++ b/src/components/routes/user/api_keys.tsx @@ -1,3 +1,5 @@ +import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined'; +import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; import { Button, Card, @@ -20,33 +22,17 @@ import FormControl from '@mui/material/FormControl'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; +import { ApiKey, User } from 'components/models/base/user'; import CustomChip from 'components/visual/CustomChip'; -import AddCircleOutlineOutlinedIcon from '@mui/icons-material/AddCircleOutlineOutlined'; -import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -type APIKeyProps = { - acl: string[]; - roles: string[]; -}; - type APIKeyCardProps = { name: string; - apikey: APIKeyProps; + apikey: ApiKey; askForDelete: (name: string) => void; }; -type APIKeysProps = { - user: { - [name: string]: any; - apikeys: { - [name: string]: APIKeyProps; - }; - }; - toggleAPIKey: (name: string, apiKey?: APIKeyProps) => void; -}; - const APIKeyCard = ({ name, apikey, askForDelete }: APIKeyCardProps) => { const { t } = useTranslation(['user']); const theme = useTheme(); @@ -81,6 +67,11 @@ const APIKeyCard = ({ name, apikey, askForDelete }: APIKeyCardProps) => { ); }; +type APIKeysProps = { + user: User; + toggleAPIKey: (name: string, apiKey?: ApiKey) => void; +}; + export default function APIKeys({ user, toggleAPIKey }: APIKeysProps) { const { t } = useTranslation(['user']); const [selectedAPIKey, setSelectedAPIKey] = useState(null); diff --git a/src/components/routes/user/apps.tsx b/src/components/routes/user/apps.tsx index 6b23e009a..14b12f594 100644 --- a/src/components/routes/user/apps.tsx +++ b/src/components/routes/user/apps.tsx @@ -1,3 +1,4 @@ +import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; import { Button, Dialog, @@ -11,22 +12,22 @@ import { useMediaQuery, useTheme } from '@mui/material'; +import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined'; +import { Apps as AppData, User } from 'components/models/base/user'; +import CustomChip from 'components/visual/CustomChip'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import CustomChip from 'components/visual/CustomChip'; -import useALContext from 'components/hooks/useALContext'; type AppsProps = { - user: any; + user: User; toggleApp: (apiKey: string) => void; }; type APIKeyCardProps = { id: string; - app: any; + app: AppData; askForDelete: (name: string) => void; }; diff --git a/src/components/routes/user/token.tsx b/src/components/routes/user/token.tsx index 900936a2d..84e9490dd 100644 --- a/src/components/routes/user/token.tsx +++ b/src/components/routes/user/token.tsx @@ -12,18 +12,19 @@ import { } from '@mui/material'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; +import { User } from 'components/models/base/user'; // eslint-disable-next-line import/extensions import CBOR from 'helpers/cbor.js'; import toArrayBuffer from 'helpers/toArrayBuffer'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; -type SecurityTokenProps = { - user: any; +type Props = { + user: User; toggleToken: (token: string) => void; }; -export default function SecurityToken({ user, toggleToken }: SecurityTokenProps) { +export default function SecurityToken({ user, toggleToken }: Props) { const { t } = useTranslation(['user']); const [selectedToken, setSelectedToken] = useState(null); const [tempToken, setTempToken] = useState(''); diff --git a/src/components/visual/ActionableCustomChip.tsx b/src/components/visual/ActionableCustomChip.tsx index b3fa023f5..41da9147c 100644 --- a/src/components/visual/ActionableCustomChip.tsx +++ b/src/components/visual/ActionableCustomChip.tsx @@ -1,4 +1,5 @@ import useExternalLookup from 'components/hooks/useExternalLookup'; +import { ExternalLinkType } from 'components/models/base/config'; import React, { useCallback } from 'react'; import ActionMenu from './ActionMenu'; import CustomChip, { CustomChipProps } from './CustomChip'; @@ -6,7 +7,7 @@ import ExternalLinks from './ExternalSearch'; export type ActionableCustomChipProps = CustomChipProps & { data_type?: string; - category?: 'hash' | 'metadata' | 'tag'; + category?: ExternalLinkType; classification?: string; label?: string; }; diff --git a/src/components/visual/ArchiveDetail/banner.tsx b/src/components/visual/ArchiveDetail/banner.tsx index 80ecf215f..00e91e8b3 100644 --- a/src/components/visual/ArchiveDetail/banner.tsx +++ b/src/components/visual/ArchiveDetail/banner.tsx @@ -27,7 +27,8 @@ import clsx from 'clsx'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { File } from 'components/routes/archive/detail'; +import { LabelCategories } from 'components/models/base/file'; +import type { File } from 'components/models/ui/file'; import CustomChip from 'components/visual/CustomChip'; import { bytesToSize } from 'helpers/utils'; import 'moment/locale/fr'; @@ -40,21 +41,11 @@ import FileDownloader from '../FileDownloader'; import InputDialog from '../InputDialog'; const VERDICTS = { - malicious: { - className: 'malicious' - }, - highly_suspicious: { - className: 'highly_suspicious' - }, - suspicious: { - className: 'suspicious' - }, - safe: { - className: 'safe' - }, - info: { - className: 'info' - } + malicious: { className: 'malicious' }, + highly_suspicious: { className: 'highly_suspicious' }, + suspicious: { className: 'suspicious' }, + safe: { className: 'safe' }, + info: { className: 'info' } }; const useStyles = makeStyles(theme => ({ @@ -181,21 +172,8 @@ const useStyles = makeStyles(theme => ({ } })); -type Props = { - sha256: string; - file: File; - sid?: string; - force?: boolean; -}; - -const DEFAULT_LABELS = { - attribution: [], - technique: [], - info: [] -}; - const LABELS: Record< - keyof typeof DEFAULT_LABELS, + keyof LabelCategories, { color: 'default' | 'primary' | 'error' | 'info' | 'success' | 'warning' | 'secondary' } > = { attribution: { color: 'primary' }, @@ -203,6 +181,13 @@ const LABELS: Record< info: { color: 'default' } }; +type Props = { + sha256: string; + file: File; + sid?: string; + force?: boolean; +}; + const WrappedArchiveBanner: React.FC = ({ sha256 = null, file = null, sid = null, force = false }) => { const { t } = useTranslation(['fileDetail', 'archive']); const theme = useTheme(); diff --git a/src/components/visual/ArchiveDetail/comments.tsx b/src/components/visual/ArchiveDetail/comments.tsx index 2c959a3eb..144343b3f 100644 --- a/src/components/visual/ArchiveDetail/comments.tsx +++ b/src/components/visual/ArchiveDetail/comments.tsx @@ -19,7 +19,8 @@ import makeStyles from '@mui/styles/makeStyles'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import CommentCard, { Authors, Comment, Comments, DEFAULT_COMMENT } from 'components/visual/CommentCard'; +import { Author, Comment, DEFAULT_COMMENT } from 'components/models/base/file'; +import CommentCard from 'components/visual/CommentCard'; import SectionContainer from 'components/visual/SectionContainer'; import 'moment/locale/fr'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -45,7 +46,7 @@ type Confirmation = { type Props = { sha256: string; - comments: Comments; + comments: Comment[]; drawer?: boolean; // inside the drawer nocollapse?: boolean; }; @@ -65,8 +66,8 @@ const WrappedCommentSection: React.FC = ({ const { user: currentUser } = useALContext(); const { showSuccessMessage, showErrorMessage } = useMySnackbar(); - const [comments, setComments] = useState(commentsProps); - const [authors, setAuthors] = useState(null); + const [comments, setComments] = useState(commentsProps); + const [authors, setAuthors] = useState(null); const [currentComment, setCurrentComment] = useState(DEFAULT_COMMENT); const [confirmation, setConfirmation] = useState({ open: false, type: 'add' }); const [waiting, setWaiting] = useState(false); diff --git a/src/components/visual/ArchiveDetail/labels.tsx b/src/components/visual/ArchiveDetail/labels.tsx index 91d22af87..80aaea7ff 100644 --- a/src/components/visual/ArchiveDetail/labels.tsx +++ b/src/components/visual/ArchiveDetail/labels.tsx @@ -30,7 +30,7 @@ import parse from 'autosuggest-highlight/parse'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { FileInfo } from 'components/routes/archive/detail'; +import { LabelCategories } from 'components/models/base/file'; import { ChipList } from 'components/visual/ChipList'; import CustomChip from 'components/visual/CustomChip'; import { useDebounce } from 'components/visual/HexViewer'; @@ -434,7 +434,7 @@ const WrappedLabelSection: React.FC = ({ sha256 = null, labels: propLabel }; type LabelCellProps = { - label_categories?: FileInfo['label_categories']; + label_categories?: LabelCategories; onLabelClick?: (event: React.MouseEvent, label: string) => void; }; diff --git a/src/components/visual/ArchiveDetail/similar.tsx b/src/components/visual/ArchiveDetail/similar.tsx index 776503d02..d2c1d104c 100644 --- a/src/components/visual/ArchiveDetail/similar.tsx +++ b/src/components/visual/ArchiveDetail/similar.tsx @@ -4,7 +4,7 @@ import { AlertTitle, IconButton, Skeleton, TableContainer, Tooltip, Typography, import makeStyles from '@mui/styles/makeStyles'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { File } from 'components/routes/archive/detail'; +import type { File, SimilarResult, SimilarResults, SimilarType } from 'components/models/ui/file'; import { GridLinkRow, GridTable, @@ -47,31 +47,14 @@ const useStyles = makeStyles(theme => ({ } })); -const DEFAULT_SIMILAR = { +const DEFAULT_SIMILAR: Record = { tlsh: { label: 'TLSH', prefix: '/search/file?query=tlsh:', suffix: '&use_archive=true' }, ssdeep: { label: 'SSDEEP', prefix: '/search/file?query=ssdeep:', suffix: '~&use_archive=true' }, vector: { label: 'Vector', prefix: '/search/result?query=result.sections.tags.vector:', suffix: '&use_archive=true' } }; -type Item = { from_archive: boolean; created?: string; sha256: string; type: string; seen?: { last: string } }; - -type Result = { - items: Item[]; - total: number; - type: string; - value: string; -}; - -type SectionProps = { - file: File; - show?: boolean; - title?: string; - drawer?: boolean; - nocollapse?: boolean; -}; - type SimilarItemProps = { - data: Result; + data: SimilarResult; drawer?: boolean; }; @@ -174,6 +157,14 @@ const SimilarItem: React.FC = ({ data, drawer }) => { ); }; +type SectionProps = { + file: File; + show?: boolean; + title?: string; + drawer?: boolean; + nocollapse?: boolean; +}; + const WrappedSimilarSection: React.FC = ({ file, show = false, @@ -187,13 +178,13 @@ const WrappedSimilarSection: React.FC = ({ const { apiCall } = useMyAPI(); const { showErrorMessage } = useMySnackbar(); - const [data, setData] = useState(null); + const [data, setData] = useState(null); const nbOfValues = useMemo(() => data && data.map(i => i.total).reduce((a, v) => a + v, 0), [data]); useEffect(() => { if (!file || data) return; - apiCall({ + apiCall({ method: 'GET', url: `/api/v4/file/similar/${file?.file_info?.sha256}/?use_archive`, onSuccess: api_data => setData(api_data.api_response), diff --git a/src/components/visual/ArchiveDetail/tags.tsx b/src/components/visual/ArchiveDetail/tags.tsx index 74f2a3d9a..6d90f7ba4 100644 --- a/src/components/visual/ArchiveDetail/tags.tsx +++ b/src/components/visual/ArchiveDetail/tags.tsx @@ -25,6 +25,8 @@ import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; import useSafeResults from 'components/hooks/useSafeResults'; +import { ResultIndexed } from 'components/models/base/result'; +import { SearchResult } from 'components/models/ui/search'; import ActionMenu from 'components/visual/ActionMenu'; import Classification from 'components/visual/Classification'; import { @@ -38,7 +40,7 @@ import { } from 'components/visual/GridTable'; import InformativeAlert from 'components/visual/InformativeAlert'; import SimpleSearchQuery from 'components/visual/SearchBar/simple-search-query'; -import ResultsTable, { ResultResult } from 'components/visual/SearchResult/results'; +import ResultsTable from 'components/visual/SearchResult/results'; import SectionContainer from 'components/visual/SectionContainer'; import Verdict from 'components/visual/Verdict'; import { safeFieldValue } from 'helpers/utils'; @@ -59,15 +61,6 @@ type Result = { classification: string; }; -type ArchivedTagSectionProps = { - sha256: string; - signatures: Signature[]; - tags: Record; - force?: boolean; - drawer?: boolean; - nocollapse?: boolean; -}; - const VERDICT_MAP = { malicious: 4, highly_suspicious: 3, @@ -76,7 +69,16 @@ const VERDICT_MAP = { safe: 0 }; -const WrappedArchivedTagSection: React.FC = ({ +type Props = { + sha256: string; + signatures: Signature[]; + tags: Record; + force?: boolean; + drawer?: boolean; + nocollapse?: boolean; +}; + +const WrappedArchivedTagSection: React.FC = ({ sha256, signatures, tags, @@ -458,12 +460,7 @@ const WrappedRow: React.FC = ({ const { c12nDef } = useALContext(); const { showErrorMessage } = useMySnackbar(); - const [resultResults, setResultResults] = useState<{ - items: ResultResult[]; - offset: number; - rows: number; - total: number; - }>(null); + const [resultResults, setResultResults] = useState>(null); const [error, setError] = useState(null); const [open, setOpen] = useState(false); const [render, setRender] = useState(false); @@ -472,7 +469,7 @@ const WrappedRow: React.FC = ({ useEffect(() => { if (!sha256 || !tag_type || !value || !open || !!resultResults) return; - apiCall({ + apiCall>({ method: 'POST', url: `/api/v4/search/result/`, body: { diff --git a/src/components/visual/Attack.tsx b/src/components/visual/Attack.tsx index eedac239b..ff1ad9e28 100644 --- a/src/components/visual/Attack.tsx +++ b/src/components/visual/Attack.tsx @@ -6,8 +6,8 @@ import useAppUser from 'commons/components/app/hooks/useAppUser'; import useClipboard from 'commons/components/utils/hooks/useClipboard'; import useALContext from 'components/hooks/useALContext'; import useHighlighter from 'components/hooks/useHighlighter'; -import { CustomUser } from 'components/hooks/useMyUser'; import useSafeResults from 'components/hooks/useSafeResults'; +import { CustomUser } from 'components/models/ui/user'; import CustomChip, { PossibleColors } from 'components/visual/CustomChip'; import { safeFieldValueURI } from 'helpers/utils'; import React, { useCallback } from 'react'; diff --git a/src/components/visual/AutoHideTagList.tsx b/src/components/visual/AutoHideTagList.tsx index 8a404e601..00a71d62c 100644 --- a/src/components/visual/AutoHideTagList.tsx +++ b/src/components/visual/AutoHideTagList.tsx @@ -1,7 +1,8 @@ -import { IconButton, Tooltip } from '@mui/material'; import MoreHorizOutlinedIcon from '@mui/icons-material/MoreHorizOutlined'; +import { IconButton, Tooltip } from '@mui/material'; import useHighlighter from 'components/hooks/useHighlighter'; import useSafeResults from 'components/hooks/useSafeResults'; +import { Verdict } from 'components/models/base/alert'; import Tag from 'components/visual/Tag'; import { verdictRank } from 'helpers/utils'; import React, { useEffect, useState } from 'react'; @@ -10,9 +11,9 @@ import Heuristic from './Heuristic'; type TagProps = { value: string; - lvl: string; + lvl: Verdict; safelisted: boolean; - classification: string; + classification?: string; }; type AutoHideTagListProps = { diff --git a/src/components/visual/Carousel/Container.tsx b/src/components/visual/Carousel/Container.tsx index f1c64a9ec..9d6ca7476 100644 --- a/src/components/visual/Carousel/Container.tsx +++ b/src/components/visual/Carousel/Container.tsx @@ -11,6 +11,7 @@ import makeStyles from '@mui/styles/makeStyles'; import clsx from 'clsx'; import Carousel from 'commons/addons/carousel/Carousel'; import useMyAPI from 'components/hooks/useMyAPI'; +import { Image as ImageData } from 'components/models/base/result_body'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Link, useLocation } from 'react-router-dom'; @@ -222,13 +223,6 @@ const useStyles = makeStyles(theme => { }; }); -export type Image = { - name: string; - description: string; - img: string; - thumb: string; -}; - type Dragging = { isDown: boolean; isDragging?: boolean; @@ -239,7 +233,7 @@ type Dragging = { }; type CarouselContainerProps = { - images: Image[]; + images: ImageData[]; open: boolean; index: number; setIndex: React.Dispatch>; @@ -282,7 +276,7 @@ const WrappedCarouselContainer = ({ const zoomTimer = useRef(null); const zoomClass = useMemo(() => isZooming && ZOOM_CLASS, [isZooming]); - const currentImage = useMemo(() => images && images[index], [images, index]); + const currentImage = useMemo(() => images && images[index], [images, index]); const dragTimer = useRef(null); const handleClose = useCallback( diff --git a/src/components/visual/CommentCard.tsx b/src/components/visual/CommentCard.tsx index a74cb1edc..a49b4a7f7 100644 --- a/src/components/visual/CommentCard.tsx +++ b/src/components/visual/CommentCard.tsx @@ -19,16 +19,17 @@ import makeStyles from '@mui/styles/makeStyles'; import clsx from 'clsx'; import { AppUserAvatar } from 'commons/components/topnav/UserProfile'; import useALContext from 'components/hooks/useALContext'; +import { REACTIONS_TYPES, type Author, type Comment, type ReactionType } from 'components/models/base/file'; import 'moment/locale/fr'; import React, { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import Moment from 'react-moment'; -const PREVIOUS_CLASS = 'previous'; -const NEXT_CLASS = 'next'; -const CURRENT_USER_CLASS = 'current_user'; -const LOADING_CLASS = 'loading'; -const REACTIONS_CLASS = 'reactions'; +const PREVIOUS_CLASS = 'previous' as const; +const NEXT_CLASS = 'next' as const; +const CURRENT_USER_CLASS = 'current_user' as const; +const LOADING_CLASS = 'loading' as const; +const REACTIONS_CLASS = 'reactions' as const; const useStyles = makeStyles(theme => ({ root: { @@ -190,59 +191,11 @@ const CALENDAR_STRINGS = { } }; -export const REACTIONS = { - thumbs_up: '๐Ÿ‘', - thumbs_down: '๐Ÿ‘Ž', - love: '๐Ÿงก', - smile: '๐Ÿ˜€', - surprised: '๐Ÿ˜ฒ', - party: '๐ŸŽ‰' -}; - -export type Reaction = { - uname: string; - icon: keyof typeof REACTIONS; -}; - -export type Comment = { - cid?: string; - date?: string; - text?: string; - uname?: string; - reactions?: Reaction[]; -}; - -export type Author = { - uname?: string; - name?: string; - avatar?: string; - email?: string; -}; - -export type Comments = Comment[]; - -export type Authors = Record; - -export const DEFAULT_COMMENT: Comment = { - cid: null, - date: null, - text: '', - uname: null, - reactions: [] -}; - -export const DEFAULT_AUTHOR: Author = { - uname: null, - name: null, - avatar: null, - email: null -}; - type Props = { currentComment?: Comment; previousComment?: Comment; nextComment?: Comment; - authors?: Authors; + authors?: Author[]; onEditClick?: (comment: Comment) => (event: React.MouseEvent) => void; onDeleteClick?: (comment: Comment) => (event: React.MouseEvent) => void; onReactionClick?: ( @@ -265,12 +218,12 @@ const WrappedCommentCard: React.FC = ({ const classes = useStyles(); const { user: currentUser } = useALContext(); - const reactions = useMemo | object>( + const reactions = useMemo | object>( () => !currentComment || !('reactions' in currentComment) ? {} - : (Object.fromEntries( - Object.keys(REACTIONS).map(reaction => [ + : Object.fromEntries( + Object.keys(REACTIONS_TYPES).map(reaction => [ reaction, currentComment?.reactions ?.filter((v, i, a) => v?.icon === reaction) @@ -278,7 +231,7 @@ const WrappedCommentCard: React.FC = ({ .filter((v, i, a) => a.findIndex(e => e === v) === i) .sort((n1, n2) => n1.localeCompare(n2)) ]) - ) as Record), + ), [currentComment] ); @@ -388,7 +341,7 @@ const WrappedCommentCard: React.FC = ({ title={ currentUser.roles.includes('archive_comment') && ( - {Object.entries(REACTIONS).map(([icon, emoji]: [keyof typeof REACTIONS, string], i) => ( + {Object.entries(REACTIONS_TYPES).map(([icon, emoji], i) => ( = ({ - {REACTIONS[reaction]} + {REACTIONS_TYPES[reaction]} {names.length} } diff --git a/src/components/visual/DivTable.tsx b/src/components/visual/DivTable.tsx index d83c2018e..d58bc9bff 100644 --- a/src/components/visual/DivTable.tsx +++ b/src/components/visual/DivTable.tsx @@ -1,4 +1,17 @@ -import { Table, TableBody, TableCell, TableCellProps, TableHead, TableRow, TableSortLabel, Theme } from '@mui/material'; +import { + Table, + TableBody, + TableBodyProps, + TableCell, + TableCellProps, + TableHead, + TableHeadProps, + TableProps, + TableRow, + TableRowProps, + TableSortLabel, + Theme +} from '@mui/material'; import createStyles from '@mui/styles/createStyles'; import withStyles from '@mui/styles/withStyles'; import SimpleSearchQuery from 'components/visual/SearchBar/simple-search-query'; @@ -36,11 +49,10 @@ const BreakableTableCell = withStyles((theme: Theme) => }) )(TableCell); -type CellProps = { +interface CellProps extends TableCellProps { children?: React.ReactNode; breakable?: boolean; - [key: string]: any; -}; +} export const DivTableCell = ({ children, breakable, ...other }: CellProps) => breakable ? ( @@ -119,25 +131,25 @@ export const LinkRow = ({ children, to, ...other }) => ( ); -export const DivTableRow = ({ children, ...other }) => ( +export const DivTableRow = ({ children, ...other }: TableRowProps) => ( {children} ); -export const DivTableHead = ({ children, ...other }) => ( +export const DivTableHead = ({ children, ...other }: TableHeadProps) => ( {children} ); -export const DivTableBody = ({ children, ...other }) => ( +export const DivTableBody = ({ children, ...other }: TableBodyProps) => ( {children} ); -export const DivTable = ({ children, size = 'small' as 'small', ...other }) => ( +export const DivTable = ({ children, size = 'small' as 'small', ...other }: TableProps) => ( {children}
diff --git a/src/components/visual/Empty.tsx b/src/components/visual/Empty.tsx index 36e3c5388..0ed691f13 100644 --- a/src/components/visual/Empty.tsx +++ b/src/components/visual/Empty.tsx @@ -1,8 +1,4 @@ -type EmptyProps = { - [string: string]: any; -}; - -function Empty(props: EmptyProps) { +function Empty() { return
; } diff --git a/src/components/visual/ErrorCard.tsx b/src/components/visual/ErrorCard.tsx index 12e70841c..a932f19db 100644 --- a/src/components/visual/ErrorCard.tsx +++ b/src/components/visual/ErrorCard.tsx @@ -1,31 +1,12 @@ -import { Box, Collapse, useTheme } from '@mui/material'; -import makeStyles from '@mui/styles/makeStyles'; import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandMore from '@mui/icons-material/ExpandMore'; +import { Box, Collapse, useTheme } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; +import { Error } from 'components/models/base/error'; import React from 'react'; import { useTranslation } from 'react-i18next'; import Moment from 'react-moment'; -export type Error = { - archive_ts: string; - created: string; - expiry_ts: string | null; - response: { - message: string; - service_debug_info: string; - service_name: string; - service_tool_version: string; - service_version: string; - status: string; - }; - sha256: string; - type: string; -}; - -type ErrorCardProps = { - error: Error; -}; - const useStyles = makeStyles(theme => ({ card: { backgroundColor: theme.palette.mode === 'dark' ? '#371a1a' : '#fce9e9', @@ -55,7 +36,11 @@ const useStyles = makeStyles(theme => ({ } })); -const ErrorCard: React.FC = ({ error }) => { +type Props = { + error: Error; +}; + +const ErrorCard: React.FC = ({ error }) => { const classes = useStyles(); const { t } = useTranslation(['adminErrorViewer']); const theme = useTheme(); diff --git a/src/components/visual/ExternalSearch.tsx b/src/components/visual/ExternalSearch.tsx index 0e0ac7b44..62b9baed2 100644 --- a/src/components/visual/ExternalSearch.tsx +++ b/src/components/visual/ExternalSearch.tsx @@ -29,8 +29,8 @@ import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import LaunchOutlinedIcon from '@mui/icons-material/LaunchOutlined'; import MoreHorizOutlinedIcon from '@mui/icons-material/MoreHorizOutlined'; import useALContext from 'components/hooks/useALContext'; +import { DetailedItem } from 'components/models/base/alert'; import { ExternalEnrichmentResult } from 'components/providers/ExternalLookupProvider'; -import { DetailedItem } from 'components/routes/alerts/hooks/useAlerts'; import { ChipList } from 'components/visual/ChipList'; import Classification from 'components/visual/Classification'; import CustomChip, { CustomChipProps } from 'components/visual/CustomChip'; diff --git a/src/components/visual/FileDetail.tsx b/src/components/visual/FileDetail.tsx index 9d9791ce9..8dba7f88b 100644 --- a/src/components/visual/FileDetail.tsx +++ b/src/components/visual/FileDetail.tsx @@ -25,13 +25,14 @@ import useAppUser from 'commons/components/app/hooks/useAppUser'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { CustomUser } from 'components/hooks/useMyUser'; +import type { Error } from 'components/models/base/error'; +import type { File } from 'components/models/ui/file'; +import { CustomUser } from 'components/models/ui/user'; import ForbiddenPage from 'components/routes/403'; import { DEFAULT_TAB, TAB_OPTIONS } from 'components/routes/file/viewer'; import AISummarySection from 'components/routes/submission/detail/ai_summary'; import Classification from 'components/visual/Classification'; -import { Error } from 'components/visual/ErrorCard'; -import { AlternateResult, emptyResult, Result } from 'components/visual/ResultCard'; +import { emptyResult } from 'components/visual/ResultCard'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router'; @@ -51,76 +52,7 @@ import URIIdentificationSection from './FileDetail/uriIdent'; import FileDownloader from './FileDownloader'; import InputDialog from './InputDialog'; -type URIInfo = { - uri: string; - scheme: string; - netloc: string; - path: string; - params: string; - query: string; - fragment: string; - username: string; - password: string; - hostname: string; - port: number; -}; - -type FileInfo = { - archive_ts: string; - ascii: string; - classification: string; - entropy: number; - expiry_ts: string | null; - hex: string; - magic: string; - md5: string; - mime: string; - seen: { - count: number; - first: string; - last: string; - }; - sha1: string; - sha256: string; - size: number; - ssdeep: string; - tlsh: string; - type: string; - uri_info: URIInfo; -}; - -type File = { - alternates: { - [serviceName: string]: AlternateResult[]; - }; - attack_matrix: { - [category: string]: string[][]; - }; - classification: string; - childrens: { - name: string; - sha256: string; - }[]; - emptys: Result[]; - errors: Error[]; - file_info: FileInfo; - heuristics: { - [category: string]: string[][]; - }; - metadata: { - [level: string]: { - [key: string]: any; - }; - }; - parents: string[]; - results: Result[]; - signatures: string[][]; - tags: { - [type: string]: string[][]; - }; -}; - -type FileDetailProps = { +type Props = { sha256: string; sid?: string; liveResultKeys?: string[]; @@ -128,7 +60,7 @@ type FileDetailProps = { force?: boolean; }; -const WrappedFileDetail: React.FC = ({ +const WrappedFileDetail: React.FC = ({ sha256, sid = null, liveResultKeys = null, @@ -136,25 +68,26 @@ const WrappedFileDetail: React.FC = ({ force = false }) => { const { t } = useTranslation(['fileDetail']); + const theme = useTheme(); + const location = useLocation(); + const navigate = useNavigate(); + const { apiCall } = useMyAPI(); + const { c12nDef, configuration } = useALContext(); + const { user: currentUser } = useAppUser(); + const { showSuccessMessage } = useMySnackbar(); + const [file, setFile] = useState(null); const [safelistDialog, setSafelistDialog] = useState(false); const [safelistReason, setSafelistReason] = useState(''); const [badlistDialog, setBadlistDialog] = useState(false); const [badlistReason, setBadlistReason] = useState(''); - const [waitingDialog, setWaitingDialog] = useState(false); - const { apiCall } = useMyAPI(); - const { c12nDef, configuration } = useALContext(); - const { user: currentUser } = useAppUser(); - const theme = useTheme(); - const navigate = useNavigate(); - const { showSuccessMessage } = useMySnackbar(); + const [waitingDialog, setWaitingDialog] = useState(false); const [resubmitAnchor, setResubmitAnchor] = useState(null); const [promotedSections, setPromotedSections] = useState([]); - const popoverOpen = Boolean(resubmitAnchor); + const sp2 = theme.spacing(2); const sp4 = theme.spacing(4); - - const location = useLocation(); + const popoverOpen = Boolean(resubmitAnchor); const params = new URLSearchParams(location.search); const fileName = file ? params.get('name') || sha256 : null; diff --git a/src/components/visual/FileDetail/attacks.tsx b/src/components/visual/FileDetail/attacks.tsx index 4011a6735..4d0625d63 100644 --- a/src/components/visual/FileDetail/attacks.tsx +++ b/src/components/visual/FileDetail/attacks.tsx @@ -1,12 +1,13 @@ import { Grid, Skeleton } from '@mui/material'; import useHighlighter from 'components/hooks/useHighlighter'; +import { AttackMatrix } from 'components/models/ui/file'; import Attack from 'components/visual/Attack'; import SectionContainer from 'components/visual/SectionContainer'; import React from 'react'; import { useTranslation } from 'react-i18next'; type AttackSectionProps = { - attacks: any; + attacks: AttackMatrix; force?: boolean; nocollapse?: boolean; }; diff --git a/src/components/visual/FileDetail/childrens.tsx b/src/components/visual/FileDetail/childrens.tsx index fa42a5b85..4b0fb4f8a 100644 --- a/src/components/visual/FileDetail/childrens.tsx +++ b/src/components/visual/FileDetail/childrens.tsx @@ -1,6 +1,7 @@ import MoreHorizOutlinedIcon from '@mui/icons-material/MoreHorizOutlined'; import { AlertTitle, IconButton, Skeleton, Tooltip, Typography, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import { Childrens } from 'components/models/ui/file'; import InformativeAlert from 'components/visual/InformativeAlert'; import SectionContainer from 'components/visual/SectionContainer'; import 'moment/locale/fr'; @@ -21,7 +22,7 @@ const useStyles = makeStyles(theme => ({ })); type ChildrenSectionProps = { - childrens: any; + childrens: Childrens; show?: boolean; title?: string; nocollapse?: boolean; diff --git a/src/components/visual/FileDetail/detection.tsx b/src/components/visual/FileDetail/detection.tsx index 6ab142e41..fe2dcf4c8 100644 --- a/src/components/visual/FileDetail/detection.tsx +++ b/src/components/visual/FileDetail/detection.tsx @@ -6,15 +6,16 @@ import makeStyles from '@mui/styles/makeStyles'; import clsx from 'clsx'; import useHighlighter from 'components/hooks/useHighlighter'; import useSafeResults from 'components/hooks/useSafeResults'; -import { Result } from 'components/visual/ResultCard'; -import ResultSection, { Section } from 'components/visual/ResultCard/result_section'; +import { HeuristicLevel, HEURISTIC_LEVELS } from 'components/models/base/heuristic'; +import { Section } from 'components/models/base/result'; +import type { File } from 'components/models/ui/file'; +import ResultSection from 'components/visual/ResultCard/result_section'; import SectionContainer from 'components/visual/SectionContainer'; import { safeFieldValueURI } from 'helpers/utils'; -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Link, useLocation, useNavigate } from 'react-router-dom'; -const HEUR_LEVELS = ['malicious' as 'malicious', 'suspicious' as 'suspicious', 'info' as 'info', 'safe' as 'safe']; const DEFAULT_SEC_SCORE = -1000; const SCORE_SHOW_THRESHOLD = 0; @@ -84,15 +85,15 @@ const useStyles = makeStyles(theme => ({ } })); -type WrappedHeuristicProps = { +type HeuristicProps = { name: string; id: string; sections: Section[]; - level: 'malicious' | 'suspicious' | 'info' | 'safe'; + level: HeuristicLevel; force?: boolean; }; -const WrappedHeuristic: React.FC = ({ name, id, sections, level, force = false }) => { +const WrappedHeuristic: React.FC = ({ name, id, sections, level, force = false }: HeuristicProps) => { const { t } = useTranslation(); const [open, setOpen] = React.useState(false); const [render, setRender] = React.useState(false); @@ -171,15 +172,13 @@ const WrappedHeuristic: React.FC = ({ name, id, sections, const Heuristic = React.memo(WrappedHeuristic); -type WrappedDetectionProps = { - heuristics: { [category: string]: string[][] }; - results?: Result[]; +type DetectionProps = Partial & { section_map?: { [heur_id: string]: Section[] }; force?: boolean; nocollapse?: boolean; }; -const WrappedDetection: React.FC = ({ +const WrappedDetection: React.FC = ({ heuristics, results, section_map = null, @@ -187,10 +186,11 @@ const WrappedDetection: React.FC = ({ nocollapse = false }) => { const { t } = useTranslation(['fileDetail']); - const [sectionMap, setSectionMap] = React.useState({}); - const [maxScore, setMaxScore] = React.useState(DEFAULT_SEC_SCORE); const { showSafeResults } = useSafeResults(); + const [sectionMap, setSectionMap] = useState({}); + const [maxScore, setMaxScore] = useState(DEFAULT_SEC_SCORE); + useEffect(() => { if (results) { let newMaxScore = DEFAULT_SEC_SCORE; @@ -236,7 +236,7 @@ const WrappedDetection: React.FC = ({ (heuristics && maxScore < SCORE_SHOW_THRESHOLD && !showSafeResults && !force) ? null : ( {sectionMap && heuristics - ? HEUR_LEVELS.map((lvl, lid) => { + ? HEURISTIC_LEVELS.map((lvl, lid) => { return heuristics[lvl] ? (
{heuristics[lvl].map(([hid, hname], idx) => { diff --git a/src/components/visual/FileDetail/emptys.tsx b/src/components/visual/FileDetail/emptys.tsx index acaa8eaf8..e9ad23aa5 100644 --- a/src/components/visual/FileDetail/emptys.tsx +++ b/src/components/visual/FileDetail/emptys.tsx @@ -1,11 +1,12 @@ import { Skeleton } from '@mui/material'; +import { FileResult } from 'components/models/base/result'; import ResultCard from 'components/visual/ResultCard'; import SectionContainer from 'components/visual/SectionContainer'; import React from 'react'; import { useTranslation } from 'react-i18next'; type EmptySectionProps = { - emptys: any; + emptys: FileResult[]; sid: string; nocollapse?: boolean; }; diff --git a/src/components/visual/FileDetail/errors.tsx b/src/components/visual/FileDetail/errors.tsx index 63dc2bf8d..9897d08e3 100644 --- a/src/components/visual/FileDetail/errors.tsx +++ b/src/components/visual/FileDetail/errors.tsx @@ -1,10 +1,11 @@ +import { Error } from 'components/models/base/error'; import ErrorCard from 'components/visual/ErrorCard'; import SectionContainer from 'components/visual/SectionContainer'; import React from 'react'; import { useTranslation } from 'react-i18next'; type ErrorSectionProps = { - errors: any; + errors: Error[]; nocollapse?: boolean; }; diff --git a/src/components/visual/FileDetail/frequency.tsx b/src/components/visual/FileDetail/frequency.tsx index f922d9978..38b69ede9 100644 --- a/src/components/visual/FileDetail/frequency.tsx +++ b/src/components/visual/FileDetail/frequency.tsx @@ -1,15 +1,12 @@ import { Grid, Skeleton } from '@mui/material'; +import { Seen } from 'components/models/base/file'; import SectionContainer from 'components/visual/SectionContainer'; import React from 'react'; import { useTranslation } from 'react-i18next'; import Moment from 'react-moment'; type Props = { - seen: { - count: number; - first: string; - last: string; - }; + seen: Seen; nocollapse?: boolean; }; diff --git a/src/components/visual/FileDetail/heuristics.tsx b/src/components/visual/FileDetail/heuristics.tsx index ca2fff731..c555acc28 100644 --- a/src/components/visual/FileDetail/heuristics.tsx +++ b/src/components/visual/FileDetail/heuristics.tsx @@ -3,6 +3,7 @@ import ExpandMore from '@mui/icons-material/ExpandMore'; import { Collapse, Divider, Grid, Skeleton, Typography, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import useHighlighter from 'components/hooks/useHighlighter'; +import { Heuristics } from 'components/models/ui/file'; import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import Heuristic from '../Heuristic'; @@ -20,7 +21,7 @@ const useStyles = makeStyles(theme => ({ })); type HeuristicSectionProps = { - heuristics: any; + heuristics: Heuristics; }; const WrappedHeuristicSection: React.FC = ({ heuristics }) => { diff --git a/src/components/visual/FileDetail/ident.tsx b/src/components/visual/FileDetail/ident.tsx index fabe2b219..7ee8901ba 100644 --- a/src/components/visual/FileDetail/ident.tsx +++ b/src/components/visual/FileDetail/ident.tsx @@ -1,5 +1,7 @@ import { Grid, Skeleton, useMediaQuery, useTheme } from '@mui/material'; import useALContext from 'components/hooks/useALContext'; +import { File as FileInfo } from 'components/models/base/file'; +import { Section } from 'components/models/base/result'; import ActionableText from 'components/visual/ActionableText'; import Classification from 'components/visual/Classification'; import { ImageInlineBody } from 'components/visual/image_inline'; @@ -10,8 +12,8 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; type IdentificationSectionProps = { - fileinfo: any; - promotedSections?: any[]; + fileinfo: FileInfo; + promotedSections?: Section[]; nocollapse?: boolean; }; @@ -170,7 +172,9 @@ const WrappedIdentificationSection: React.FC = ({ {promotedSections ? promotedSections .filter(section => section.promote_to === 'ENTROPY') - .map((section, idx) => ) + .map((section, idx) => + section.body_format === 'GRAPH_DATA' ? : null + ) : null} @@ -179,7 +183,9 @@ const WrappedIdentificationSection: React.FC = ({ {promotedSections ? promotedSections .filter(section => section.promote_to === 'SCREENSHOT') - .map((section, idx) => ) + .map((section, idx) => + section.body_format === 'IMAGE' ? : null + ) : null}
diff --git a/src/components/visual/FileDetail/metadata.tsx b/src/components/visual/FileDetail/metadata.tsx index 336e4c3a5..fb2833adf 100644 --- a/src/components/visual/FileDetail/metadata.tsx +++ b/src/components/visual/FileDetail/metadata.tsx @@ -1,11 +1,12 @@ import { Grid, Skeleton } from '@mui/material'; +import { Metadata } from 'components/models/ui/file'; import CustomChip from 'components/visual/CustomChip'; import SectionContainer from 'components/visual/SectionContainer'; import React from 'react'; import { useTranslation } from 'react-i18next'; type MetadataSectionProps = { - metadata: any; + metadata: Metadata; nocollapse?: boolean; }; diff --git a/src/components/visual/FileDetail/parents.tsx b/src/components/visual/FileDetail/parents.tsx index d26ed443a..4e5b467af 100644 --- a/src/components/visual/FileDetail/parents.tsx +++ b/src/components/visual/FileDetail/parents.tsx @@ -21,7 +21,7 @@ const useStyles = makeStyles(theme => ({ })); type ParentSectionProps = { - parents: any; + parents: string[]; show?: boolean; title?: string; nocollapse?: boolean; diff --git a/src/components/visual/FileDetail/results.tsx b/src/components/visual/FileDetail/results.tsx index 85f4f60e5..939509f23 100644 --- a/src/components/visual/FileDetail/results.tsx +++ b/src/components/visual/FileDetail/results.tsx @@ -1,27 +1,21 @@ import { Skeleton } from '@mui/material'; import useSafeResults from 'components/hooks/useSafeResults'; -import ResultCard, { AlternateResult } from 'components/visual/ResultCard'; +import { FileResult } from 'components/models/base/result'; +import { Alternates } from 'components/models/ui/file'; +import ResultCard from 'components/visual/ResultCard'; import SectionContainer from 'components/visual/SectionContainer'; import React from 'react'; import { useTranslation } from 'react-i18next'; -type ResultSectionProps = { - results: any; +type Props = { sid: string; - alternates?: { - [serviceName: string]: AlternateResult[]; - }; + results: FileResult[]; + alternates?: Alternates; force?: boolean; nocollapse?: boolean; }; -const WrappedResultSection: React.FC = ({ - results, - sid, - alternates, - force = false, - nocollapse = false -}) => { +const WrappedResultSection: React.FC = ({ results, sid, alternates, force = false, nocollapse = false }) => { const { t } = useTranslation(['fileDetail']); const { showSafeResults } = useSafeResults(); diff --git a/src/components/visual/FileDetail/tags.tsx b/src/components/visual/FileDetail/tags.tsx index 848bf887d..a7dd01a7f 100644 --- a/src/components/visual/FileDetail/tags.tsx +++ b/src/components/visual/FileDetail/tags.tsx @@ -13,6 +13,8 @@ import { } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import useSafeResults from 'components/hooks/useSafeResults'; +import { Signature } from 'components/models/base/tagging'; +import { Tags } from 'components/models/ui/file'; import AutoHideTagList from 'components/visual/AutoHideTagList'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -38,8 +40,8 @@ const useStyles = makeStyles(theme => ({ })); type TagSectionProps = { - signatures: any; - tags: any; + signatures: Signature[]; + tags: Tags; force?: boolean; }; diff --git a/src/components/visual/FileDetail/uriIdent.tsx b/src/components/visual/FileDetail/uriIdent.tsx index f20d4fced..52d0d3b0d 100644 --- a/src/components/visual/FileDetail/uriIdent.tsx +++ b/src/components/visual/FileDetail/uriIdent.tsx @@ -1,60 +1,60 @@ import { Grid, Skeleton, useMediaQuery, useTheme } from '@mui/material'; +import { File as FileInfo } from 'components/models/base/file'; +import { Section } from 'components/models/base/result'; +import type { KeyValueBody, OrderedKeyValueBody } from 'components/models/base/result_body'; import { ImageInlineBody } from 'components/visual/image_inline'; import SectionContainer from 'components/visual/SectionContainer'; import React from 'react'; import { useTranslation } from 'react-i18next'; -type URIIdentificationSectionProps = { - fileinfo: any; - promotedSections?: any[]; - nocollapse?: boolean; -}; - -const parseValue = value => { - if (value instanceof Array) { - return value.join(' | '); - } else if (value === true) { - return 'true'; - } else if (value === false) { - return 'false'; - } else if (typeof value === 'object') { - return JSON.stringify(value); - } - return value; -}; - -const KVItem = ({ name, value }) => ( +const KVItem = ({ name, value }: { name: string; value: any }) => ( <> {name} - {parseValue(value)} + {(() => { + if (value instanceof Array) { + return value.join(' | '); + } else if (value === true) { + return 'true'; + } else if (value === false) { + return 'false'; + } else if (typeof value === 'object') { + return JSON.stringify(value); + } + return value; + })()} ); -const WrappedOrderedKVExtra = ({ body }) => ( +const WrappedOrderedKVExtra = ({ body }: { body: OrderedKeyValueBody }) => ( <> - {Object.keys(body).map(id => { - const item = body[id]; - return ; - })} + {Object.keys(body).map(id => ( + + ))} ); const OrderedKVExtra = React.memo(WrappedOrderedKVExtra); -const WrappedKVExtra = ({ body }) => ( +const WrappedKVExtra = ({ body }: { body: KeyValueBody }) => ( <> - {Object.keys(body).map((key, id) => { - return ; - })} + {Object.keys(body).map((key, id) => ( + + ))} ); const KVExtra = React.memo(WrappedKVExtra); +type URIIdentificationSectionProps = { + fileinfo: FileInfo; + promotedSections?: Section[]; + nocollapse?: boolean; +}; + const WrappedURIIdentificationSection: React.FC = ({ fileinfo, promotedSections = [], @@ -178,9 +178,9 @@ const WrappedURIIdentificationSection: React.FC = .map((section, idx) => section.body_format === 'KEY_VALUE' ? ( - ) : ( + ) : section.body_format === 'ORDERED_KEY_VALUE' ? ( - ) + ) : null ) : null} @@ -189,7 +189,9 @@ const WrappedURIIdentificationSection: React.FC = {promotedSections ? promotedSections .filter(section => section.promote_to === 'SCREENSHOT') - .map((section, idx) => ) + .map((section, idx) => + section.body_format === 'IMAGE' ? : null + ) : null}
diff --git a/src/components/visual/FileDropper.tsx b/src/components/visual/FileDropper.tsx index 2a32c18e3..79d650e11 100644 --- a/src/components/visual/FileDropper.tsx +++ b/src/components/visual/FileDropper.tsx @@ -3,7 +3,7 @@ import Typography from '@mui/material/Typography'; import { makeStyles } from '@mui/styles'; import clsx from 'clsx'; import useAppUser from 'commons/components/app/hooks/useAppUser'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import { memo, useEffect } from 'react'; import { useDropzone } from 'react-dropzone'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/visual/FileViewer/ascii.tsx b/src/components/visual/FileViewer/ascii.tsx index 5b7ea6500..57973f6df 100644 --- a/src/components/visual/FileViewer/ascii.tsx +++ b/src/components/visual/FileViewer/ascii.tsx @@ -2,7 +2,7 @@ import { Alert, Button, CircularProgress, Grid, LinearProgress, Tooltip, useMedi import makeStyles from '@mui/styles/makeStyles'; import useAppUser from 'commons/components/app/hooks/useAppUser'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import ForbiddenPage from 'components/routes/403'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/visual/FileViewer/code_summary.tsx b/src/components/visual/FileViewer/code_summary.tsx index 7ae35501c..d850c190d 100644 --- a/src/components/visual/FileViewer/code_summary.tsx +++ b/src/components/visual/FileViewer/code_summary.tsx @@ -2,7 +2,7 @@ import { Alert, CircularProgress, Tooltip, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import useAppUser from 'commons/components/app/hooks/useAppUser'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import ForbiddenPage from 'components/routes/403'; import React, { useCallback, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/visual/FileViewer/hex.tsx b/src/components/visual/FileViewer/hex.tsx index 581ad49bf..de50c5052 100644 --- a/src/components/visual/FileViewer/hex.tsx +++ b/src/components/visual/FileViewer/hex.tsx @@ -2,7 +2,7 @@ import { Alert, LinearProgress } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import useAppUser from 'commons/components/app/hooks/useAppUser'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import ForbiddenPage from 'components/routes/403'; import { HexViewerApp } from 'components/visual/HexViewer'; import React, { useEffect, useState } from 'react'; diff --git a/src/components/visual/FileViewer/image.tsx b/src/components/visual/FileViewer/image.tsx index 7f587a040..a96177fe1 100644 --- a/src/components/visual/FileViewer/image.tsx +++ b/src/components/visual/FileViewer/image.tsx @@ -5,7 +5,7 @@ import makeStyles from '@mui/styles/makeStyles'; import clsx from 'clsx'; import useAppUser from 'commons/components/app/hooks/useAppUser'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import ForbiddenPage from 'components/routes/403'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; diff --git a/src/components/visual/FileViewer/strings.tsx b/src/components/visual/FileViewer/strings.tsx index 854360500..0f1a5f3be 100644 --- a/src/components/visual/FileViewer/strings.tsx +++ b/src/components/visual/FileViewer/strings.tsx @@ -1,7 +1,7 @@ import { Alert, LinearProgress } from '@mui/material'; import useAppUser from 'commons/components/app/hooks/useAppUser'; import useMyAPI from 'components/hooks/useMyAPI'; -import { CustomUser } from 'components/hooks/useMyUser'; +import { CustomUser } from 'components/models/ui/user'; import ForbiddenPage from 'components/routes/403'; import React, { useEffect, useMemo, useState } from 'react'; import MonacoEditor, { LANGUAGE_SELECTOR } from '../MonacoEditor'; diff --git a/src/components/visual/GridTable.tsx b/src/components/visual/GridTable.tsx index 09459ec00..538eaf69f 100644 --- a/src/components/visual/GridTable.tsx +++ b/src/components/visual/GridTable.tsx @@ -30,9 +30,7 @@ export const StyledPaper: FC = memo( forwardRef(({ component, ...other }: StyledPaperProps, ref) => ( )), - { - shouldForwardProp: prop => prop !== 'paper' - } + { shouldForwardProp: prop => prop !== 'paper' } )(({ theme, paper = false }) => ({ // backgroundColor: theme.palette.mode === 'dark' ? '#0000001A' : '#FFFFFF1A', backgroundColor: paper @@ -139,15 +137,14 @@ export const GridTableRow: FC = memo( })) ); +// eslint-disable-next-line @typescript-eslint/no-unused-vars interface GridLinkRowProps extends GridTableRowProps { component?: never; to: To; } -export const GridLinkRow: FC = memo( - styled(({ to, ...other }: GridLinkRowProps) => ( - - ))(() => ({ +export const GridLinkRow: FC = memo( + styled(({ to, ...other }: any) => )(() => ({ cursor: 'pointer', textDecoration: 'none' })) diff --git a/src/components/visual/Histogram.tsx b/src/components/visual/Histogram.tsx index 8099cedb0..65353a6bd 100644 --- a/src/components/visual/Histogram.tsx +++ b/src/components/visual/Histogram.tsx @@ -191,7 +191,7 @@ const WrappedHistogram = ({ return histData ? (
- +
) : ( diff --git a/src/components/visual/LineGraph.tsx b/src/components/visual/LineGraph.tsx index 2d17a712b..4179a76f4 100644 --- a/src/components/visual/LineGraph.tsx +++ b/src/components/visual/LineGraph.tsx @@ -89,7 +89,7 @@ function WrappedLineGraph({ dataset, height, title, datatype, onClick, sorter, t return barData ? (
- +
) : ( diff --git a/src/components/visual/Notification/NotificationArea.tsx b/src/components/visual/Notification/NotificationArea.tsx index 0f3c61ea6..62b6d6943 100644 --- a/src/components/visual/Notification/NotificationArea.tsx +++ b/src/components/visual/Notification/NotificationArea.tsx @@ -37,14 +37,15 @@ import clsx from 'clsx'; import useALContext from 'components/hooks/useALContext'; import useMyAPI from 'components/hooks/useMyAPI'; import useMySnackbar from 'components/hooks/useMySnackbar'; -import { ConfigurationDefinition, SystemMessageDefinition } from 'components/hooks/useMyUser'; +import { Configuration } from 'components/models/base/config'; +import { ServiceIndexed } from 'components/models/base/service'; +import { SystemMessage } from 'components/models/ui/user'; import 'moment-timezone'; import 'moment/locale/fr'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { JSONFeedItem, NotificationItem, useNotificationFeed } from '.'; import ConfirmationDialog from '../ConfirmationDialog'; -import { ServiceResult } from '../SearchResult/service'; const useStyles = makeStyles(theme => ({ drawer: { @@ -186,7 +187,7 @@ const WrappedNotificationArea = () => { const { fetchJSONNotifications } = useNotificationFeed(); const [notifications, setNotifications] = useState>([]); - const [newSystemMessage, setNewSystemMessage] = useState({ + const [newSystemMessage, setNewSystemMessage] = useState({ user: '', title: '', severity: 'info', @@ -286,7 +287,7 @@ const WrappedNotificationArea = () => { }; const getColor = useCallback( - (sm: SystemMessageDefinition, color: 'color' | 'bgColor' = 'color', type: 1 | 2 | 3 = 1) => { + (sm: SystemMessage, color: 'color' | 'bgColor' = 'color', type: 1 | 2 | 3 = 1) => { if (sm === null || sm === undefined || sm.severity === null) return null; const c = classes[color + sm.severity.charAt(0).toUpperCase() + sm.severity.slice(1) + type]; return c === undefined ? null : c; @@ -323,7 +324,7 @@ const WrappedNotificationArea = () => { ); const getVersionType = useCallback( - (notification: JSONFeedItem, config: ConfigurationDefinition): null | 'newer' | 'current' | 'older' => { + (notification: JSONFeedItem, config: Configuration): null | 'newer' | 'current' | 'older' => { const notVer = notification?.url; const sysVer = config?.system?.version; if ( @@ -349,7 +350,7 @@ const WrappedNotificationArea = () => { [] ); - const getNewService = useCallback((notification: JSONFeedItem, services: Array): null | boolean => { + const getNewService = useCallback((notification: JSONFeedItem, services: ServiceIndexed[]): null | boolean => { if (!/(s|S)ervice/g.test(notification.title)) return null; const notificationTitle = notification?.title?.toLowerCase().slice(0, -16); return services.some(s => notificationTitle === s?.name?.toLowerCase()); @@ -358,10 +359,10 @@ const WrappedNotificationArea = () => { useEffect(() => { handleLastTimeOpen(); if (!configuration || !currentUser) return; - apiCall({ + apiCall({ url: '/api/v4/service/all/', onSuccess: api_data => { - const services2: Array = + const services2: ServiceIndexed[] = api_data && api_data.api_response && Array.isArray(api_data.api_response) ? api_data.api_response : null; fetchJSONNotifications({ urls: configuration.ui.rss_feeds, diff --git a/src/components/visual/ResultCard.tsx b/src/components/visual/ResultCard.tsx index c4c582d85..a83abb580 100644 --- a/src/components/visual/ResultCard.tsx +++ b/src/components/visual/ResultCard.tsx @@ -18,64 +18,16 @@ import useALContext from 'components/hooks/useALContext'; import useHighlighter from 'components/hooks/useHighlighter'; import useMyAPI from 'components/hooks/useMyAPI'; import useSafeResults from 'components/hooks/useSafeResults'; +import { AlternateResult, FileResult } from 'components/models/base/result'; import Classification from 'components/visual/Classification'; import Verdict from 'components/visual/Verdict'; import React, { useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import Moment from 'react-moment'; import ExtractedSection from './ResultCard/extracted'; -import { ExtractedFiles } from './ResultCard/extracted_file'; -import ResultSection, { Section, SectionItem } from './ResultCard/result_section'; +import ResultSection from './ResultCard/result_section'; import SupplementarySection from './ResultCard/supplementary'; -export type Result = { - archive_ts: string; - classification: string; - created: string; - drop_file: boolean; - expiry_ts: string | null; - response: { - extracted: ExtractedFiles[]; - milestones: { - service_completed: string; - service_started: string; - }; - service_context: string; - service_debug_info: string; - service_name: string; - service_tool_version: string; - service_version: string; - supplementary: ExtractedFiles[]; - }; - result: { - score: number; - sections: Section[]; - }; - section_hierarchy: SectionItem[]; - sha256: string; -}; - -export type AlternateResult = { - classification: string; - created: string; - drop_file: boolean; - id: string; - response: { - service_name: string; - service_version: string; - }; - result: { - score: number; - }; -}; - -type ResultCardProps = { - result: Result; - sid: string | null; - alternates?: AlternateResult[] | null; - force?: boolean; -}; - const useStyles = makeStyles(theme => ({ card: { backgroundColor: theme.palette.background.default, @@ -102,13 +54,20 @@ const useStyles = makeStyles(theme => ({ } })); -export const emptyResult = (result: Result) => +export const emptyResult = (result: FileResult) => result.result.score === 0 && result.result.sections.length === 0 && result.response.extracted.length === 0 && result.response.supplementary.length === 0; -const WrappedResultCard: React.FC = ({ result, sid, alternates = null, force = false }) => { +type Props = { + result: FileResult; + sid: string | null; + alternates?: AlternateResult[] | null; + force?: boolean; +}; + +const WrappedResultCard: React.FC = ({ result, sid, alternates = null, force = false }) => { const { t } = useTranslation(['fileDetail']); const classes = useStyles(); const theme = useTheme(); @@ -116,7 +75,7 @@ const WrappedResultCard: React.FC = ({ result, sid, alternates const sp2 = theme.spacing(2); const { c12nDef, settings } = useALContext(); const empty = emptyResult(result); - const [displayedResult, setDisplayedResult] = React.useState(result); + const [displayedResult, setDisplayedResult] = React.useState(result); const [open, setOpen] = React.useState(!empty && displayedResult.result.score >= settings.expand_min_score); const [render, setRender] = React.useState(!empty && displayedResult.result.score >= settings.expand_min_score); const [anchorEl, setAnchorEl] = React.useState(null); diff --git a/src/components/visual/ResultCard/extracted.tsx b/src/components/visual/ResultCard/extracted.tsx index 34610d41e..810d33014 100644 --- a/src/components/visual/ResultCard/extracted.tsx +++ b/src/components/visual/ResultCard/extracted.tsx @@ -2,9 +2,10 @@ import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandMore from '@mui/icons-material/ExpandMore'; import { Box, Collapse } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import { File } from 'components/models/base/result'; import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import ExtractedFile, { ExtractedFiles } from './extracted_file'; +import ExtractedFile from './extracted_file'; const useStyles = makeStyles(theme => ({ title: { @@ -19,7 +20,7 @@ const useStyles = makeStyles(theme => ({ })); type ExtractedSectionProps = { - extracted: ExtractedFiles[]; + extracted: File[]; sid: string; }; diff --git a/src/components/visual/ResultCard/extracted_file.tsx b/src/components/visual/ResultCard/extracted_file.tsx index e9aa0b97d..712305b71 100644 --- a/src/components/visual/ResultCard/extracted_file.tsx +++ b/src/components/visual/ResultCard/extracted_file.tsx @@ -1,25 +1,12 @@ import PageviewOutlinedIcon from '@mui/icons-material/PageviewOutlined'; import { IconButton, Link, Tooltip } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import { File } from 'components/models/base/result'; import { DEFAULT_TAB, TAB_OPTIONS } from 'components/routes/file/viewer'; import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Link as RouterLink, useLocation } from 'react-router-dom'; -export type ExtractedFiles = { - classification: string; - description: string; - name: string; - sha256: string; - is_section_image?: boolean; -}; - -type ExtractedFileProps = { - file: ExtractedFiles; - download?: boolean; - sid?: string; -}; - const useStyles = makeStyles(theme => ({ file: { marginRight: theme.spacing(1) @@ -46,7 +33,13 @@ const useStyles = makeStyles(theme => ({ } })); -const WrappedExtractedFile: React.FC = ({ file, download = false, sid = null }) => { +type Props = { + file: File; + download?: boolean; + sid?: string; +}; + +const WrappedExtractedFile: React.FC = ({ file, download = false, sid = null }) => { const { t } = useTranslation(['fileDetail']); const classes = useStyles(); const location = useLocation(); diff --git a/src/components/visual/ResultCard/graph_body.tsx b/src/components/visual/ResultCard/graph_body.tsx index d411ddba5..a555ae2b9 100644 --- a/src/components/visual/ResultCard/graph_body.tsx +++ b/src/components/visual/ResultCard/graph_body.tsx @@ -1,5 +1,6 @@ import { Theme, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import type { GraphBody as GraphData } from 'components/models/base/result_body'; import { scaleLinear } from 'd3-scale'; import React, { useState } from 'react'; @@ -10,7 +11,11 @@ const useStyles = makeStyles((theme: Theme) => ({ } })); -const WrappedGraphBody = ({ body }) => { +type Props = { + body: GraphData; +}; + +const WrappedGraphBody = ({ body }: Props) => { const theme = useTheme(); const [currentValue, setCurrentValue] = useState(null); diff --git a/src/components/visual/ResultCard/image_body.tsx b/src/components/visual/ResultCard/image_body.tsx index 87c2eb3e9..e4480bd5a 100644 --- a/src/components/visual/ResultCard/image_body.tsx +++ b/src/components/visual/ResultCard/image_body.tsx @@ -2,8 +2,8 @@ import { Theme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import clsx from 'clsx'; import useCarousel from 'components/hooks/useCarousel'; -import { Image } from 'components/visual/Carousel/Container'; -import { ImageBodyData, ImageItem } from 'components/visual/image_inline'; +import type { Image, ImageBody as ImageData } from 'components/models/base/result_body'; +import { ImageItem } from 'components/visual/image_inline'; import { default as React, useEffect, useState } from 'react'; const useStyles = makeStyles((theme: Theme) => ({ @@ -20,13 +20,13 @@ const useStyles = makeStyles((theme: Theme) => ({ } })); -type ImageBodyProps = { - body: ImageBodyData; +type Props = { + body: ImageData; printable?: boolean; small?: boolean; }; -const WrappedImageBody = ({ body, printable = false }: ImageBodyProps) => { +const WrappedImageBody = ({ body, printable = false }: Props) => { const classes = useStyles(); const { openCarousel } = useCarousel(); const [data, setData] = useState([]); diff --git a/src/components/visual/ResultCard/json_body.tsx b/src/components/visual/ResultCard/json_body.tsx index d6459f890..a553e918d 100644 --- a/src/components/visual/ResultCard/json_body.tsx +++ b/src/components/visual/ResultCard/json_body.tsx @@ -1,5 +1,6 @@ import { useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import type { JSONBody as JSONData } from 'components/models/base/result_body'; import { default as React } from 'react'; import ReactJson from 'react-json-view'; @@ -19,7 +20,12 @@ const useStyles = makeStyles(theme => ({ } })); -const WrappedJSONBody = ({ body, printable }) => { +type Props = { + body: JSONData; + printable: boolean; +}; + +const WrappedJSONBody = ({ body, printable }: Props) => { const classes = useStyles(); const theme = useTheme(); diff --git a/src/components/visual/ResultCard/kv_body.tsx b/src/components/visual/ResultCard/kv_body.tsx index e0ab8a601..0fe4b5bc1 100644 --- a/src/components/visual/ResultCard/kv_body.tsx +++ b/src/components/visual/ResultCard/kv_body.tsx @@ -1,7 +1,12 @@ +import type { KeyValueBody as KeyValueData } from 'components/models/base/result_body'; import TitleKey from 'components/visual/TitleKey'; import { default as React } from 'react'; -const WrappedKVBody = ({ body }) => ( +type Props = { + body: KeyValueData; +}; + +const WrappedKVBody = ({ body }: Props) => ( {Object.keys(body).map((key, id) => { diff --git a/src/components/visual/ResultCard/memdump_body.tsx b/src/components/visual/ResultCard/memdump_body.tsx index 15a84d398..34cfa3b31 100644 --- a/src/components/visual/ResultCard/memdump_body.tsx +++ b/src/components/visual/ResultCard/memdump_body.tsx @@ -1,4 +1,5 @@ import makeStyles from '@mui/styles/makeStyles'; +import type { MemDumpBody as MemDumpData } from 'components/models/base/result_body'; import { default as React } from 'react'; const useStyles = makeStyles(theme => ({ @@ -17,7 +18,11 @@ const useStyles = makeStyles(theme => ({ } })); -const WrappedMemDumpBody = ({ body }) => { +type Props = { + body: MemDumpData; +}; + +const WrappedMemDumpBody = ({ body }: Props) => { const classes = useStyles(); return
{body}
; }; diff --git a/src/components/visual/ResultCard/multi_body.tsx b/src/components/visual/ResultCard/multi_body.tsx index 071f937b4..e0eadb222 100644 --- a/src/components/visual/ResultCard/multi_body.tsx +++ b/src/components/visual/ResultCard/multi_body.tsx @@ -1,5 +1,6 @@ import { Divider, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import type { MultiBody as MultiData } from 'components/models/base/result_body'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { GraphBody } from './graph_body'; @@ -21,7 +22,12 @@ const useStyles = makeStyles(theme => ({ } })); -const WrappedMultiBody = ({ body, printable = false }) => { +type Props = { + body: MultiData; + printable?: boolean; +}; + +const WrappedMultiBody = ({ body, printable = false }: Props) => { const { t } = useTranslation(['fileDetail']); const classes = useStyles(); const theme = useTheme(); diff --git a/src/components/visual/ResultCard/ordered_kv_body.tsx b/src/components/visual/ResultCard/ordered_kv_body.tsx index 12e707f7d..8951ec237 100644 --- a/src/components/visual/ResultCard/ordered_kv_body.tsx +++ b/src/components/visual/ResultCard/ordered_kv_body.tsx @@ -1,7 +1,12 @@ +import type { OrderedKeyValueBody as OrderedKeyValueData } from 'components/models/base/result_body'; import TitleKey from 'components/visual/TitleKey'; import { default as React } from 'react'; -const WrappedOrderedKVBody = ({ body }) => ( +type Props = { + body: OrderedKeyValueData; +}; + +const WrappedOrderedKVBody = ({ body }: Props) => (
{Object.keys(body).map(id => { diff --git a/src/components/visual/ResultCard/process_tree_body.tsx b/src/components/visual/ResultCard/process_tree_body.tsx index 0d1ce553b..b9fc2679f 100644 --- a/src/components/visual/ResultCard/process_tree_body.tsx +++ b/src/components/visual/ResultCard/process_tree_body.tsx @@ -10,6 +10,7 @@ import makeStyles from '@mui/styles/makeStyles'; import clsx from 'clsx'; import useALContext from 'components/hooks/useALContext'; import useSafeResults from 'components/hooks/useSafeResults'; +import type { ProcessTreeBody as ProcessTreeData } from 'components/models/base/result_body'; import { humanReadableNumber } from 'helpers/utils'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -101,7 +102,12 @@ const useTreeItemStyles = makeStyles((theme: Theme) => ({ } })); -const ProcessTreeItem = ({ process, force = false }) => { +type ProcessTreeItemProps = { + process: ProcessTreeData; + force?: boolean; +}; + +const ProcessTreeItem = ({ process, force = false }: ProcessTreeItemProps) => { const { t } = useTranslation(['fileDetail']); const classes = useTreeItemStyles(); const { showSafeResults } = useSafeResults(); @@ -197,10 +203,25 @@ const ProcessTreeItem = ({ process, force = false }) => { ); }; -const ProcessTreeItemList = ({ processes, force = false }) => - processes.map((process, id) => ); +type ProcessTreeItemListProps = { + processes: ProcessTreeData[]; + force?: boolean; +}; + +const ProcessTreeItemList = ({ processes, force = false }: ProcessTreeItemListProps) => ( + <> + {processes.map((process, id) => ( + + ))} + +); + +type Props = { + body: ProcessTreeData[]; + force?: boolean; +}; -const WrappedProcessTreeBody = ({ body, force = false }) => { +const WrappedProcessTreeBody = ({ body, force = false }: Props) => { try { const expanded = []; diff --git a/src/components/visual/ResultCard/result_section.tsx b/src/components/visual/ResultCard/result_section.tsx index eabe0fd78..7961b0f91 100644 --- a/src/components/visual/ResultCard/result_section.tsx +++ b/src/components/visual/ResultCard/result_section.tsx @@ -9,6 +9,7 @@ import useClipboard from 'commons/components/utils/hooks/useClipboard'; import useALContext from 'components/hooks/useALContext'; import useHighlighter from 'components/hooks/useHighlighter'; import useSafeResults from 'components/hooks/useSafeResults'; +import type { Section, SectionItem } from 'components/models/base/result'; import Attack from 'components/visual/Attack'; import Classification from 'components/visual/Classification'; import Heuristic from 'components/visual/Heuristic'; @@ -72,47 +73,7 @@ const useStyles = makeStyles(theme => ({ } })); -export type SectionItem = { - children: SectionItem[]; - id: number; -}; - -export type Section = { - auto_collapse: boolean; - body: any; - body_format: string; - body_config?: { - column_order?: string[]; - }; - classification: string; - depth: number; - heuristic: { - attack: { - attack_id: string; - categories: string[]; - pattern: string; - }[]; - heur_id: string; - name: string; - score: number; - signature: { - frequency: number; - name: string; - safe: boolean; - }[]; - }; - tags: { - type: string; - short_type: string; - value: string; - safelisted: boolean; - classification: string; - }[]; - title_text: string; - promote_to: string; -}; - -type ResultSectionProps = { +type Props = { section: Section; section_list?: Section[]; sub_sections?: SectionItem[]; @@ -123,7 +84,7 @@ type ResultSectionProps = { force?: boolean; }; -const WrappedResultSection: React.FC = ({ +const WrappedResultSection: React.FC = ({ section, section_list = [], sub_sections = [], diff --git a/src/components/visual/ResultCard/supplementary.tsx b/src/components/visual/ResultCard/supplementary.tsx index d41a17614..e1d9f645b 100644 --- a/src/components/visual/ResultCard/supplementary.tsx +++ b/src/components/visual/ResultCard/supplementary.tsx @@ -2,9 +2,10 @@ import ExpandLess from '@mui/icons-material/ExpandLess'; import ExpandMore from '@mui/icons-material/ExpandMore'; import { Box, Collapse } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import { File } from 'components/models/base/result'; import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import ExtractedFile, { ExtractedFiles } from './extracted_file'; +import ExtractedFile from './extracted_file'; const useStyles = makeStyles(theme => ({ title: { @@ -19,7 +20,7 @@ const useStyles = makeStyles(theme => ({ })); type SupplementarySectionProps = { - supplementary: ExtractedFiles[]; + supplementary: File[]; sid: string; }; diff --git a/src/components/visual/ResultCard/table_body.tsx b/src/components/visual/ResultCard/table_body.tsx index 112ef5056..f03ea55f2 100644 --- a/src/components/visual/ResultCard/table_body.tsx +++ b/src/components/visual/ResultCard/table_body.tsx @@ -2,6 +2,7 @@ import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Theme import createStyles from '@mui/styles/createStyles'; import makeStyles from '@mui/styles/makeStyles'; import withStyles from '@mui/styles/withStyles'; +import type { TableBody as TableData } from 'components/models/base/result_body'; import { default as React } from 'react'; import TitleKey from '../TitleKey'; import { KVBody } from './kv_body'; @@ -53,7 +54,13 @@ const StyledTableRow = withStyles((theme: Theme) => }) )(TableRow); -const WrappedTblBody = ({ body, printable, order }) => { +type Props = { + body: TableData; + printable: boolean; + order: string[]; +}; + +const WrappedTblBody = ({ body, printable, order }: Props) => { const classes = useStyles(printable); const headers = []; diff --git a/src/components/visual/ResultCard/text_body.tsx b/src/components/visual/ResultCard/text_body.tsx index c02f78e95..582713196 100644 --- a/src/components/visual/ResultCard/text_body.tsx +++ b/src/components/visual/ResultCard/text_body.tsx @@ -1,6 +1,11 @@ +import type { TextBody as TextData } from 'components/models/base/result_body'; import { default as React } from 'react'; -export const WrappedTextBody = ({ body }) => ( +type Props = { + body: TextData; +}; + +export const WrappedTextBody = ({ body }: Props) => (
{body}
); export const TextBody = React.memo(WrappedTextBody); diff --git a/src/components/visual/ResultCard/timeline_body.tsx b/src/components/visual/ResultCard/timeline_body.tsx index be41dd9d3..949d157c3 100644 --- a/src/components/visual/ResultCard/timeline_body.tsx +++ b/src/components/visual/ResultCard/timeline_body.tsx @@ -1,6 +1,7 @@ import { Tooltip, useTheme } from '@mui/material'; import Typography from '@mui/material/Typography'; import useALContext from 'components/hooks/useALContext'; +import type { TimelineBody as TimelineData } from 'components/models/base/result_body'; import { verdictToColor } from 'helpers/utils'; import { AiOutlineFile, AiOutlineFileImage, AiOutlineFileUnknown, AiOutlineFileZip } from 'react-icons/ai'; import { BsFileEarmarkCode, BsFileLock, BsFileText, BsGlobe2, BsHddNetwork, BsTerminal } from 'react-icons/bs'; @@ -30,7 +31,11 @@ const AL_TYPE_ICON = { NETWORK: }; -const WrappedTimelineBody = ({ body }) => { +type Props = { + body: TimelineData[]; +}; + +const WrappedTimelineBody = ({ body }: Props) => { const { scoreToVerdict } = useALContext(); const theme = useTheme(); const COLOR_MAP = { diff --git a/src/components/visual/ResultCard/url_body.tsx b/src/components/visual/ResultCard/url_body.tsx index 5f6d0d3e5..c0526c545 100644 --- a/src/components/visual/ResultCard/url_body.tsx +++ b/src/components/visual/ResultCard/url_body.tsx @@ -1,7 +1,12 @@ import { Link } from '@mui/material'; +import type { URLBody as URLData } from 'components/models/base/result_body'; import { default as React } from 'react'; -const WrappedURLBody = ({ body }) => { +type Props = { + body: URLData; +}; + +const WrappedURLBody = ({ body }: Props) => { const arr = []; if (!(body instanceof Array)) { arr.push(body); diff --git a/src/components/visual/SearchPager.tsx b/src/components/visual/SearchPager.tsx index dc763aa16..0e4d0cd69 100644 --- a/src/components/visual/SearchPager.tsx +++ b/src/components/visual/SearchPager.tsx @@ -1,32 +1,25 @@ -import { Pagination } from '@mui/material'; +import { Pagination, PaginationProps } from '@mui/material'; import useMyAPI from 'components/hooks/useMyAPI'; +import { SearchResult } from 'components/models/ui/search'; import SimpleSearchQuery from 'components/visual/SearchBar/simple-search-query'; import React from 'react'; const MAX_TRACKED_RECORDS = 10000; -type SearchResults = { - items: any[]; - offset: number; - rows: number; - total: number; -}; - -export interface SearchPagerProps { +interface Props extends PaginationProps { total: number; pageSize: number; index: string; method?: 'POST' | 'GET'; query: SimpleSearchQuery; - setResults: (data: SearchResults) => void; scrollToTop?: boolean; size?: 'small' | 'large' | null; - setSearching?: (value: boolean) => void | null; url?: string; - [propName: string]: any; + setSearching?: (value: boolean) => void | null; + setResults: (data: SearchResult) => void; } -const WrappedSearchPager: React.FC = ({ +const WrappedSearchPager: React.FC = ({ total, pageSize, index, @@ -37,7 +30,6 @@ const WrappedSearchPager: React.FC = ({ size = 'small', setSearching = null, url = null, - children, ...otherProps }) => { const { apiCall } = useMyAPI(); diff --git a/src/components/visual/SearchResult/alerts.tsx b/src/components/visual/SearchResult/alerts.tsx index 27b0fb1e6..323c205ad 100644 --- a/src/components/visual/SearchResult/alerts.tsx +++ b/src/components/visual/SearchResult/alerts.tsx @@ -2,6 +2,8 @@ import { AlertTitle, Skeleton, Tooltip } from '@mui/material'; import Paper from '@mui/material/Paper'; import TableContainer from '@mui/material/TableContainer'; import useALContext from 'components/hooks/useALContext'; +import type { AlertIndexed } from 'components/models/base/alert'; +import type { SearchResult } from 'components/models/ui/search'; import Classification from 'components/visual/Classification'; import Verdict from 'components/visual/Verdict'; import 'moment/locale/fr'; @@ -20,41 +22,12 @@ import { } from '../DivTable'; import InformativeAlert from '../InformativeAlert'; -export type AlertResult = { - al: { - av: string[]; - score: number; - }; - alert_id: string; - classification: string; - file: { - md5: string; - sha1: string; - sha256: string; - type: string; - }; - filtered: boolean; - id: string; - label: string[]; - owner: string; - priority: string; - reporting_ts: string; - status: string; - ts: string; - type: string; -}; - -type SearchResults = { - items: AlertResult[]; - total: number; -}; - -type AlertsTableProps = { - alertResults: SearchResults; +type Props = { + alertResults: SearchResult; allowSort?: boolean; }; -const WrappedAlertsTable: React.FC = ({ alertResults, allowSort = true }) => { +const WrappedAlertsTable: React.FC = ({ alertResults, allowSort = true }) => { const { t, i18n } = useTranslation(['search']); const { c12nDef } = useALContext(); diff --git a/src/components/visual/SearchResult/archives.tsx b/src/components/visual/SearchResult/archives.tsx index 8d697f644..613cd5544 100644 --- a/src/components/visual/SearchResult/archives.tsx +++ b/src/components/visual/SearchResult/archives.tsx @@ -6,8 +6,9 @@ import Paper from '@mui/material/Paper'; import TableContainer from '@mui/material/TableContainer'; import useAppUser from 'commons/components/app/hooks/useAppUser'; import useALContext from 'components/hooks/useALContext'; -import { CustomUser } from 'components/hooks/useMyUser'; -import { FileInfo } from 'components/routes/archive/detail'; +import type { FileIndexed, LabelCategories } from 'components/models/base/file'; +import type { SearchResult } from 'components/models/ui/search'; +import { CustomUser } from 'components/models/ui/user'; import Classification from 'components/visual/Classification'; import CustomChip from 'components/visual/CustomChip'; import { @@ -27,27 +28,20 @@ import { useTranslation } from 'react-i18next'; import Moment from 'react-moment'; import { Link } from 'react-router-dom'; -type SearchResults = { - items: FileInfo[]; - rows: number; - offset: number; - total: number; -}; - -type ArchivesTableProps = { - fileResults: SearchResults; - allowSort?: boolean; - setFileID?: (id: string) => void; - onLabelClick?: (event: React.MouseEvent, label: string) => void; -}; - const LABELS_COLOR_MAP = { info: 'default', technique: 'secondary', attribution: 'primary' +} as const; + +type Props = { + fileResults: SearchResult; + allowSort?: boolean; + setFileID?: (id: string) => void; + onLabelClick?: (event: React.MouseEvent, label: string) => void; }; -const WrappedArchivesTable: React.FC = ({ +const WrappedArchivesTable: React.FC = ({ fileResults, allowSort = true, setFileID = null, @@ -191,7 +185,7 @@ const WrappedArchivesTable: React.FC = ({ }; type LabelCellProps = { - label_categories?: FileInfo['label_categories']; + label_categories?: LabelCategories; onLabelClick?: (event: React.MouseEvent, label: string) => void; }; diff --git a/src/components/visual/SearchResult/badlist.tsx b/src/components/visual/SearchResult/badlist.tsx index 97103cf86..bca274af6 100644 --- a/src/components/visual/SearchResult/badlist.tsx +++ b/src/components/visual/SearchResult/badlist.tsx @@ -2,6 +2,8 @@ import { AlertTitle, Skeleton, Tooltip } from '@mui/material'; import Paper from '@mui/material/Paper'; import TableContainer from '@mui/material/TableContainer'; import useALContext from 'components/hooks/useALContext'; +import type { Badlist } from 'components/models/base/badlist'; +import type { SearchResult } from 'components/models/ui/search'; import Classification from 'components/visual/Classification'; import { maxLenStr } from 'helpers/utils'; import 'moment/locale/fr'; @@ -21,49 +23,13 @@ import { } from '../DivTable'; import InformativeAlert from '../InformativeAlert'; -export type Badlist = { - added: string; - classification: string; - enabled: boolean; - hashes: { - md5: string; - sha1: string; - sha256: string; - }; - file: { - name: string[]; - }; - id: string; - sources: { - name: string; - }[]; - signature: { - name: string; - }; - tag: { - type: string; - value: string; - }; - type: string; - updated: string; -}; - -type SearchResults = { - items: Badlist[]; - total: number; -}; - -type BadlistTableProps = { - badlistResults: SearchResults; +type Props = { + badlistResults: SearchResult; setBadlistID?: (id: string) => void; allowSort?: boolean; }; -const WrappedBadlistTable: React.FC = ({ - badlistResults, - setBadlistID = null, - allowSort = true -}) => { +const WrappedBadlistTable: React.FC = ({ badlistResults, setBadlistID = null, allowSort = true }) => { const { t, i18n } = useTranslation(['search']); const { c12nDef } = useALContext(); diff --git a/src/components/visual/SearchResult/errors.tsx b/src/components/visual/SearchResult/errors.tsx index 8402ccf4e..ac1cad330 100644 --- a/src/components/visual/SearchResult/errors.tsx +++ b/src/components/visual/SearchResult/errors.tsx @@ -4,6 +4,8 @@ import ReportProblemOutlinedIcon from '@mui/icons-material/ReportProblemOutlined import { AlertTitle, Skeleton, Tooltip, useTheme } from '@mui/material'; import Paper from '@mui/material/Paper'; import TableContainer from '@mui/material/TableContainer'; +import type { Error, ErrorType } from 'components/models/base/error'; +import type { SearchResult } from 'components/models/ui/search'; import { DivTable, DivTableBody, @@ -14,45 +16,23 @@ import { SortableHeaderCell } from 'components/visual/DivTable'; import 'moment/locale/fr'; -import React from 'react'; +import React, { ReactElement } from 'react'; import { useTranslation } from 'react-i18next'; import Moment from 'react-moment'; import { Link } from 'react-router-dom'; import InformativeAlert from '../InformativeAlert'; -export type ErrorResult = { - created: string; - id: string; - response: { - message: string; - service_debug_info: string; - service_name: string; - service_tool_version: string; - service_version: string; - status: string; - }; - sha256: string; - type: string; -}; - -type SearchResults = { - items: ErrorResult[]; - rows: number; - offset: number; - total: number; -}; - -type ErrorsTableProps = { - errorResults: SearchResults; +type Props = { + errorResults: SearchResult; setErrorKey?: (key: string) => void; allowSort?: boolean; }; -const WrappedErrorsTable: React.FC = ({ errorResults, setErrorKey = null, allowSort = true }) => { +const WrappedErrorsTable: React.FC = ({ errorResults, setErrorKey = null, allowSort = true }) => { const { t, i18n } = useTranslation(['adminErrorViewer']); const theme = useTheme(); - const errorMap = { + const errorMap: Record = { 'MAX DEPTH REACHED': , 'MAX RETRY REACHED': , EXCEPTION: , diff --git a/src/components/visual/SearchResult/files.tsx b/src/components/visual/SearchResult/files.tsx index 07c3c29aa..df5abd27d 100644 --- a/src/components/visual/SearchResult/files.tsx +++ b/src/components/visual/SearchResult/files.tsx @@ -3,6 +3,8 @@ import { AlertTitle, Skeleton, Tooltip } from '@mui/material'; import Paper from '@mui/material/Paper'; import TableContainer from '@mui/material/TableContainer'; import useALContext from 'components/hooks/useALContext'; +import type { FileIndexed } from 'components/models/base/file'; +import type { SearchResult } from 'components/models/ui/search'; import Classification from 'components/visual/Classification'; import 'moment/locale/fr'; import React from 'react'; @@ -20,34 +22,12 @@ import { } from '../DivTable'; import InformativeAlert from '../InformativeAlert'; -export type FileResult = { - classification: string; - entropy: number; - from_archive: boolean; - id: string; - md5: string; - seen: { - count: number; - first: string; - last: string; - }; - sha1: string; - sha256: string; - size: number; - type: string; -}; - -type SearchResults = { - items: FileResult[]; - total: number; -}; - -type FilesTableProps = { - fileResults: SearchResults; +type Props = { + fileResults: SearchResult; allowSort?: boolean; }; -const WrappedFilesTable: React.FC = ({ fileResults, allowSort = true }) => { +const WrappedFilesTable: React.FC = ({ fileResults, allowSort = true }) => { const { t, i18n } = useTranslation(['search']); const { c12nDef } = useALContext(); diff --git a/src/components/visual/SearchResult/heuristics.tsx b/src/components/visual/SearchResult/heuristics.tsx index 4ab46ad43..c0fd6485d 100644 --- a/src/components/visual/SearchResult/heuristics.tsx +++ b/src/components/visual/SearchResult/heuristics.tsx @@ -1,8 +1,9 @@ +import { AlertTitle, Skeleton } from '@mui/material'; import Paper from '@mui/material/Paper'; import TableContainer from '@mui/material/TableContainer'; -import { AlertTitle, Skeleton } from '@mui/material'; import useALContext from 'components/hooks/useALContext'; -import { Statistics } from 'components/routes/manage/heuristic_detail'; +import type { Heuristic } from 'components/models/base/heuristic'; +import type { SearchResult } from 'components/models/ui/search'; import Classification from 'components/visual/Classification'; import 'moment/locale/fr'; import React from 'react'; @@ -20,37 +21,13 @@ import { } from '../DivTable'; import InformativeAlert from '../InformativeAlert'; -export type HeuristicResult = { - attack_id: string[]; - classification: string; - description: string; - filetype: string; - heur_id: string; - id: string; - max_score: number; - name: string; - score: number; - stats: Statistics; -}; - -type SearchResults = { - items: HeuristicResult[]; - rows: number; - offset: number; - total: number; -}; - -type HeuristicsTableProps = { - heuristicResults: SearchResults; +type Props = { + heuristicResults: SearchResult; setHeuristicID?: (id: string) => void; allowSort?: boolean; }; -const WrappedHeuristicsTable: React.FC = ({ - heuristicResults, - setHeuristicID = null, - allowSort = true -}) => { +const WrappedHeuristicsTable: React.FC = ({ heuristicResults, setHeuristicID = null, allowSort = true }) => { const { t, i18n } = useTranslation(['search']); const { c12nDef } = useALContext(); diff --git a/src/components/visual/SearchResult/results.tsx b/src/components/visual/SearchResult/results.tsx index fe382f23a..766f84228 100644 --- a/src/components/visual/SearchResult/results.tsx +++ b/src/components/visual/SearchResult/results.tsx @@ -3,6 +3,8 @@ import { AlertTitle, Skeleton, Tooltip, useTheme } from '@mui/material'; import Paper from '@mui/material/Paper'; import TableContainer from '@mui/material/TableContainer'; import useALContext from 'components/hooks/useALContext'; +import type { ResultIndexed } from 'components/models/base/result'; +import type { SearchResult } from 'components/models/ui/search'; import Classification from 'components/visual/Classification'; import Verdict from 'components/visual/Verdict'; import 'moment/locale/fr'; @@ -21,34 +23,13 @@ import { } from '../DivTable'; import InformativeAlert from '../InformativeAlert'; -export type ResultResult = { - classification: string; - created: number; - drop_file: boolean; - from_archive: boolean; - id: string; - response: { - service_name: string; - service_tool_version: string; - }; - result: { - score: number; - }; - type: number; -}; - -type SearchResults = { - items: ResultResult[]; - total: number; -}; - -type ResultsTableProps = { - resultResults: SearchResults; - component?: any; +type Props = { + resultResults: SearchResult; + component?: React.ElementType; allowSort?: boolean; }; -const WrappedResultsTable: React.FC = ({ resultResults, component = Paper, allowSort = true }) => { +const WrappedResultsTable: React.FC = ({ resultResults, component = Paper, allowSort = true }) => { const { t, i18n } = useTranslation(['search']); const { c12nDef } = useALContext(); const theme = useTheme(); diff --git a/src/components/visual/SearchResult/safelist.tsx b/src/components/visual/SearchResult/safelist.tsx index ee608288b..497cf59f9 100644 --- a/src/components/visual/SearchResult/safelist.tsx +++ b/src/components/visual/SearchResult/safelist.tsx @@ -2,6 +2,8 @@ import { AlertTitle, Skeleton, Tooltip } from '@mui/material'; import Paper from '@mui/material/Paper'; import TableContainer from '@mui/material/TableContainer'; import useALContext from 'components/hooks/useALContext'; +import type { Safelist } from 'components/models/base/safelist'; +import type { SearchResult } from 'components/models/ui/search'; import Classification from 'components/visual/Classification'; import { maxLenStr } from 'helpers/utils'; import 'moment/locale/fr'; @@ -21,49 +23,13 @@ import { } from '../DivTable'; import InformativeAlert from '../InformativeAlert'; -export type Safelist = { - added: string; - classification: string; - enabled: boolean; - hashes: { - md5: string; - sha1: string; - sha256: string; - }; - file: { - name: string[]; - }; - id: string; - sources: { - name: string; - }[]; - signature: { - name: string; - }; - tag: { - type: string; - value: string; - }; - type: string; - updated: string; -}; - -type SearchResults = { - items: Safelist[]; - total: number; -}; - -type SafelistTableProps = { - safelistResults: SearchResults; +type Props = { + safelistResults: SearchResult; setSafelistID?: (id: string) => void; allowSort?: boolean; }; -const WrappedSafelistTable: React.FC = ({ - safelistResults, - setSafelistID = null, - allowSort = true -}) => { +const WrappedSafelistTable: React.FC = ({ safelistResults, setSafelistID = null, allowSort = true }) => { const { t, i18n } = useTranslation(['search']); const { c12nDef } = useALContext(); diff --git a/src/components/visual/SearchResult/service.tsx b/src/components/visual/SearchResult/service.tsx index 8af054815..c78308105 100644 --- a/src/components/visual/SearchResult/service.tsx +++ b/src/components/visual/SearchResult/service.tsx @@ -5,6 +5,7 @@ import { AlertTitle, IconButton, Skeleton, Tooltip, useTheme } from '@mui/materi import Paper from '@mui/material/Paper'; import TableContainer from '@mui/material/TableContainer'; import useALContext from 'components/hooks/useALContext'; +import type { ServiceIndexed, ServiceUpdateData, ServiceUpdates } from 'components/models/base/service'; import 'moment/locale/fr'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -15,40 +16,14 @@ import CustomChip from '../CustomChip'; import { DivTable, DivTableBody, DivTableCell, DivTableHead, DivTableRow, LinkRow } from '../DivTable'; import InformativeAlert from '../InformativeAlert'; -export type ServiceResult = { - accepts: string; - category: string; - classification: string; - description: string; - enabled: boolean; - is_external: boolean; - name: string; - privileged: boolean; - rejects: string; - stage: string; - version: string; -}; - -type UpdateData = { - auth: string; - image: string; - latest_tag: string; - update_available: boolean; - updating: boolean; -}; - -type Updates = { - [name: string]: UpdateData; -}; - -type ServiceTableProps = { - serviceResults: ServiceResult[]; - updates: Updates; +type Props = { + serviceResults: ServiceIndexed[]; + updates: ServiceUpdates; setService: (svc: string) => void; - onUpdate: (svc: string, updateData: UpdateData) => void; + onUpdate: (svc: string, updateData: ServiceUpdateData) => void; }; -const WrappedServiceTable: React.FC = ({ serviceResults, updates, setService, onUpdate }) => { +const WrappedServiceTable: React.FC = ({ serviceResults, updates, setService, onUpdate }) => { const { t } = useTranslation(['search']); const { c12nDef } = useALContext(); const theme = useTheme(); diff --git a/src/components/visual/SearchResult/signatures.tsx b/src/components/visual/SearchResult/signatures.tsx index ed3213aa6..7e5f41d5e 100644 --- a/src/components/visual/SearchResult/signatures.tsx +++ b/src/components/visual/SearchResult/signatures.tsx @@ -1,8 +1,9 @@ +import { AlertTitle, Skeleton } from '@mui/material'; import Paper from '@mui/material/Paper'; import TableContainer from '@mui/material/TableContainer'; -import { AlertTitle, Skeleton } from '@mui/material'; import useALContext from 'components/hooks/useALContext'; -import { Statistics } from 'components/routes/manage/signature_detail'; +import type { SignatureIndexed } from 'components/models/base/signature'; +import type { SearchResult } from 'components/models/ui/search'; import Classification from 'components/visual/Classification'; import 'moment/locale/fr'; import React from 'react'; @@ -21,35 +22,13 @@ import { import InformativeAlert from '../InformativeAlert'; import SignatureStatus from '../SignatureStatus'; -export type SignatureResult = { - classification: string; - id: string; - last_modified: string; - name: string; - revision: string; - signature_id: string; - source: string; - stats: Statistics; - status: 'DEPLOYED' | 'NOISY' | 'DISABLED'; - type: string; -}; - -type SearchResults = { - items: SignatureResult[]; - total: number; -}; - -type SignaturesTableProps = { - signatureResults: SearchResults; +type Props = { + signatureResults: SearchResult; setSignatureID?: (id: string) => void; allowSort?: boolean; }; -const WrappedSignaturesTable: React.FC = ({ - signatureResults, - setSignatureID = null, - allowSort = true -}) => { +const WrappedSignaturesTable: React.FC = ({ signatureResults, setSignatureID = null, allowSort = true }) => { const { t, i18n } = useTranslation(['search']); const { c12nDef } = useALContext(); diff --git a/src/components/visual/SearchResult/submissions.tsx b/src/components/visual/SearchResult/submissions.tsx index f48bcfa6b..285618945 100644 --- a/src/components/visual/SearchResult/submissions.tsx +++ b/src/components/visual/SearchResult/submissions.tsx @@ -3,6 +3,8 @@ import { AlertTitle, Skeleton, Tooltip } from '@mui/material'; import Paper from '@mui/material/Paper'; import TableContainer from '@mui/material/TableContainer'; import useALContext from 'components/hooks/useALContext'; +import type { SubmissionIndexed } from 'components/models/base/submission'; +import type { SearchResult } from 'components/models/ui/search'; import Classification from 'components/visual/Classification'; import SubmissionState from 'components/visual/SubmissionState'; import Verdict from 'components/visual/Verdict'; @@ -23,35 +25,12 @@ import { } from '../DivTable'; import InformativeAlert from '../InformativeAlert'; -export type SubmissionResult = { - classification: string; - error_count: number; - file_count: number; - from_archive: boolean; - id: string; - max_score: number; - params: { - description: string; - submitter: string; - }; - sid: string; - state: string; - times: { - submitted: string; - }; -}; - -type SearchResults = { - items: SubmissionResult[]; - total: number; -}; - -type SubmissionsTableProps = { - submissionResults: SearchResults; +type Props = { + submissionResults: SearchResult; allowSort?: boolean; }; -const WrappedSubmissionsTable: React.FC = ({ submissionResults, allowSort = true }) => { +const WrappedSubmissionsTable: React.FC = ({ submissionResults, allowSort = true }) => { const { t, i18n } = useTranslation(['search']); const { c12nDef } = useALContext(); diff --git a/src/components/visual/SearchResult/users.tsx b/src/components/visual/SearchResult/users.tsx index b79e8ad0b..778071a8b 100644 --- a/src/components/visual/SearchResult/users.tsx +++ b/src/components/visual/SearchResult/users.tsx @@ -4,6 +4,8 @@ import { AlertTitle, Skeleton } from '@mui/material'; import Paper from '@mui/material/Paper'; import TableContainer from '@mui/material/TableContainer'; import useALContext from 'components/hooks/useALContext'; +import { UserIndexed } from 'components/models/base/user'; +import type { SearchResult } from 'components/models/ui/search'; import Classification from 'components/visual/Classification'; import { DivTable, @@ -19,29 +21,11 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import InformativeAlert from '../InformativeAlert'; -export type UserResult = { - classification: string; - email: string; - groups: string[]; - id: string; - is_active: boolean; - name: string; - type: string[]; - uname: string; +type Props = { + userResults: SearchResult; }; -type SearchResults = { - items: UserResult[]; - rows: number; - offset: number; - total: number; -}; - -type UsersTableProps = { - userResults: SearchResults; -}; - -const WrappedUsersTable: React.FC = ({ userResults }) => { +const WrappedUsersTable: React.FC = ({ userResults }) => { const { t } = useTranslation(['adminUsers']); const { c12nDef } = useALContext(); diff --git a/src/components/visual/SearchResult/workflow.tsx b/src/components/visual/SearchResult/workflow.tsx index 53742fcb5..81d19d02a 100644 --- a/src/components/visual/SearchResult/workflow.tsx +++ b/src/components/visual/SearchResult/workflow.tsx @@ -2,6 +2,8 @@ import { AlertTitle, Skeleton } from '@mui/material'; import Paper from '@mui/material/Paper'; import TableContainer from '@mui/material/TableContainer'; import useALContext from 'components/hooks/useALContext'; +import { WorkflowIndexed } from 'components/models/base/workflow'; +import type { SearchResult } from 'components/models/ui/search'; import Classification from 'components/visual/Classification'; import 'moment/locale/fr'; import React from 'react'; @@ -19,35 +21,12 @@ import { } from '../DivTable'; import InformativeAlert from '../InformativeAlert'; -export type WorkflowResult = { - classification: string; - creation_date: string; - creator: string; - edited_by: string; - hit_count: string; - id: string; - last_edit: string; - last_seen: string; - name: string; - priority: string; - query: string; - status: string; - workflow_id: string; -}; - -type SearchResults = { - items: WorkflowResult[]; - rows: number; - offset: number; - total: number; -}; - -type WorkflowTableProps = { - workflowResults: SearchResults; +type Props = { + workflowResults: SearchResult; setWorkflowID?: (id: string) => void; }; -const WrappedWorflowTable: React.FC = ({ workflowResults, setWorkflowID = null }) => { +const WrappedWorflowTable: React.FC = ({ workflowResults, setWorkflowID = null }) => { const { t, i18n } = useTranslation(['search']); const { c12nDef } = useALContext(); diff --git a/src/components/visual/ServiceManagement/NewServiceTable.tsx b/src/components/visual/ServiceManagement/NewServiceTable.tsx index 8005c9aa5..988689926 100644 --- a/src/components/visual/ServiceManagement/NewServiceTable.tsx +++ b/src/components/visual/ServiceManagement/NewServiceTable.tsx @@ -1,25 +1,18 @@ -import { IconButton, Paper, TableContainer, TableRow, Tooltip } from '@mui/material'; -import makeStyles from '@mui/styles/makeStyles'; import CloudDownloadOutlinedIcon from '@mui/icons-material/CloudDownloadOutlined'; -import { AlertTitle, Skeleton } from '@mui/material'; +import { AlertTitle, IconButton, Paper, Skeleton, TableContainer, TableRow, Tooltip } from '@mui/material'; +import makeStyles from '@mui/styles/makeStyles'; import 'moment/locale/fr'; import React, { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; -import { JSONFeedItem } from '.'; import { DivTable, DivTableBody, DivTableCell, DivTableHead, DivTableRow } from '../DivTable'; import InformativeAlert from '../InformativeAlert'; +import { JSONFeedItem } from '../Notification/useNotificationFeed'; -export type ServiceResult = { - accepts: string; - category: string; - description: string; - enabled: boolean; - name: string; - privileged: boolean; - rejects: string; - stage: string; - version: string; -}; +const useStyles = makeStyles(() => ({ + center: { + textAlign: 'center' + } +})); type Props = { services: JSONFeedItem[]; @@ -27,12 +20,6 @@ type Props = { onInstall: (s: JSONFeedItem[]) => void; }; -const useStyles = makeStyles(() => ({ - center: { - textAlign: 'center' - } -})); - const WrappedNewServiceTable: React.FC = ({ services, installingServices, onInstall }: Props) => { const { t } = useTranslation(['search']); const classes = useStyles(); @@ -83,7 +70,8 @@ const WrappedNewServiceTable: React.FC = ({ services, installingServices, onInstall([service]); }} disabled={installingServices?.includes(service?.summary)} - size="large"> + size="large" + > diff --git a/src/components/visual/ServiceManagement/index.tsx b/src/components/visual/ServiceManagement/index.tsx deleted file mode 100644 index 39bdc9fd4..000000000 --- a/src/components/visual/ServiceManagement/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from './NewServiceTable'; -export * from './useNotificationFeed'; diff --git a/src/components/visual/ServiceManagement/useNotificationFeed.tsx b/src/components/visual/ServiceManagement/useNotificationFeed.tsx deleted file mode 100644 index a6365fb12..000000000 --- a/src/components/visual/ServiceManagement/useNotificationFeed.tsx +++ /dev/null @@ -1,239 +0,0 @@ -import { useCallback, useMemo } from 'react'; - -/** - * JSON Feed Version 1.1 - * https://www.jsonfeed.org/ - */ - -export type JSONFeedItemAttachment = { - url: string; - mime_type: string; - title?: string; - size_in_bytes?: number; - duration_in_seconds?: number; -}; - -export type JSONFeedAuthor = { - name?: string; - url?: string; - avatar?: string; -}; - -export type JSONFeedItem = { - id: string; - url?: string; - external_url?: string; - title?: string; - content_html?: string; - content_text?: string; - content_md?: string; - summary?: string; - image?: string; - banner_image?: string; - date_published?: Date; - date_modified?: Date; - authors?: Array; - tags?: Array; - language?: string; - attachments?: Array; - _isNew: boolean; -}; - -export type ServiceFeedItem = JSONFeedItem & { - _status?: 'available' | 'installing' | 'installed'; - _isInstalling?: boolean; -}; - -export type JSONFeed = { - version: string; - title: string; - home_page_url?: string; - feed_url?: string; - description?: string; - user_comment?: string; - next_url?: string; - icon?: string; - favicon?: string; - authors?: Array; - language?: string; - expired?: boolean; - hubs?: Array<{ type: string; url: string }>; - items: Array; -}; - -type FetchJSONProps = { - urls: Array; - lastTimeOpen?: Date; - onSuccess?: (feeds: Array) => void; - onError?: (err: any) => void; -}; - -export type UseNotificationFeedReturn = { - fetchJSONFeeds: (urls?: Array) => Promise; - fetchJSONNotifications: ({ urls, lastTimeOpen, onSuccess, onError }: FetchJSONProps) => void; -}; - -export const useNotificationFeed = (): UseNotificationFeedReturn => { - const DEFAULT_JSON_FEED_ITEM_ATTACHMENT = useMemo( - () => ({ - url: null, - mime_type: null, - title: null, - size_in_bytes: 0, - duration_in_seconds: 0 - }), - [] - ); - - const DEFAULT_JSON_FEED_AUTHOR = useMemo( - () => ({ - name: null, - url: null, - avatar: null - }), - [] - ); - - const DEFAULT_JSON_FEED_ITEM = useMemo( - () => ({ - id: null, - url: null, - external_url: null, - title: null, - content_html: null, - content_text: null, - summary: null, - image: null, - banner_image: null, - date_published: new Date(0), - date_modified: new Date(0), - authors: [], - tags: [], - language: null, - attachments: [], - _isNew: false - }), - [] - ); - - const DEFAULT_JSON_FEED = useMemo( - () => ({ - version: null, - title: null, - home_page_url: null, - feed_url: null, - description: null, - user_comment: null, - next_url: null, - icon: null, - favicon: null, - authors: [], - language: null, - expired: false, - hubs: [], - items: [] - }), - [] - ); - - const decodeHTML = useCallback((html: string) => { - if (!html) return ''; - var txt = document.createElement('textarea'); - txt.innerHTML = html; - return txt.value; - }, []); - - const parseJSONFeedItemAttachment = useCallback( - (attachments: Array): Array => - !attachments ? [] : attachments.map(attachment => ({ ...DEFAULT_JSON_FEED_ITEM_ATTACHMENT, ...attachment })), - [DEFAULT_JSON_FEED_ITEM_ATTACHMENT] - ); - - const parseJSONFeedAuthor = useCallback( - (authors: Array): Array => - !authors ? [] : authors.map(author => ({ ...DEFAULT_JSON_FEED_AUTHOR, ...author })), - [DEFAULT_JSON_FEED_AUTHOR] - ); - - const parseJSONFeedItem = useCallback( - (items: Array) => - !items - ? [] - : items.map(item => ({ - ...DEFAULT_JSON_FEED_ITEM, - ...item, - date_published: new Date(item.date_published), - date_modified: new Date(item.date_modified), - authors: parseJSONFeedAuthor(item?.authors), - attachments: parseJSONFeedItemAttachment(item?.attachment), - content_html: decodeHTML(item?.content_html) - })), - [DEFAULT_JSON_FEED_ITEM, decodeHTML, parseJSONFeedAuthor, parseJSONFeedItemAttachment] - ); - - const parseJSONFeed = useCallback( - (feed: any): JSONFeed => - !feed - ? null - : { - ...DEFAULT_JSON_FEED, - ...feed, - items: parseJSONFeedItem(feed?.items), - authors: parseJSONFeedAuthor(feed?.authors) - }, - [DEFAULT_JSON_FEED, parseJSONFeedAuthor, parseJSONFeedItem] - ); - - const fetchJSON = useCallback( - (url: string): Promise => - new Promise(async (resolve, reject) => { - const response: Response = (await fetch(url, { method: 'GET' }).catch(err => - // eslint-disable-next-line no-console - console.error(`Notification Area: error caused by URL "${err}`) - )) as Response; - - if (!response) { - resolve({ ...DEFAULT_JSON_FEED }); - return; - } - - const textResponse: string = await response.text(); - const jsonFeed = JSON.parse(textResponse); - resolve(parseJSONFeed(jsonFeed)); - return; - }), - [DEFAULT_JSON_FEED, parseJSONFeed] - ); - - const fetchJSONFeeds = useCallback( - (urls: Array = []): Promise => - new Promise(async (resolve, reject) => { - if (!urls) { - reject('no urls'); - return; - } - const feeds: JSONFeed[] = (await Promise.all(urls.map(url => fetchJSON(url))).catch(err => - reject(err) - )) as JSONFeed[]; - resolve(feeds); - }), - [fetchJSON] - ); - - const fetchJSONNotifications = useCallback( - ({ urls, lastTimeOpen = new Date(0), onSuccess = null, onError = null }: FetchJSONProps): void => { - fetchJSONFeeds(urls) - .then(feeds => { - const newNotifications = feeds - .flatMap(f => f.items) - .map((n: JSONFeedItem) => ({ ...n, _isNew: n.date_published.valueOf() > lastTimeOpen.valueOf() })) - .sort((a, b) => b.date_published.valueOf() - a.date_published.valueOf()); - onSuccess(newNotifications); - }) - .catch(err => onError && onError(err)); - }, - [fetchJSONFeeds] - ); - - return { fetchJSONFeeds, fetchJSONNotifications }; -}; diff --git a/src/components/visual/SignatureStatus.tsx b/src/components/visual/SignatureStatus.tsx index 50286c42c..882135fcf 100644 --- a/src/components/visual/SignatureStatus.tsx +++ b/src/components/visual/SignatureStatus.tsx @@ -1,27 +1,33 @@ +import { ChipProps } from '@mui/material'; +import type { RuleStatus } from 'components/models/base/signature'; import React from 'react'; import { useTranslation } from 'react-i18next'; import CustomChip from './CustomChip'; -const COLOR_MAP = { - DEPLOYED: 'success' as 'success', - NOISY: 'info' as 'info', - DISABLED: 'error' as 'error' -}; +type Color = 'success' | 'info' | 'error' | 'default' | 'primary' | 'secondary' | 'warning'; + +const COLOR_MAP: Record = { + DEPLOYED: 'success', + NOISY: 'info', + DISABLED: 'error', + STAGING: 'default', + TESTING: 'default', + INVALID: 'default' +} as const; -type SignatureStatusProps = { - status: 'DEPLOYED' | 'NOISY' | 'DISABLED'; +interface Props extends Omit { fullWidth?: boolean; size?: 'tiny' | 'small' | 'medium'; - variant?: 'outlined' | 'filled'; - onClick?: () => void; -}; + status: RuleStatus; +} -const WrappedSignatureStatus: React.FC = ({ +const WrappedSignatureStatus: React.FC = ({ status, fullWidth = true, onClick = null, size = 'small', - variant = 'filled' + variant = 'filled', + ...props }) => { const { t } = useTranslation(); @@ -33,6 +39,7 @@ const WrappedSignatureStatus: React.FC = ({ variant={variant} label={t(`signature.status.${status}`)} onClick={onClick} + {...props} /> ); }; diff --git a/src/components/visual/SubmissionState.tsx b/src/components/visual/SubmissionState.tsx index b3c0cb040..56ffcf39f 100644 --- a/src/components/visual/SubmissionState.tsx +++ b/src/components/visual/SubmissionState.tsx @@ -1,7 +1,7 @@ -import { Tooltip } from '@mui/material'; import ClearIcon from '@mui/icons-material/Clear'; import DoneIcon from '@mui/icons-material/Done'; import UpdateIcon from '@mui/icons-material/Update'; +import { Tooltip } from '@mui/material'; import React from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/src/components/visual/TabContainer.tsx b/src/components/visual/TabContainer.tsx index 55acb53d7..02947790b 100644 --- a/src/components/visual/TabContainer.tsx +++ b/src/components/visual/TabContainer.tsx @@ -2,7 +2,7 @@ import { Tab, TabProps, Tabs, TabsProps, useTheme } from '@mui/material'; import React, { FC, ReactElement, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from 'react'; interface TabElement extends TabProps { - content?: ReactNode; + inner?: ReactNode; } type TabElements = Record; @@ -111,8 +111,8 @@ const WrappedTabContainer = ({ - {Object.entries(tabs).map(([value, { label = '', disabled = false, content = null }], i) => - disabled ? null : + {Object.entries(tabs).map(([value, { label = '', disabled = false, inner = null }], i) => + disabled ? null : )} ); diff --git a/src/components/visual/Table/enhanced_table.tsx b/src/components/visual/Table/enhanced_table.tsx index c13566a36..f266629cc 100644 --- a/src/components/visual/Table/enhanced_table.tsx +++ b/src/components/visual/Table/enhanced_table.tsx @@ -20,6 +20,7 @@ import { Link } from 'react-router-dom'; import Classification from '../Classification'; import { DivTable, DivTableBody, DivTableCell, DivTableHead, DivTableRow, LinkRow } from '../DivTable'; +// TODO: what is this and is this used? const throttler = new Throttler(250); function descendingComparator(a: T, b: T, orderBy: keyof T) { @@ -100,7 +101,7 @@ const WrappedEnhancedTableHead: React.FC = ({ key={cell.id} className={!dense ? classes.comfortable : null} align={cell.numeric ? 'right' : 'left'} - padding={cell.disablePadding ? 'none' : 'default'} + padding={cell.disablePadding ? 'none' : 'normal'} sortDirection={orderBy === cell.id ? order : false} > = { info: 'default', safe: 'success', suspicious: 'warning', diff --git a/src/components/visual/Verdict.tsx b/src/components/visual/Verdict.tsx index 18b60ebf6..8c112b692 100644 --- a/src/components/visual/Verdict.tsx +++ b/src/components/visual/Verdict.tsx @@ -1,11 +1,12 @@ import { Tooltip, useTheme } from '@mui/material'; import useALContext from 'components/hooks/useALContext'; +import { Verdict as VerdictType } from 'components/models/base/alert'; import CustomChip from 'components/visual/CustomChip'; import React from 'react'; import { useTranslation } from 'react-i18next'; type VerdictProps = { - verdict?: 'info' | 'safe' | 'suspicious' | 'highly_suspicious' | 'malicious'; + verdict?: VerdictType; score?: number; short?: boolean; variant?: 'outlined' | 'filled'; diff --git a/src/components/visual/VerdictBar.tsx b/src/components/visual/VerdictBar.tsx index f4c056f0a..ba4e58ec9 100644 --- a/src/components/visual/VerdictBar.tsx +++ b/src/components/visual/VerdictBar.tsx @@ -1,16 +1,14 @@ import { Tooltip, useTheme } from '@mui/material'; +import { SubmissionVerdict } from 'components/models/base/alert'; import React from 'react'; import { useTranslation } from 'react-i18next'; -type VerdictBarProps = { - verdicts: { - malicious: string[]; - non_malicious: string[]; - }; +type Props = { + verdicts: SubmissionVerdict; width?: string; }; -const VerdictBar: React.FC = ({ verdicts, width = '100%' }) => { +const VerdictBar: React.FC = ({ verdicts, width = '100%' }) => { const { t } = useTranslation(); const theme = useTheme(); diff --git a/src/components/visual/VerdictGauge.tsx b/src/components/visual/VerdictGauge.tsx index 2076ad89b..9c2051971 100644 --- a/src/components/visual/VerdictGauge.tsx +++ b/src/components/visual/VerdictGauge.tsx @@ -1,5 +1,6 @@ import { Tooltip, useTheme } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; +import { SubmissionVerdict } from 'components/models/base/alert'; import { scaleLinear } from 'd3-scale'; import { arc } from 'd3-shape'; import React from 'react'; @@ -42,22 +43,14 @@ const useStyles = makeStyles(theme => ({ } })); -type VerdictGaugeProps = { - verdicts: { - malicious: any[]; - non_malicious: any[]; - }; +type Props = { + verdicts: SubmissionVerdict; max?: number; colorBack?: string; autoHide?: boolean; }; -const VerdictGauge: React.FC = ({ - verdicts, - max = 20, - colorBack = '#68686815', - autoHide = false -}) => { +const VerdictGauge: React.FC = ({ verdicts, max = 20, colorBack = '#68686815', autoHide = false }) => { const { t } = useTranslation(); const classes = useStyles(); const theme = useTheme(); diff --git a/src/components/visual/image_inline.tsx b/src/components/visual/image_inline.tsx index e5875d266..1bdecbc4d 100644 --- a/src/components/visual/image_inline.tsx +++ b/src/components/visual/image_inline.tsx @@ -4,7 +4,7 @@ import makeStyles from '@mui/styles/makeStyles'; import clsx from 'clsx'; import useCarousel from 'components/hooks/useCarousel'; import useMyAPI from 'components/hooks/useMyAPI'; -import { Image } from 'components/visual/Carousel/Container'; +import type { Image, ImageBody } from 'components/models/base/result_body'; import React, { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; @@ -72,41 +72,12 @@ const useStyles = makeStyles((theme: Theme) => ({ } })); -export type ImageBodyData = Array<{ - img: { - name: string; - description: string; - sha256: string; - }; - thumb: { - name: string; - description: string; - sha256: string; - }; -}>; - type ImageInlineBodyProps = { - body: ImageBodyData; - printable?: boolean; - small?: boolean; -}; - -type ImageInlineProps = { - data: Image[]; + body: ImageBody; printable?: boolean; small?: boolean; }; -type ImageItemProps = { - src: string; - alt: string; - index: number; - to?: string; - count?: number; - small?: boolean; - onImageClick: (event: React.MouseEvent, index: number) => void; -}; - const WrappedImageInlineBody = ({ body, printable = false, small = false }: ImageInlineBodyProps) => { const [data, setData] = useState([]); @@ -128,6 +99,12 @@ const WrappedImageInlineBody = ({ body, printable = false, small = false }: Imag return body && Array.isArray(body) ? : null; }; +type ImageInlineProps = { + data: Image[]; + printable?: boolean; + small?: boolean; +}; + const WrappedImageInline = ({ data, printable = false, small = false }: ImageInlineProps) => { const classes = useStyles(); const { openCarousel } = useCarousel(); @@ -147,6 +124,16 @@ const WrappedImageInline = ({ data, printable = false, small = false }: ImageInl ) : null; }; +type ImageItemProps = { + src: string; + alt: string; + index: number; + to?: string; + count?: number; + small?: boolean; + onImageClick: (event: React.MouseEvent, index: number) => void; +}; + const WrappedImageItem = ({ src, alt, @@ -156,11 +143,12 @@ const WrappedImageItem = ({ small = false, onImageClick = () => null }: ImageItemProps) => { - const [image, setImage] = useState(null); - const [loading, setLoading] = useState(true); const classes = useStyles(); const { apiCall } = useMyAPI(); + const [image, setImage] = useState(null); + const [loading, setLoading] = useState(true); + useEffect(() => { apiCall({ url: `/api/v4/file/image/${src}/`,