import { Observable } from "rxjs"; import { HttpHeaders } from '@angular/common/http'; import { RequestQueryParams } from '../services'; import { DebugElement } from '@angular/core'; import { Comparator, State, HttpOptionInterface, HttpOptionTextInterface, QuotaUnitInterface } from '../services'; import { QuotaUnits, StorageMultipleConstant } from '../entities/shared.const'; import { AbstractControl } from "@angular/forms"; import { isValidCron } from 'cron-validator'; /** * Api levels */ enum APILevels { V1 = '', V2 = '/v2.0' } /** * v1 base href */ export const V1_BASE_HREF = '/api' + APILevels.V1; /** * Current base href */ export const CURRENT_BASE_HREF = '/api' + APILevels.V2; /** * Convert the different async channels to the Promise type. * ** * template T * ** deprecated param {(Observable | Promise | T)} async * returns {Promise} */ export function toPromise(async: Observable | Promise | T): Promise { if (!async) { return Promise.reject("Bad argument"); } if (async instanceof Observable) { let obs: Observable = async; return obs.toPromise(); } else { return Promise.resolve(async); } } export const HTTP_JSON_OPTIONS: HttpOptionInterface = { headers: new HttpHeaders({ "Content-Type": 'application/json', "Accept": 'application/json' }), responseType: 'json' }; export const HTTP_GET_OPTIONS: HttpOptionInterface = { headers: new HttpHeaders({ "Content-Type": 'application/json', "Accept": 'application/json', "Cache-Control": 'no-cache', "Pragma": 'no-cache' }), responseType: 'json' }; export const HTTP_GET_OPTIONS_OBSERVE_RESPONSE: HttpOptionInterface = { headers: new HttpHeaders({ "Content-Type": 'application/json', "Accept": 'application/json', "Cache-Control": 'no-cache', "Pragma": 'no-cache' }), observe: 'response' as 'body', responseType: 'json' }; export const HTTP_GET_OPTIONS_TEXT: HttpOptionTextInterface = { headers: new HttpHeaders({ "Content-Type": 'application/json', "Accept": 'application/json', "Cache-Control": 'no-cache', "Pragma": 'no-cache' }), responseType: 'text' }; export const HTTP_FORM_OPTIONS: HttpOptionInterface = { headers: new HttpHeaders({ "Content-Type": 'application/x-www-form-urlencoded' }), responseType: 'json' }; export const HTTP_GET_HEADER: HttpHeaders = new HttpHeaders({ "Content-Type": 'application/json', "Accept": 'application/json', "Cache-Control": 'no-cache', "Pragma": 'no-cache' }); export const HTTP_GET_OPTIONS_CACHE: HttpOptionInterface = { headers: new HttpHeaders({ "Content-Type": 'application/json', "Accept": 'application/json', "Cache-Control": 'no-cache', "Pragma": 'no-cache', }), responseType: 'json' }; export const FILE_UPLOAD_OPTION: HttpOptionInterface = { headers: new HttpHeaders({ "Content-Type": 'multipart/form-data', }), responseType: 'json' }; /** * Build http request options * ** * ** deprecated param {RequestQueryParams} params * returns {RequestOptions} */ export function buildHttpRequestOptions(params: RequestQueryParams): HttpOptionInterface { let reqOptions: HttpOptionInterface = { headers: new HttpHeaders({ "Content-Type": 'application/json', "Accept": 'application/json', "Cache-Control": 'no-cache', "Pragma": 'no-cache' }), responseType: 'json', }; if (params) { reqOptions.params = params; } return reqOptions; } export function buildHttpRequestOptionsWithObserveResponse(params: RequestQueryParams): HttpOptionInterface { let reqOptions: HttpOptionInterface = { headers: new HttpHeaders({ "Content-Type": 'application/json', "Accept": 'application/json', "Cache-Control": 'no-cache', "Pragma": 'no-cache' }), responseType: 'json', observe: 'response' as 'body' }; if (params) { reqOptions.params = params; } return reqOptions; } /** Button events to pass to `DebugElement.triggerEventHandler` for RouterLink event handler */ export const ButtonClickEvents = { left: { button: 0 }, right: { button: 2 } }; /** Simulate element click. Defaults to mouse left-button click event. */ export function click(el: DebugElement | HTMLElement, eventObj: any = ButtonClickEvents.left): void { if (el instanceof HTMLElement) { el.click(); } else { el.triggerEventHandler('click', eventObj); } } /** * Comparator for fields with specific type. * */ export class CustomComparator implements Comparator { fieldName: string; type: string; constructor(fieldName: string, type: string) { this.fieldName = fieldName; this.type = type; } compare(a: { [key: string]: any | any[] }, b: { [key: string]: any | any[] }) { let comp = 0; if (a && b) { let fieldA, fieldB; for (let key of Object.keys(a)) { if (key === this.fieldName) { fieldA = a[key]; fieldB = b[key]; break; } else if (typeof a[key] === 'object') { let insideObject = a[key]; for (let insideKey in insideObject) { if (insideKey === this.fieldName) { fieldA = insideObject[insideKey]; fieldB = b[key][insideKey]; break; } } } } switch (this.type) { case "number": comp = fieldB - fieldA; break; case "date": comp = new Date(fieldB).getTime() - new Date(fieldA).getTime(); break; case "string": comp = fieldB.localeCompare(fieldA); break; } } return comp; } } /** * The default page size */ export const DEFAULT_PAGE_SIZE: number = 15; /** * The default supported mime type */ export const DEFAULT_SUPPORTED_MIME_TYPES = "application/vnd.security.vulnerability.report; version=1.1, application/vnd.scanner.adapter.vuln.report.harbor+json; version=1.0"; /** * the property name of vulnerability database updated time */ export const DATABASE_UPDATED_PROPERTY = "harbor.scanner-adapter/vulnerability-database-updated-at"; export const DATABASE_NEXT_UPDATE_PROPERTY = "harbor.scanner-adapter/vulnerability-database-next-update-at"; /** * The state of vulnerability scanning */ export const VULNERABILITY_SCAN_STATUS = { // front-end status NOT_SCANNED: "Not Scanned", // back-end status PENDING: "Pending", RUNNING: "Running", ERROR: "Error", STOPPED: "Stopped", SUCCESS: "Success", SCHEDULED: "Scheduled" }; /** * The severity of vulnerability scanning */ export const VULNERABILITY_SEVERITY = { NEGLIGIBLE: "Negligible", UNKNOWN: "Unknown", LOW: "Low", MEDIUM: "Medium", HIGH: "High", CRITICAL: "Critical", NONE: "None" }; /** * The level of vulnerability severity for comparing */ export const SEVERITY_LEVEL_MAP = { "Critical": 6, "High": 5, "Medium": 4, "Low": 3, "Negligible": 2, "Unknown": 1, "None": 0 }; /** * Calculate page number by state */ export function calculatePage(state: State): number { if (!state || !state.page) { return 1; } let pageNumber = Math.ceil((state.page.to + 1) / state.page.size); if (pageNumber === 0) { return 1; } else { return pageNumber; } } /** * Filter columns via RegExp * ** * ** deprecated param {State} state * returns {void} */ export function doFiltering(items: T[], state: State): T[] { if (!items || items.length === 0) { return items; } if (!state || !state.filters || state.filters.length === 0) { return items; } state.filters.forEach((filter: { property: string; value: string; }) => { items = items.filter(item => { if (filter['property'].indexOf('.') !== -1) { let arr = filter['property'].split('.'); if (Array.isArray(item[arr[0]]) && item[arr[0]].length) { return item[arr[0]].some((data: any) => { return filter['value'] === data[arr[1]]; }); } } else { return regexpFilter(filter['value'], item[filter['property']]); } }); }); return items; } /** * Match items via RegExp * ** * ** deprecated param {string} terms * ** deprecated param {*} testedValue * returns {boolean} */ export function regexpFilter(terms: string, testedValue: any): boolean { let reg = new RegExp('.*' + terms + '.*', 'i'); return reg.test(testedValue); } /** * Sorting the data by column * ** * template T * ** deprecated param {T[]} items * ** deprecated param {State} state * returns {T[]} */ export function doSorting(items: T[], state: State): T[] { if (!items || items.length === 0) { return items; } if (!state || !state.sort) { return items; } return items.sort((a: T, b: T) => { let comp: number = 0; if (typeof state.sort.by !== "string") { comp = state.sort.by.compare(a, b); } else { let propA = a[state.sort.by.toString()], propB = b[state.sort.by.toString()]; if (typeof propA === "string") { comp = propA.localeCompare(propB); } else { if (propA > propB) { comp = 1; } else if (propA < propB) { comp = -1; } } } if (state.sort.reverse) { comp = -comp; } return comp; }); } /** * Compare the two objects to adjust if they're equal * ** * ** deprecated param {*} a * ** deprecated param {*} b * returns {boolean} */ export function compareValue(a: any, b: any): boolean { if ((a && !b) || (!a && b)) { return false; } if (!a && !b) { return true; } return JSON.stringify(a) === JSON.stringify(b); } /** * Check if the object is null or empty '{}' * ** * ** deprecated param {*} obj * returns {boolean} */ export function isEmptyObject(obj: any): boolean { return !obj || JSON.stringify(obj) === "{}"; } /** * Deeper clone all * ** * ** deprecated param {*} srcObj * returns {*} */ export function clone(srcObj: any): any { if (!srcObj) { return null; } return JSON.parse(JSON.stringify(srcObj)); } export function isEmpty(obj: any): boolean { return !obj || JSON.stringify(obj) === '{}'; } export function downloadFile(fileData) { let url = window.URL.createObjectURL(fileData.data); let a = document.createElement("a"); document.body.appendChild(a); a.setAttribute("style", "display: none"); a.href = url; a.download = fileData.filename; a.click(); window.URL.revokeObjectURL(url); a.remove(); } export function getChanges(original: any, afterChange: any): { [key: string]: any | any[] } { let changes: { [key: string]: any | any[] } = {}; if (!afterChange || !original) { return changes; } for (let prop of Object.keys(afterChange)) { let field = original[prop]; if (field && field.editable) { if (!compareValue(field.value, afterChange[prop].value)) { changes[prop] = afterChange[prop].value; // Number if (typeof field.value === 'number') { changes[prop] = +changes[prop]; } // Trim string value if (typeof field.value === "string") { changes[prop] = ('' + changes[prop]).trim(); } } } } return changes; } /** * validate cron expressions * @param testValue */ export function cronRegex(testValue: any): boolean { // must have 6 fields if (testValue && testValue.trim().split(/\s+/g).length < 6) { return false; } return isValidCron(testValue, {seconds: true, alias: true, allowBlankDay: true}); } /** * Keep decimal digits * @param count number * @param decimals number 1、2、3 ··· */ export const roundDecimals = (count, decimals = 0) => { return Number(`${Math.round(+`${count}e${decimals}`)}e-${decimals}`); }; /** * get suitable unit * @param count number ;bit * @param quotaUnitsDeep Array link QuotaUnits; */ export const getSuitableUnit = (count: number, quotaUnitsDeep: QuotaUnitInterface[]): string => { for (let unitObj of quotaUnitsDeep) { if (count / StorageMultipleConstant >= 1 && quotaUnitsDeep.length > 1) { quotaUnitsDeep.shift(); return getSuitableUnit(count / StorageMultipleConstant, quotaUnitsDeep); } else { return +count ? `${roundDecimals(count, 2)}${unitObj.UNIT}` : `0${unitObj.UNIT}`; } } return `${roundDecimals(count, 2)}${QuotaUnits[0].UNIT}`; }; /** * get byte from GB、MB、TB * @param count number * @param unit MB /GB / TB */ export const getByte = (count: number, unit: string): number => { let flagIndex; return QuotaUnits.reduce((totalValue, currentValue, index) => { if (currentValue.UNIT === unit) { flagIndex = index; return totalValue; } else { if (!flagIndex) { return totalValue * StorageMultipleConstant; } return totalValue; } }, count); }; /** * get integet and unit in hard storage and used storage;and the unit of used storage <= the unit of hard storage * @param hardNumber hard storage number * @param quotaUnitsDeep clone(Quotas) * @param usedNumber used storage number * @param quotaUnitsDeepClone clone(Quotas) */ export const GetIntegerAndUnit = (hardNumber: number, quotaUnitsDeep: QuotaUnitInterface[] , usedNumber: number, quotaUnitsDeepClone: QuotaUnitInterface[]) => { for (let unitObj of quotaUnitsDeep) { if (hardNumber % StorageMultipleConstant === 0 && quotaUnitsDeep.length > 1) { quotaUnitsDeep.shift(); if (usedNumber / StorageMultipleConstant >= 1) { quotaUnitsDeepClone.shift(); return GetIntegerAndUnit(hardNumber / StorageMultipleConstant , quotaUnitsDeep, usedNumber / StorageMultipleConstant, quotaUnitsDeepClone); } else { return GetIntegerAndUnit(hardNumber / StorageMultipleConstant, quotaUnitsDeep, usedNumber, quotaUnitsDeepClone); } } else { return { partNumberHard: +hardNumber, partCharacterHard: unitObj.UNIT, partNumberUsed: roundDecimals(+usedNumber, 2), partCharacterUsed: quotaUnitsDeepClone[0].UNIT }; } } }; export const validateLimit = unitContrl => { return (control: AbstractControl) => { if ( // 1024TB getByte(control.value, unitContrl.value) > Math.pow(StorageMultipleConstant, 5) ) { return { error: true }; } return null; }; }; export function formatSize(tagSize: string): string { let size: number = Number.parseInt(tagSize); if (Math.pow(1024, 1) <= size && size < Math.pow(1024, 2)) { return (size / Math.pow(1024, 1)).toFixed(2) + "KB"; } else if (Math.pow(1024, 2) <= size && size < Math.pow(1024, 3)) { return (size / Math.pow(1024, 2)).toFixed(2) + "MB"; } else if (Math.pow(1024, 3) <= size && size < Math.pow(1024, 4)) { return (size / Math.pow(1024, 3)).toFixed(2) + "GB"; } else { return size + "B"; } } /** * Simple object check. * @param item * @returns {boolean} */ export function isObject(item): boolean { return (item && typeof item === 'object' && !Array.isArray(item)); } /** * Deep merge two objects. * @param target * @param ...sources */ export function mergeDeep(target, ...sources) { if (!sources.length) { return target; } const source = sources.shift(); if (isObject(target) && isObject(source)) { for (const key in source) { if (isObject(source[key])) { if (!target[key]) { Object.assign(target, { [key]: {} }); } mergeDeep(target[key], source[key]); } else { Object.assign(target, { [key]: source[key] }); } } } return mergeDeep(target, ...sources); } export function dbEncodeURIComponent(url: string) { if (typeof url === "string") { return encodeURIComponent(encodeURIComponent(url)); } return ""; } /** * delete empty key * @param obj */ export function deleteEmptyKey(obj: Object): void { if (isEmptyObject(obj)) { return; } for ( let key in obj ) { if ( !obj[key] ) { delete obj[key]; } } }