();\\n const NOTIFICATION_HUB = '/notificationhub';\\n\\n const onAddScenario = () => {\\n setNewScenario({\\n fullName: `scenario-${uniqueId()}`,\\n data: `{\\\"name\\\":\\\"My Scenario JSON\\\",\\\"startTime\\\":\\\"${new Date().toISOString()}\\\",\\\"vessel\\\":{\\\"vesselName\\\":\\\"MSC Pamela\\\"},\\\"mooring\\\":{\\\"berthName\\\":\\\"VIG Berth 2\\\"}}`,\\n dateTime: new Date().toISOString(),\\n permissions: [\\n {\\n principals: ['Administrators', 'Editors', 'Users'],\\n operation: 'read',\\n },\\n {\\n principals: ['Administrators', 'Editors'],\\n operation: 'update',\\n },\\n {\\n principals: ['Administrators'],\\n operation: 'delete',\\n },\\n ],\\n });\\n };\\n\\n const queryDates = {\\n from: addDays(new Date(), -3).toISOString(),\\n to: addDays(new Date(), 1).toISOString(),\\n };\\n\\n const actionButton = {\\n name: 'Operational View',\\n color: '#00A4EC',\\n handleActionButton: () => console.log('Action Button Clicked'),\\n };\\n\\n return (\\n \\n \\n \\n Scenarios JSON\\n \\n \\n \\n {({ token }) => (\\n <>\\n \\n }\\n onClick={onAddScenario}\\n >\\n Add new scenario\\n \\n \\n \\n console.log('Scenario menu item clicked', {\\n menuItem,\\n scenario,\\n })\\n }\\n onScenarioSelected={(scenario: Scenario) => {\\n console.log('Scenario selected', scenario);\\n }}\\n onScenarioReceived={(scenario: Scenario) => {\\n console.log('Full Scenario received', scenario);\\n }}\\n onScenariosReceived={(scenarios: Scenario[]) => {\\n console.log('Received new scenarios!', scenarios);\\n }}\\n showDate\\n showHour\\n showMenu\\n showStatus\\n status={STATUS}\\n highlightNameOnStatus=\\\"Error\\\"\\n addScenario={newScenario}\\n translations={TRANSLATIONS}\\n debug\\n />\\n >\\n )}\\n \\n
\\n );\\n};\\n\", \"locationsMap\": { \"scenarios-story\": { \"startLoc\": { \"col\": 30, \"line\": 18 }, \"endLoc\": { \"col\": 1, \"line\": 247 }, \"startBody\": { \"col\": 30, \"line\": 18 }, \"endBody\": { \"col\": 1, \"line\": 247 } }, \"scenarios-json-story\": { \"startLoc\": { \"col\": 34, \"line\": 249 }, \"endLoc\": { \"col\": 1, \"line\": 399 }, \"startBody\": { \"col\": 34, \"line\": 249 }, \"endBody\": { \"col\": 1, \"line\": 399 } } } }, },\r\n title: 'Scenarios Components',\r\n component: ScenariosOLD,\r\n};\r\nexport const ScenariosStory = () => {\r\n const [newScenario, setNewScenario] = useState();\r\n const onAddScenario = () => {\r\n setNewScenario({\r\n data: `{\"testProperty\":false,\"name\":\"My Scenario test\",\"startTime\":\"${new Date().toISOString()}\",\"vessel\":{\"vesselName\":\"MSC Pamela\"},\"mooring\":{\"berthName\":\"VIG Berth 2\"}}`,\r\n });\r\n };\r\n const queryDates = {\r\n from: addDays(new Date(), -1).toISOString(),\r\n to: addDays(new Date(), 1).toISOString(),\r\n };\r\n return (\n \n \n Scenarios\n \n \n \n {({ token }) => (<>\n \n } onClick={onAddScenario}>\n Add new scenario\n \n \n console.log('Scenario menu item clicked', {\r\n menuItem,\r\n scenario,\r\n })} onScenarioSelected={(scenario) => {\r\n console.log('Scenario selected', scenario);\r\n }} onScenarioReceived={(scenario) => {\r\n console.log('Full Scenario received', scenario);\r\n }} onScenariosReceived={(scenarios) => {\r\n console.log('Received new scenarios!', scenarios);\r\n }} showDate showHour showMenu showStatus status={[\r\n {\r\n name: 'Pending',\r\n color: 'orange',\r\n message: 'Pending',\r\n },\r\n {\r\n name: 'Starting',\r\n color: 'orange',\r\n message: 'Starting',\r\n },\r\n {\r\n name: 'Cancel',\r\n color: 'grey',\r\n message: 'Cancel',\r\n },\r\n {\r\n name: 'Cancelling',\r\n color: 'orange',\r\n message: 'Cancelling',\r\n },\r\n {\r\n name: 'Cancelled',\r\n color: 'grey',\r\n message: 'Cancelled',\r\n },\r\n {\r\n name: 'InProgress',\r\n color: 'orange',\r\n message: 'Running',\r\n },\r\n {\r\n name: 'Unknown',\r\n color: 'black',\r\n message: 'Unknown',\r\n },\r\n {\r\n name: 'ReadyToInitiate',\r\n color: 'red',\r\n message: 'Ready',\r\n },\r\n {\r\n name: 'Completed',\r\n color: 'green',\r\n message: 'Completed',\r\n },\r\n {\r\n name: 'Error',\r\n color: 'black',\r\n message: 'Error',\r\n },\r\n {\r\n name: 'Default',\r\n color: 'black',\r\n message: 'Unknown',\r\n },\r\n ]} addScenario={newScenario} translations={{\r\n executeConfirmation: 'Ini akan memulai pekerjaan baru di latar belakang. Status akan berubah setelah penyelesaian pekerjaan. Anda yakin ingin mengeksekusi',\r\n terminateConfirmation: 'Ini akan membatalkan pekerjaan yang sedang dieksekusi. Status akan berubah setelah pembatalan pekerjaan. Anda yakin ingin mengakhiri',\r\n cloneConfirmation: 'Ini akan memulai pekerjaan baru di latar belakang. Anda dapat menghapus skenario kloning ini nanti. Anda yakin ingin mengkloning',\r\n deleteConfirmation: 'Ini akan menghapus skenario yang dipilih dari daftar. Setelah dihapus, Anda tidak dapat mengambil data. Anda yakin ingin menghapus',\r\n cancelLabel: 'Batal',\r\n confirmLabel: 'Lanjut',\r\n }}/>\n >)}\n \n
);\r\n};;;\r\n;\r\nexport const ScenariosJSONStory = () => {\r\n const [newScenario, setNewScenario] = useState();\r\n const NOTIFICATION_HUB = '/notificationhub';\r\n const onAddScenario = () => {\r\n setNewScenario({\r\n fullName: `scenario-${uniqueId()}`,\r\n data: `{\"name\":\"My Scenario JSON\",\"startTime\":\"${new Date().toISOString()}\",\"vessel\":{\"vesselName\":\"MSC Pamela\"},\"mooring\":{\"berthName\":\"VIG Berth 2\"}}`,\r\n dateTime: new Date().toISOString(),\r\n permissions: [\r\n {\r\n principals: ['Administrators', 'Editors', 'Users'],\r\n operation: 'read',\r\n },\r\n {\r\n principals: ['Administrators', 'Editors'],\r\n operation: 'update',\r\n },\r\n {\r\n principals: ['Administrators'],\r\n operation: 'delete',\r\n },\r\n ],\r\n });\r\n };\r\n const queryDates = {\r\n from: addDays(new Date(), -3).toISOString(),\r\n to: addDays(new Date(), 1).toISOString(),\r\n };\r\n const actionButton = {\r\n name: 'Operational View',\r\n color: '#00A4EC',\r\n handleActionButton: () => console.log('Action Button Clicked'),\r\n };\r\n return (\n \n \n Scenarios JSON\n \n \n \n {({ token }) => (<>\n \n } onClick={onAddScenario}>\n Add new scenario\n \n \n console.log('Scenario menu item clicked', {\r\n menuItem,\r\n scenario,\r\n })} onScenarioSelected={(scenario) => {\r\n console.log('Scenario selected', scenario);\r\n }} onScenarioReceived={(scenario) => {\r\n console.log('Full Scenario received', scenario);\r\n }} onScenariosReceived={(scenarios) => {\r\n console.log('Received new scenarios!', scenarios);\r\n }} showDate showHour showMenu showStatus status={STATUS} highlightNameOnStatus=\"Error\" addScenario={newScenario} translations={TRANSLATIONS} debug/>\n >)}\n \n
);\r\n};\r\n\n\n\nScenariosStory.parameters = { storySource: { source: \"() => {\\r\\n const [newScenario, setNewScenario] = useState();\\r\\n const onAddScenario = () => {\\r\\n setNewScenario({\\r\\n data: `{\\\"testProperty\\\":false,\\\"name\\\":\\\"My Scenario test\\\",\\\"startTime\\\":\\\"${new Date().toISOString()}\\\",\\\"vessel\\\":{\\\"vesselName\\\":\\\"MSC Pamela\\\"},\\\"mooring\\\":{\\\"berthName\\\":\\\"VIG Berth 2\\\"}}`,\\r\\n });\\r\\n };\\r\\n const queryDates = {\\r\\n from: addDays(new Date(), -1).toISOString(),\\r\\n to: addDays(new Date(), 1).toISOString(),\\r\\n };\\r\\n return (\\n \\n \\n Scenarios\\n \\n \\n \\n {({ token }) => (<>\\n \\n } onClick={onAddScenario}>\\n Add new scenario\\n \\n \\n console.log('Scenario menu item clicked', {\\r\\n menuItem,\\r\\n scenario,\\r\\n })} onScenarioSelected={(scenario) => {\\r\\n console.log('Scenario selected', scenario);\\r\\n }} onScenarioReceived={(scenario) => {\\r\\n console.log('Full Scenario received', scenario);\\r\\n }} onScenariosReceived={(scenarios) => {\\r\\n console.log('Received new scenarios!', scenarios);\\r\\n }} showDate showHour showMenu showStatus status={[\\r\\n {\\r\\n name: 'Pending',\\r\\n color: 'orange',\\r\\n message: 'Pending',\\r\\n },\\r\\n {\\r\\n name: 'Starting',\\r\\n color: 'orange',\\r\\n message: 'Starting',\\r\\n },\\r\\n {\\r\\n name: 'Cancel',\\r\\n color: 'grey',\\r\\n message: 'Cancel',\\r\\n },\\r\\n {\\r\\n name: 'Cancelling',\\r\\n color: 'orange',\\r\\n message: 'Cancelling',\\r\\n },\\r\\n {\\r\\n name: 'Cancelled',\\r\\n color: 'grey',\\r\\n message: 'Cancelled',\\r\\n },\\r\\n {\\r\\n name: 'InProgress',\\r\\n color: 'orange',\\r\\n message: 'Running',\\r\\n },\\r\\n {\\r\\n name: 'Unknown',\\r\\n color: 'black',\\r\\n message: 'Unknown',\\r\\n },\\r\\n {\\r\\n name: 'ReadyToInitiate',\\r\\n color: 'red',\\r\\n message: 'Ready',\\r\\n },\\r\\n {\\r\\n name: 'Completed',\\r\\n color: 'green',\\r\\n message: 'Completed',\\r\\n },\\r\\n {\\r\\n name: 'Error',\\r\\n color: 'black',\\r\\n message: 'Error',\\r\\n },\\r\\n {\\r\\n name: 'Default',\\r\\n color: 'black',\\r\\n message: 'Unknown',\\r\\n },\\r\\n ]} addScenario={newScenario} translations={{\\r\\n executeConfirmation: 'Ini akan memulai pekerjaan baru di latar belakang. Status akan berubah setelah penyelesaian pekerjaan. Anda yakin ingin mengeksekusi',\\r\\n terminateConfirmation: 'Ini akan membatalkan pekerjaan yang sedang dieksekusi. Status akan berubah setelah pembatalan pekerjaan. Anda yakin ingin mengakhiri',\\r\\n cloneConfirmation: 'Ini akan memulai pekerjaan baru di latar belakang. Anda dapat menghapus skenario kloning ini nanti. Anda yakin ingin mengkloning',\\r\\n deleteConfirmation: 'Ini akan menghapus skenario yang dipilih dari daftar. Setelah dihapus, Anda tidak dapat mengambil data. Anda yakin ingin menghapus',\\r\\n cancelLabel: 'Batal',\\r\\n confirmLabel: 'Lanjut',\\r\\n }}/>\\n >)}\\n \\n
);\\r\\n}\" }, ...ScenariosStory.parameters };\nScenariosJSONStory.parameters = { storySource: { source: \"() => {\\r\\n const [newScenario, setNewScenario] = useState();\\r\\n const NOTIFICATION_HUB = '/notificationhub';\\r\\n const onAddScenario = () => {\\r\\n setNewScenario({\\r\\n fullName: `scenario-${uniqueId()}`,\\r\\n data: `{\\\"name\\\":\\\"My Scenario JSON\\\",\\\"startTime\\\":\\\"${new Date().toISOString()}\\\",\\\"vessel\\\":{\\\"vesselName\\\":\\\"MSC Pamela\\\"},\\\"mooring\\\":{\\\"berthName\\\":\\\"VIG Berth 2\\\"}}`,\\r\\n dateTime: new Date().toISOString(),\\r\\n permissions: [\\r\\n {\\r\\n principals: ['Administrators', 'Editors', 'Users'],\\r\\n operation: 'read',\\r\\n },\\r\\n {\\r\\n principals: ['Administrators', 'Editors'],\\r\\n operation: 'update',\\r\\n },\\r\\n {\\r\\n principals: ['Administrators'],\\r\\n operation: 'delete',\\r\\n },\\r\\n ],\\r\\n });\\r\\n };\\r\\n const queryDates = {\\r\\n from: addDays(new Date(), -3).toISOString(),\\r\\n to: addDays(new Date(), 1).toISOString(),\\r\\n };\\r\\n const actionButton = {\\r\\n name: 'Operational View',\\r\\n color: '#00A4EC',\\r\\n handleActionButton: () => console.log('Action Button Clicked'),\\r\\n };\\r\\n return (\\n \\n \\n Scenarios JSON\\n \\n \\n \\n {({ token }) => (<>\\n \\n } onClick={onAddScenario}>\\n Add new scenario\\n \\n \\n console.log('Scenario menu item clicked', {\\r\\n menuItem,\\r\\n scenario,\\r\\n })} onScenarioSelected={(scenario) => {\\r\\n console.log('Scenario selected', scenario);\\r\\n }} onScenarioReceived={(scenario) => {\\r\\n console.log('Full Scenario received', scenario);\\r\\n }} onScenariosReceived={(scenarios) => {\\r\\n console.log('Received new scenarios!', scenarios);\\r\\n }} showDate showHour showMenu showStatus status={STATUS} highlightNameOnStatus=\\\"Error\\\" addScenario={newScenario} translations={TRANSLATIONS} debug/>\\n >)}\\n \\n
);\\r\\n}\" }, ...ScenariosJSONStory.parameters };","import * as React from 'react';\nimport { Login, LoginProps } from './Login/Login';\nimport { Token, User } from './types';\n\nexport type LoginState = { user?: User; token?: Token };\n\n/** See ../Accounts/Accounts.stories.tsx for example usage */\nexport const LoginGate = (\n props: LoginProps & {\n children(login: Required): React.ReactNode;\n },\n) => {\n const [state, setState] = React.useState({});\n\n if (!state.token?.accessToken?.token)\n return (\n {\n setState({ user, token });\n props.onSuccess && props.onSuccess(user, token);\n }}\n />\n );\n\n return <>{props.children(state as Required)}>;\n};\n","import { fetchAccount, fetchToken, resetPassword, updatePassword } from '../api';\nimport { Form, OtpInfo, Token, User } from './types';\n\nexport default class AuthService {\n host: string | string[];\n\n constructor(host: string | string[]) {\n this.host = host;\n }\n\n login = (\n form: Form,\n onOtpRequired: (otpInfo: OtpInfo) => void,\n onSuccess: (user: User, token: Token) => void,\n onError: (err: string) => void,\n ) => {\n if (Array.isArray(this.host)) {\n let firstResponse;\n const accessTokenList = [];\n\n this.host.forEach((host, index) => {\n fetchToken(host, {\n id: form.id,\n password: form.password,\n otp: form.otp,\n otpAuthenticator: form.otpAuthenticator,\n }).subscribe(\n (response) => {\n if ((response as OtpInfo).otpRequired && !form.otp) {\n onOtpRequired(response as OtpInfo);\n } else {\n fetchAccount(host, response.accessToken.token, 'me').subscribe(\n (user) => {\n const loggedInUser: User = {\n ...user,\n roles: user.roles ? user.roles.split(',').map((role: string) => role.trim()) : [],\n metadata: user.metadata ? user.metadata : {},\n };\n\n if (index === 0) {\n this.setSession(response, loggedInUser, form.rememberMe);\n firstResponse = response;\n\n if (onSuccess != null) {\n onSuccess(loggedInUser, response);\n }\n } else {\n accessTokenList.push({ ...response, host });\n this.setSession(firstResponse, loggedInUser, form.rememberMe, accessTokenList);\n }\n },\n (err) => {\n if (onError != null) {\n onError(err);\n }\n },\n );\n }\n },\n (error) => onError(error),\n );\n });\n } else {\n fetchToken(this.host, {\n id: form.id,\n password: form.password,\n otp: form.otp,\n otpAuthenticator: form.otpAuthenticator,\n }).subscribe(\n (response) => {\n if ((response as OtpInfo).otpRequired && !form.otp) {\n onOtpRequired(response as OtpInfo);\n } else {\n fetchAccount(this.host as string, response.accessToken.token, 'me').subscribe(\n (user) => {\n const loggedInUser: User = {\n ...user,\n roles: user.roles ? user.roles.split(',').map((role: string) => role.trim()) : [],\n metadata: user.metadata ? user.metadata : {},\n };\n\n this.setSession(response, loggedInUser, form.rememberMe);\n\n if (onSuccess != null) {\n onSuccess(loggedInUser, response);\n }\n },\n (err) => {\n if (onError != null) {\n onError(err);\n }\n },\n );\n }\n },\n (error) => onError(error),\n );\n }\n };\n\n requestResetPassword = (\n mailBody: string,\n emailAddress: string,\n onSuccess: () => void,\n onError: (err: string) => void,\n ) => {\n return resetPassword(this.host as string, mailBody, emailAddress).subscribe(\n (response) => {\n if (onSuccess != null) {\n onSuccess();\n }\n },\n (error) => onError(error),\n );\n };\n\n confirmResetPassword = (\n token: string,\n newPassword: string,\n onSuccess: () => void,\n onError: (err: string) => void,\n ) => {\n return updatePassword(this.host as string, token, newPassword).subscribe(\n (response) => {\n if (onSuccess != null) {\n onSuccess();\n }\n },\n (error) => onError(error),\n );\n };\n\n // Get user details in local storage\n getSession = () => {\n const storage = localStorage.getItem('accessToken') != null ? localStorage : sessionStorage;\n const userStorage = storage.getItem('user');\n const accessTokenList = storage.getItem('accessTokenList');\n\n return {\n accessToken: storage.getItem('accessToken'),\n refreshToken: storage.getItem('refreshToken'),\n user: userStorage ? JSON.parse(userStorage) : null,\n expiration: storage.getItem('expiration'),\n accessTokenList: accessTokenList ? JSON.parse(accessTokenList) : null,\n };\n };\n\n // Sets user details in localStorage\n setSession = (authResult: Token, user: User, useLocalStorage: boolean, list?: any) => {\n // Set the time that the access token will expire at\n // const expiresAt = JSON.stringify(authResult.expiresIn * 1000 + new Date().getTime());\n const storage = useLocalStorage ? localStorage : sessionStorage;\n\n storage.setItem('accessToken', authResult.accessToken.token);\n\n storage.setItem('refreshToken', authResult.refreshToken.token);\n\n storage.setItem('user', JSON.stringify(user));\n\n storage.setItem('expiration', authResult.accessToken.expiration);\n\n if (list) {\n storage.setItem('accessTokenList', JSON.stringify(list));\n }\n };\n\n // checks if the user is authenticated\n isAuthenticated = (): boolean => {\n const storage = localStorage.getItem('accessToken') != null ? localStorage : sessionStorage;\n const expirationStorage = storage.getItem('expiration');\n const accessToken = storage.getItem('accessToken');\n\n if (expirationStorage && accessToken) {\n // Check whether the current time is past the\n // access token's expiry time (in UTC, but once it goes into new Date() it will be converted to local time)\n const expiresAt = new Date(expirationStorage);\n const now = new Date();\n // Get and check against UTC date\n // const nowUtc = new Date(now.getTime() + now.getTimezoneOffset() * 60000);\n\n return now < expiresAt;\n }\n\n return false;\n };\n\n // removes user details from localStorage\n logout = () => {\n const storage = localStorage.getItem('accessToken') != null ? localStorage : sessionStorage;\n // Clear access token and ID token from local storage\n\n storage.removeItem('accessToken');\n storage.removeItem('refreshToken');\n storage.removeItem('user');\n storage.removeItem('expiration');\n storage.removeItem('accessTokenList');\n };\n}\n","import { createTheme } from '@material-ui/core/styles';\n\nconst DHITheme = createTheme({\n palette: {\n primary: {\n main: '#0B4566', // DHI Primary color\n },\n secondary: {\n main: '#3d6079', // Automatically generated secondary color\n },\n },\n});\n\nexport default DHITheme;\n","import { addHours, differenceInMinutes, parseISO } from 'date-fns';\nimport { format, toDate, utcToZonedTime } from 'date-fns-tz';\nimport jp from 'jsonpath';\nimport { isArray } from 'lodash';\nimport { Condition, DescriptionField, Scenario, Status } from '../Scenarios/types';\nimport ReportProblemOutlinedIcon from '@material-ui/icons/ReportProblemOutlined';\nimport { ErrorRounded } from '@material-ui/icons';\n\nconst dataObjectToArray = (data: { [x: string]: any }) => {\n return Object.keys(data).map((key) => ({\n id: key,\n data: data[key],\n }));\n};\n\nconst getObjectProperty = (objectItem: any, property: string): any => {\n if (!objectItem) return null;\n\n const value = jp.query(objectItem, property);\n\n return value.length > 0 ? value[0] : null;\n};\n\nconst setObjectProperty = (objectItem: any, property: string, newValue: any) => {\n // Use jsonpath to apply in a deep path approach\n jp.apply(objectItem, `$.${property}`, () => newValue);\n};\n\nconst trimRecursive = (obj) => {\n for (const k in obj) {\n if (typeof obj[k] === 'string') obj[k] = obj[k].trim();\n else if (typeof obj[k] === 'object' && !(obj[k] instanceof Array)) trimRecursive(obj[k]);\n }\n\n return obj;\n};\n\nconst getDescriptions = (\n scenarioData: Scenario,\n descriptionFields: DescriptionField[] | undefined,\n timeZone: string | undefined,\n) => {\n const descriptions: { name: string; value: any }[] = [];\n\n if (!descriptionFields) {\n return descriptions;\n }\n\n for (const field of descriptionFields) {\n const value = getObjectProperty(scenarioData, field.field);\n\n // if (!value) {\n // console.warn(`Could not find field reference: ${field.field}!`);\n // continue;\n // }\n\n // Check if valid conditions met\n if (!field.condition || checkCondition(scenarioData, field.condition)) {\n // Formatting\n let formattedValue = value;\n\n // Formatting if date/time type\n if (value && field.dataType === 'dateTime') {\n let date: Date = parseISO(value);\n\n if (timeZone) {\n date = utcToTz(value, timeZone);\n }\n\n formattedValue = format(date, field.format ? field.format : 'yyyy-MM-dd HH:mm:ss');\n }\n\n descriptions.push({\n name: field.name,\n value: formattedValue,\n });\n }\n }\n\n return descriptions;\n};\n\n/**\n * Check if the property passed in the condition object is in the Scenario object\n * @param scenarioData Scenario Data\n * @param condition A object with a condition\n * @returns true or false\n */\nconst checkCondition = (scenarioData: Scenario, condition: Condition) => {\n let conditions: string[] = [];\n let isInverse = false;\n\n if (condition) {\n if (condition!.field.indexOf('!') === 0) {\n isInverse = true;\n }\n\n // If we have a value, check that it matches\n // If we didn't specify a value, just want to check if this field has data or not\n if (condition.value) {\n if (isArray(condition.value)) {\n conditions = [...condition.value];\n } else {\n conditions = [condition.value!];\n }\n\n return conditions.indexOf(getObjectProperty(scenarioData, condition!.field.replace('!', ''))) >= 0 === !isInverse;\n } else {\n return (getObjectProperty(scenarioData, condition!.field.replace('!', '')) != null) === !isInverse;\n }\n } else {\n return true;\n }\n};\n\n/**\n * Check if any of the listed properties are in the Scenario object\n * @param scenarioData Scenario Data\n * @param conditions Array of conditions\n * @returns true or false\n */\nconst checkConditions = (scenarioData: Scenario, conditions: Condition[]) => {\n let conditionsValue: string[] = [];\n let isInverse = false;\n const check = [];\n\n conditions.forEach((condition) => {\n if (condition) {\n if (condition!.field.indexOf('!') === 0) {\n isInverse = true;\n }\n\n const dtoProperty = getObjectProperty(scenarioData, condition!.field.replace('!', ''));\n\n // If we have a value, check that it matches\n // If we didn't specify a value, just want to check if this field has data or not\n if (condition.value) {\n if (isArray(condition.value)) {\n conditionsValue = [...condition.value];\n } else {\n conditionsValue = [condition.value!];\n }\n\n // * If checking field exists in the model, compare as normal *\n // * If checking field does NOT exist in the model, skip unless a boolean check equalling `false` *\n // This ensures we have backwards-compatibility where newly added DTO properties may not exist in older scenarioData's\n // but if it DOES exist, 'false' matches are explicitly checked.\n const values = conditionsValue\n .filter((val) => dtoProperty !== null || val !== 'false')\n .map((val) => (val === 'true' || val === 'false' ? val === 'true' : val));\n\n if (values.length > 0) {\n check.push(values.indexOf(dtoProperty) >= 0 === !isInverse);\n }\n } else {\n check.push((dtoProperty != null) === !isInverse);\n }\n } else {\n check.push(true);\n }\n });\n\n const finalCheck = check.filter((item) => item === false);\n\n return !(finalCheck.length > 0);\n};\n\nconst changeObjectProperty = (objectItem: any, property: string, intent: any) => {\n const properties = property.split('.');\n let value = objectItem;\n const body = [value];\n\n for (let i = 0; i < properties.length; i++) {\n value = value[properties[i]];\n body.push(value);\n }\n\n body[properties.length] = intent;\n\n for (let j = properties.length; j > 0; j--) {\n body[j - 1][properties[j - 1]] = body[j];\n }\n\n return body[0];\n};\n\nconst checkStatus = (scenario: Scenario, status: Status[], scenarioOLD?: boolean) => {\n let scenarioStatus;\n let progress;\n\n if (scenarioOLD) {\n scenarioStatus = getObjectProperty(scenario, 'lastJobStatus');\n progress = Number(getObjectProperty(scenario, 'lastJobProgress'));\n } else {\n scenarioStatus = getObjectProperty(scenario, 'status');\n progress = Number(getObjectProperty(scenario, 'progress'));\n }\n\n const currentStatus = {\n ...status.find((s) => s.name === scenarioStatus),\n progress: scenarioStatus === 'InProgress' ? progress : 0,\n };\n\n let result;\n\n if (!scenarioStatus) {\n result = {\n color: '#FD3F75',\n name: 'Error',\n message: 'Unknown Status',\n Icon: ErrorRounded,\n } as Status;\n } else {\n result = currentStatus;\n }\n\n return result;\n};\n\n/**\n * This converts the date provided to a specific IANA time zone\n * @param date The UTC date to convert. No time zone provided\n * @param timeZone The time zone to convert it to\n */\nconst utcToTz = (date: string, timeZone: string) => utcToZonedTime(`${date}Z`, timeZone);\n\nconst queryProp = (query: any) => {\n return typeof query === 'undefined' ? '' : query;\n};\n\nconst uniqueId = () => `${format(new Date(), 'yyyyMMddhhmmss')}-${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`;\n\nconst s4 = () =>\n Math.floor((1 + Math.random()) * 0x10000)\n .toString(16)\n .substring(1);\n\nexport const passwordStrength = (password?: string) => {\n let score = 0;\n\n if (!password) {\n return 0;\n }\n\n // Length 4 or less\n if (password.length < 5) {\n score += 3;\n // Length between 5 and 7\n } else if (password.length < 8) {\n score += 6;\n // Length between 8 and 15\n } else if (password.length < 16) {\n score += 12;\n // Length 16 or more\n } else {\n score += 18;\n }\n\n // At least one lower case letter\n if (password.match(/[a-z]/)) {\n score += 3;\n }\n\n // At least one upper case letter\n if (password.match(/[A-Z]/)) {\n score += 6;\n }\n\n // At least one number\n if (password.match(/\\d+/)) {\n score += 6;\n }\n\n // At least three numbers\n if (password.match(/(.*[0-9].*[0-9].*[0-9])/)) {\n score += 6;\n }\n\n // At least one special character\n if (password.match(/.[!,@,#,$,%,^,&,*,?,_,~]/)) {\n score += 6;\n }\n\n // Aat least two special characters\n if (password.match(/(.*[!,@,#,$,%,^,&,*,?,_,~].*[!,@,#,$,%,^,&,*,?,_,~])/)) {\n score += 8;\n }\n\n // Combinations both upper and lower case\n if (password.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/)) {\n score += 5;\n }\n\n // Both letters and numbers\n if (password.match(/([a-zA-Z])/) && password.match(/([0-9])/)) {\n score += 5;\n }\n\n // Letters, numbers, and special characters\n if (password.match(/([a-zA-Z0-9].*[!,@,#,$,%,^,&,*,?,_,~])|([!,@,#,$,%,^,&,*,?,_,~].*[a-zA-Z0-9])/)) {\n score += 5;\n }\n\n if (score < 16) {\n return 0;\n }\n\n if (score < 25) {\n return 1;\n }\n\n if (score < 35) {\n return 2;\n }\n\n if (score < 45) {\n return 3;\n }\n\n if (score > 45) {\n return 4;\n }\n\n return 0;\n};\n\nconst calcTimeDifference = (beginDate: string, endDate: string) => {\n const difference = differenceInMinutes(new Date(endDate), new Date(beginDate));\n const hour = Math.floor(difference / 60);\n const minute = Math.floor(difference - hour * 60);\n\n if (isNaN(difference)) {\n return '';\n }\n\n if (hour === 0 && minute === 0) {\n return '<1m';\n } else if (hour !== 0) {\n return `${hour}h ${minute}m`;\n } else {\n return `${minute}m`;\n }\n};\n\n/**\n * Convert and format data to from UTC to Zoned Time\n * @param date Date and Time\n * @param timeZone 'Australia/Brisbane'\n * @param dateTimeFormat 'Date time format. 'yyyy-MM-dd HH:mm:ss'\n */\nconst zonedTimeFromUTC = (date, timeZone, dateTimeFormat) => {\n date = `${date.replace('Z', '')}Z`;\n\n return format(utcToZonedTime(date, timeZone), dateTimeFormat);\n};\n\n/**\n * This the current utc time possibly offset by a number of hours. The returned string is without time zone\n * @param offsetHours An optional number of hours to offset the time\n */\nconst utcNow = (offsetHours?: number | null) =>\n format(addHours(utcToZonedTime(new Date(), 'UTC'), offsetHours == null ? 0 : offsetHours), 'yyyy-MM-dd HH:mm:ss');\n\n/**\n * This converts the date provided in a specific IANA time zone to UTC\n * @param date The date in a time zone to convert. No time zone provided\n * @param timeZone The time zone its in\n */\nconst tzToUtc = (date: string | Date, timeZone: string) =>\n date\n ? format(utcToZonedTime(toDate(date, { timeZone }), 'UTC'), 'yyyy-MM-dd HH:mm:ss')\n : format(utcToZonedTime(toDate(utcNow(), { timeZone }), 'UTC'), 'yyyy-MM-dd HH:mm:ss');\n\nconst toISOLocal = (d: Date) => {\n const z = (n) => `0${n}`.slice(-2);\n const zz = (n) => `00${n}`.slice(-3);\n let off = d.getTimezoneOffset();\n off = Math.abs(off);\n\n return `${d.getFullYear()}-${z(d.getMonth() + 1)}-${z(d.getDate())}T${z(d.getHours())}:${z(d.getMinutes())}:${z(\n d.getSeconds(),\n )}.${zz(d.getMilliseconds())}`;\n};\n\n/**\n * A recursive function that will add a children property into the object when it matches the current obj.value\n * @param obj Object where the recursive function will look for its property\n * @param name Name of the value to match the current loop\n * @param children object that will be added\n */\nconst recursive = (obj: any, name: string, children: any) => {\n if (obj.value === name && !obj.fetched) {\n obj.children = children;\n obj.fetched = true;\n }\n\n Array.isArray(obj.children) && obj.children.map((obj) => recursive(obj, name, children));\n};\n\nexport {\n dataObjectToArray,\n getObjectProperty,\n setObjectProperty,\n getDescriptions,\n trimRecursive,\n changeObjectProperty,\n checkCondition,\n checkConditions,\n checkStatus,\n utcToTz,\n queryProp,\n uniqueId,\n calcTimeDifference,\n zonedTimeFromUTC,\n tzToUtc,\n toISOLocal,\n recursive,\n};\n","import ReactEcharts from 'echarts-for-react';\nimport React, { useEffect, useState } from 'react';\nimport { StandardChartProps } from './types';\n\nconst BaseChart = (props: StandardChartProps) => {\n const { className, options, chartHeightFunc, getRefFunc, debug } = props;\n const [chartSize, setChartSize] = useState({ width: '100%', height: chartHeightFunc() });\n\n useEffect(() => {\n const handleResize = () => setChartSize({ width: '100%', height: chartHeightFunc() });\n\n window.addEventListener('resize', handleResize);\n\n return () => {\n window.removeEventListener('resize', handleResize);\n };\n });\n\n useEffect(() => {\n setChartSize({ width: '100%', height: chartHeightFunc() });\n }, [chartHeightFunc]);\n\n if (debug) {\n console.log('chart config', { data: options, raw: JSON.stringify(options) });\n }\n\n return (\n options?.series && (\n \n {\n if (getRefFunc) getRefFunc(e);\n }}\n style={{ width: chartSize.width, height: chartSize.height }}\n option={options}\n {...props}\n />\n
\n )\n );\n};\n\nexport { BaseChart, StandardChartProps };\n","import { from, of, throwError } from 'rxjs';\nimport { catchError, flatMap, map, tap } from 'rxjs/operators';\nimport { Options } from '../api/types';\n\nconst DEFAULT_OPTIONS = {\n headers: {\n 'Content-Type': 'application/json',\n },\n};\n\nconst checkStatus = (response: Response) => {\n if (response.status === 204 || response.status === 202) {\n return of(response);\n }\n\n return response.text().then((text) => {\n return text ? JSON.parse(text) : {};\n });\n};\n\nconst fetchUrl = (endPoint: RequestInfo, options?: Options) => {\n const mergedOptions = {\n ...DEFAULT_OPTIONS,\n ...options,\n headers: {\n ...DEFAULT_OPTIONS.headers,\n ...options?.additionalHeaders,\n },\n };\n\n return from(fetch(endPoint, mergedOptions as any)).pipe(\n // tap((response) => console.log(`Response status: ${response.status}`)),\n map((response) => {\n if (response.status >= 400) {\n throw new Error(`Error: ${response.status}, reason: ${response.statusText}`);\n } else {\n return response;\n }\n }),\n flatMap((response) => checkStatus(response)),\n catchError((error) => throwError(error)),\n );\n};\n\nexport { DEFAULT_OPTIONS, fetchUrl };\n","import {\n EditingState,\n FilteringState,\n IntegratedFiltering,\n IntegratedSorting,\n SortingState,\n} from '@devexpress/dx-react-grid';\nimport {\n ColumnChooser,\n Grid,\n TableColumnVisibility,\n TableEditColumn,\n TableFilterRow,\n TableHeaderRow,\n Toolbar,\n VirtualTable,\n} from '@devexpress/dx-react-grid-material-ui';\nimport Paper from '@material-ui/core/Paper';\nimport React, { useEffect, useState } from 'react';\nimport { createAccount, deleteAccount, fetchAccounts, fetchUserGroups, updateAccount } from '../api';\nimport {\n Command,\n DefaultColumnsTypeProvider,\n DeleteDialog,\n FilterCellRow,\n filterRules,\n MetadataTypeProvider,\n Popup,\n PopupEditing,\n UsersTypeProvider,\n} from '../common/Table';\nimport { UserGroupProps, UserGroupsData } from '../UserGroups/types';\n\nconst DEFAULT_COLUMNS = [\n {\n title: 'Username',\n name: 'id',\n },\n {\n title: 'Name',\n name: 'name',\n },\n {\n title: 'Email',\n name: 'email',\n },\n {\n title: 'User Groups',\n name: 'userGroups',\n },\n];\n\nconst Accounts: React.FC = ({ host, token, userGroupsDefaultSelected, metadata }) => {\n const [rows, setRows] = useState([]);\n const [userGroups, setUserGroups] = useState[]>([]);\n const [deletedDialog, setDeletedDialog] = useState(false);\n const [deleteRow, setDeleteRow] = useState({});\n const [filteringColumnExtensions, setFilteringColumnExtensions] = useState([]);\n const getRowId = (row) => row.id;\n\n const metadataHeader = metadata\n ? metadata.reduce(\n (acc, cur) => [\n ...acc,\n {\n title: cur.label,\n type: cur.type,\n name: cur.key,\n },\n ],\n [],\n )\n : [];\n\n const [columns] = useState(DEFAULT_COLUMNS.concat(metadataHeader));\n const metadataColumnsArray = metadata ? metadata.reduce((acc, cur) => [...acc, cur.key], []) : [];\n const [metadataColumns] = useState(metadataColumnsArray);\n const [usersColumn] = useState(['userGroups']);\n const [defaultColumnsNameArray] = useState(DEFAULT_COLUMNS.map((column) => column.name));\n\n const fetchData = () => {\n fetchAccounts(host, token).subscribe(\n async (body: Record) => {\n setRows(body as any);\n },\n (error) => {\n console.error('AU Error: ', error);\n },\n );\n\n fetchUserGroups(host, token).subscribe(async (body) => {\n const userGroups = body.map((ug) => ({\n id: ug.id,\n name: ug.name,\n }));\n setUserGroups(userGroups);\n });\n };\n\n const commitChanges = ({ added, changed, deleted }) => {\n let changedRows;\n\n if (added) {\n const startingAddedId = rows.length > 0 ? rows[rows.length - 1].id + 1 : 0;\n\n changedRows = [\n ...rows,\n ...added.map((row, index) => ({\n id: startingAddedId + index,\n ...row,\n })),\n ];\n }\n if (changed) {\n changedRows = rows.map((row) => (changed[row.id] ? { ...row, ...changed[row.id] } : row));\n }\n if (deleted) {\n setDeletedDialog(true);\n const deletedSet = new Set(deleted);\n const selectedRow = rows.filter((row) => deletedSet.has(row.id));\n setDeleteRow(selectedRow);\n\n // return the same rows and let the handleDelete deal with the data, otherwise it will be undefined and crash with no rows\n changedRows = rows;\n }\n\n setRows(changedRows);\n };\n\n const handleSubmit = (row, isNew = false) => {\n if (isNew) {\n return (\n createAccount(host, token, { ...row }).subscribe(() => {\n fetchData();\n }),\n (error) => {\n console.log('Create Account: ', error);\n }\n );\n } else {\n return (\n updateAccount(host, token, { ...row }).subscribe(() => {\n fetchData();\n }),\n (error) => {\n console.log('Update Accounts: ', error);\n }\n );\n }\n };\n\n const handleDelete = (row) => {\n deleteAccount(host, token, row.id).subscribe(\n () => {\n fetchData();\n setDeletedDialog(false);\n },\n (error) => console.log(error),\n );\n };\n\n useEffect(() => {\n fetchData();\n\n if (metadata) {\n setFilteringColumnExtensions(filterRules(metadata));\n }\n }, []);\n\n return (\n \n setDeletedDialog(false)}\n handleDelete={handleDelete}\n />\n \n \n \n\n \n \n\n \n \n\n \n {metadataColumns && }\n \n\n \n \n\n \n \n \n \n \n \n \n );\n};\n\nexport { UserGroupProps, Accounts };\n","import { makeStyles, Theme } from '@material-ui/core';\nimport { round, uniq } from 'lodash';\nimport React from 'react';\nimport { InterpolateIntensityColor } from './ColourScaleHelper';\nimport { ColourScaleProps } from './types';\n\nconst useStyles = makeStyles((theme: Theme) => ({}));\n\nconst ColourScale = ({\n type = 'intensity',\n mode = 'blocks',\n intervals = [],\n height = 24,\n markers = true,\n borderColour = 'transparent',\n}: ColourScaleProps) => {\n const classes = useStyles();\n let colours = [];\n\n if (type === 'intensity') {\n for (let i = 0; i < 100; i++) {\n const fillColor = InterpolateIntensityColor((i + 1) / 100);\n\n if (fillColor !== '#FFFFFF') {\n colours.push(fillColor);\n }\n }\n }\n\n // Only unique\n colours = uniq(colours);\n\n if (intervals.length === 0) {\n for (let i = 0; i < colours.length; i++) {\n intervals.push((i / colours.length) * 10);\n }\n }\n\n const intervalBreak = round(colours.length / intervals.length);\n let intervalIndex = 0;\n\n const markerStrip = markers && (\n \n {colours.map((c, index) => {\n if (index % intervalBreak === 0) {\n intervalIndex++;\n }\n\n return (\n
\n {index === colours.length || index % intervalBreak === 0 ? intervals[intervalIndex - 1] : null}\n
\n );\n })}\n
\n );\n\n if (mode === 'gradient') {\n return (\n \n );\n }\n if (mode === 'blocks')\n return (\n \n
\n {colours.map((c, index) => (\n
\n ))}\n
\n {markerStrip}\n
\n );\n\n return <>>;\n};\n\nexport { ColourScale, ColourScaleProps };\n","import { Button } from '@material-ui/core';\nimport { differenceInSeconds, format, parseISO } from 'date-fns';\nimport React from 'react';\nimport { TimeseriesData, TimeseriesExporterProps } from './types';\n\nconst TimeseriesExporter = (props: TimeseriesExporterProps) => {\n const exportTable = () => {\n const columns = [\n '',\n ...props.data.map(\n (timeseries, index) =>\n (props.timeseries && props.timeseries[index] && props.timeseries[index]?.name) || timeseries.id,\n ),\n ];\n\n let timesteps: string[] = [];\n\n props.data.forEach((timeseries) => {\n timesteps = [\n ...timesteps,\n ...timeseries.data.map((timestep: (string | number)[]) =>\n format(parseISO(timestep[0].toString()), \"yyyy-MM-dd'T'HH:mm:ss\"),\n ),\n ];\n });\n\n timesteps = timesteps.filter((value, index, self) => self.indexOf(value) === index);\n\n timesteps = timesteps.map((timestep) => format(parseISO(timestep), \"yyyy-MM-dd'T'HH:mm:ss\"));\n\n timesteps.sort((a, b) => differenceInSeconds(new Date(a), new Date(b)));\n\n let table: string[][] = [];\n\n timesteps.forEach((datetime: string) => {\n const cells = [format(parseISO(datetime), props.dateTimeFormat || 'yyyy-MM-dd HH:mm:ss')];\n const dateTimeLookup = format(parseISO(datetime), \"yyyy-MM-dd'T'HH:mm:ss\");\n\n props.data.forEach((timeseries: TimeseriesData, index: number) => {\n const found = timeseries.data.filter((timestep: (string | number)[]) => timestep[0] === dateTimeLookup);\n\n cells.push(\n found.length === 1\n ? props.timeseries && props.timeseries[index] && props.timeseries[index]?.decimals\n ? (found[0][1] as number).toFixed(props.timeseries[index]!.decimals!)\n : found[0][1].toString()\n : '',\n );\n });\n\n table = [...table, cells];\n });\n\n const csv = columns.join(', ') + table.map((row) => `\\r\\n${row.join(', ')}`);\n const link = document.createElement('a');\n\n link.setAttribute('href', encodeURI(`data:text/csv;charset=utf-8,${csv}`));\n\n link.setAttribute('download', !props.exportFileName ? 'Export.csv' : `${props.exportFileName}.csv`);\n\n document.body.appendChild(link);\n link.click();\n };\n\n return (\n \n \n
\n );\n};\n\nexport { TimeseriesExporterProps, TimeseriesData, TimeseriesExporter };\n","import {\n EditingState,\n FilteringState,\n IntegratedFiltering,\n IntegratedSorting,\n SortingState,\n} from '@devexpress/dx-react-grid';\nimport {\n ColumnChooser,\n Grid,\n TableColumnVisibility,\n TableEditColumn,\n TableFilterRow,\n TableHeaderRow,\n Toolbar,\n VirtualTable,\n} from '@devexpress/dx-react-grid-material-ui';\nimport Paper from '@material-ui/core/Paper';\nimport React, { useEffect, useState } from 'react';\nimport { createUserGroup, deleteUserGroup, fetchAccounts, fetchUserGroups, updateUserGroups } from '../api';\nimport {\n Command,\n DeleteDialog,\n FilterCellRow,\n filterRules,\n MetadataTypeProvider,\n Popup,\n PopupEditing,\n UsersTypeProvider,\n} from '../common/Table';\nimport { UserGroupProps, UserGroups, UserGroupsData } from './types';\n\nconst DEFAULT_COLUMNS = [\n {\n title: 'Name',\n name: 'name',\n },\n {\n title: 'Users',\n name: 'users',\n },\n];\n\nconst UserGroups: React.FC = ({ host, token, metadata }) => {\n const [rows, setRows] = useState([]);\n const [users, setUsers] = useState[]>([]);\n const [deletedDialog, setDeletedDialog] = useState(false);\n const [deleteRow, setDeleteRow] = useState({});\n const [filteringColumnExtensions, setFilteringColumnExtensions] = useState([]);\n const getRowId = (row) => row.id;\n\n const metadataHeader = metadata\n ? metadata.reduce(\n (acc, cur) => [\n ...acc,\n {\n title: cur.label,\n type: cur.type,\n name: cur.key,\n },\n ],\n [],\n )\n : [];\n\n const [columns] = useState(DEFAULT_COLUMNS.concat(metadataHeader));\n const metadataColumnsArray = metadata ? metadata.reduce((acc, cur) => [...acc, cur.key], []) : [];\n const [metadataColumns] = useState(metadataColumnsArray);\n const [usersColumn] = useState(['users']);\n\n const fetchData = () => {\n fetchUserGroups(host, token).subscribe(\n async (body: Record) => {\n const userGroups = body as UserGroups[];\n setRows(userGroups);\n },\n (error) => {\n console.error('UG Error: ', error);\n },\n );\n\n fetchAccounts(host, token).subscribe(\n async (body: Record) => {\n const usersOnly = body.map((item) => ({\n id: item.id,\n name: item.name,\n }));\n setUsers(usersOnly);\n },\n (error) => {\n console.error('UGU Error: ', error);\n },\n );\n };\n\n const commitChanges = ({ added, changed, deleted }) => {\n let changedRows;\n\n if (added) {\n const startingAddedId = rows.length > 0 ? rows[rows.length - 1].id + 1 : 0;\n\n changedRows = [\n ...rows,\n ...added.map((row, index) => ({\n id: startingAddedId + index,\n ...row,\n })),\n ];\n }\n if (changed) {\n changedRows = rows.map((row) => (changed[row.id] ? { ...row, ...changed[row.id] } : row));\n }\n if (deleted) {\n setDeletedDialog(true);\n const deletedSet = new Set(deleted);\n const selectedRow = rows.filter((row) => deletedSet.has(row.id));\n setDeleteRow(selectedRow);\n\n // return the same rows and let the handleDelete deal with the data, otherwise it will be undefined and crash with no rows\n changedRows = rows;\n }\n\n setRows(changedRows);\n };\n\n const handleSubmit = (row, isNew = false) => {\n if (isNew) {\n return (\n createUserGroup(host, token, {\n id: row.id,\n name: row.name,\n users: row.users,\n metadata: row.metadata,\n }).subscribe(() => {\n fetchData();\n }),\n (error) => {\n console.log('Create User Group: ', error);\n }\n );\n } else {\n return (\n updateUserGroups(host, token, {\n id: row.id,\n name: row.name,\n users: row.users,\n metadata: row.metadata,\n }).subscribe(() => {\n fetchData();\n }),\n (error) => {\n console.log('Update User Groups: ', error);\n }\n );\n }\n };\n\n const handleDelete = (row) => {\n deleteUserGroup(host, token, row.id).subscribe(\n () => {\n fetchData();\n setDeletedDialog(false);\n },\n (error) => console.log(error),\n );\n };\n\n useEffect(() => {\n fetchData();\n setFilteringColumnExtensions(filterRules(metadata));\n }, []);\n\n return (\n \n setDeletedDialog(false)}\n handleDelete={handleDelete}\n />\n \n \n \n\n \n \n\n \n \n\n \n \n\n \n \n\n \n \n \n \n \n \n \n );\n};\n\nexport { UserGroupProps, UserGroups };\n","import { Box, CircularProgress, Tooltip, Typography } from '@material-ui/core';\nimport { blue, green, red, yellow } from '@material-ui/core/colors';\nimport { Cancel, CancelScheduleSend, CheckCircle, Error, HelpOutline, HourglassEmpty } from '@material-ui/icons';\nimport React from 'react';\n\nconst StatusCell = ({ row }: { row: any }) => {\n const { status, progress } = row;\n\n switch (status) {\n case 'Completed':\n return (\n \n \n \n );\n case 'InProgress':\n return (\n \n \n \n \n {`${progress}%`}\n \n \n \n );\n case 'Pending':\n return (\n \n \n \n );\n case 'Error':\n return (\n \n \n \n );\n case 'Cancelled':\n return (\n \n \n \n );\n case 'Cancelling':\n case 'Cancel':\n return (\n \n \n \n );\n default:\n return (\n \n \n \n );\n }\n};\n\nexport default StatusCell;\n","import { VirtualTable } from '@devexpress/dx-react-grid-material-ui';\nimport { Tooltip, Typography, Zoom } from '@material-ui/core';\nimport { differenceInMinutes, format } from 'date-fns';\nimport React from 'react';\nimport StatusCell from './StatusCell';\n\nexport const GroupCellContent = (props: any) => (\n \n {props.row.value}\n \n);\n\nexport const Cell = (props: any) => {\n if (props.column.name === 'status') {\n return (\n \n \n | \n );\n }\n\n if (props.column.name === 'delay') {\n const { requested, started } = props.row;\n\n if (!requested && !started) {\n return | ;\n }\n\n const difference = differenceInMinutes(new Date(props.row.started), new Date(props.row.requested));\n const hour = Math.floor(difference / 60);\n const minute = Math.floor(difference - hour * 60);\n\n let delayColor = '';\n\n if (minute > 30) {\n delayColor = 'darkorange';\n } else if (minute > 60) {\n delayColor = ' red';\n }\n\n return (\n \n {props.value && (\n \n \n {props.value}\n \n \n )}\n | \n );\n }\n\n return ;\n};\n\nexport const dateGroupCriteria = (value) => {\n return { key: format(new Date(value), 'yyyy-MM-dd - HH:00') };\n};\n","import { makeStyles, Theme } from '@material-ui/core/styles';\n\nexport const JobDetailStyles = makeStyles((theme: Theme) => ({\n root: {\n flexGrow: 1,\n height: '100%',\n },\n paper: {\n display: 'flex',\n padding: '16px 0',\n textAlign: 'center',\n color: theme.palette.text.secondary,\n justifyContent: 'space-evenly',\n position: 'relative',\n width: '100%',\n alignItems: 'center',\n },\n item: {\n display: 'flex',\n flexDirection: 'column',\n fontFamily: theme.typography.fontFamily,\n fontSize: 12,\n },\n button: {\n backgroundColor: 'transparent',\n border: 'none',\n position: 'absolute',\n right: '0',\n cursor: 'pointer',\n },\n textarea: {\n width: '100%',\n minHeight: 400,\n overflow: 'scroll',\n whiteSpace: 'nowrap',\n height: 'calc(100% - 180px)',\n marginTop: theme.spacing(2),\n },\n}));\n\nexport const JobPanelStyles = (loadingDetail) =>\n makeStyles(() => ({\n wrapper: {\n display: 'flex',\n position: 'relative',\n overflow: loadingDetail ? 'hidden' : 'visible',\n },\n loadJobDetail: {\n display: 'flex',\n position: 'absolute',\n zIndex: 1200,\n width: '100%',\n justifyContent: 'center',\n alignItems: 'center',\n height: '100vh',\n },\n jobPanel: {\n zIndex: 999,\n padding: '1rem 2rem',\n background: '#FFF',\n top: 0,\n bottom: 0,\n right: 0,\n width: '50%',\n transition: 'all, .5s',\n position: loadingDetail ? 'absolute' : 'fixed',\n transform: loadingDetail ? 'translate3d(0, 0, 0)' : 'translate3d(100%, 0, 0)',\n boxShadow: loadingDetail ? '-20px 0px 19px -12px rgba(0,0,0,0.3)' : 'none',\n visibility: loadingDetail ? 'visible' : 'hidden',\n },\n }));\n","import { Grid, Paper, Typography } from '@material-ui/core';\nimport CancelOutlinedIcon from '@material-ui/icons/CancelOutlined';\nimport React, { useCallback, useEffect, useState } from 'react';\nimport { zonedTimeFromUTC } from '../../../utils/Utils';\nimport { JobDetailStyles } from '../styles';\nimport { JobDetailProps } from '../types';\n\nconst JobDetail = ({ detail, textareaScrolled, timeZone, dateTimeFormat, onClose }: JobDetailProps) => {\n const classes = JobDetailStyles();\n const [structuredLogs, setStructuredLogs] = useState('');\n const [scrollHeight, setScrollHeight] = useState(null);\n\n const displayBlock = (detail) => {\n return (\n \n {Object.entries(detail).map(([key, value], i) => {\n if (key !== 'taskId' && key !== 'id' && key !== 'logs' && value !== '') {\n return (\n \n {key}: {value}\n \n );\n } else {\n return null;\n }\n })}\n
\n );\n };\n\n const formatLog = () => {\n const log = detail?.logs\n ?.map(\n (item) => `${zonedTimeFromUTC(item.dateTime, timeZone, dateTimeFormat)} - [${item.logLevel}] - ${item.text} \\n`,\n )\n .join('');\n\n setStructuredLogs(log);\n };\n\n useEffect(() => {\n formatLog();\n }, [detail.logs]);\n\n const textareaInputRef = useCallback(\n (node) => {\n if (node !== null) {\n if (textareaScrolled) {\n setScrollHeight(node.scrollHeight);\n node.scrollTop = scrollHeight;\n } else {\n node.scrollTop = 0;\n }\n }\n },\n [textareaScrolled, scrollHeight],\n );\n\n return (\n \n
\n \n Timezone: {timeZone}\n Job Detail: {detail.taskId}\n id: {detail.id}\n \n \n \n {displayBlock(detail)}\n\n
\n );\n};\n\nexport default JobDetail;\n","import {\n FilteringState,\n GroupingState,\n IntegratedFiltering,\n IntegratedGrouping,\n IntegratedSorting,\n SortingState,\n} from '@devexpress/dx-react-grid';\nimport {\n ColumnChooser,\n DragDropProvider,\n Grid,\n GroupingPanel,\n TableColumnVisibility,\n TableFilterRow,\n TableGroupRow,\n TableHeaderRow,\n Toolbar,\n VirtualTable,\n} from '@devexpress/dx-react-grid-material-ui';\nimport { FormControlLabel, Grid as MUIGrid, Paper, Switch } from '@material-ui/core';\nimport { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';\nimport React, { useEffect, useRef, useState } from 'react';\nimport { executeJobQuery, fetchLogs } from '../../api';\nimport Loading from '../../common/Loading/Loading';\nimport { DefaultColumnsTypeProvider } from '../../common/Table';\nimport { calcTimeDifference, zonedTimeFromUTC } from '../../utils/Utils';\nimport { DateFilter } from '../../common/DateFilter/DateFilter';\nimport { Cell, dateGroupCriteria, GroupCellContent } from './helpers/helpers';\nimport JobDetail from './helpers/JobDetail';\nimport { JobPanelStyles } from './styles';\nimport JobListProps, { JobData } from './types';\nimport { DateProps } from '../../common/types';\n\nconst DEFAULT_COLUMNS = [\n { title: 'Task Id', name: 'taskId' },\n { title: 'Status', name: 'status', width: 80 },\n { title: 'Account ID', name: 'accountId' },\n { title: 'Host Id', name: 'hostId' },\n { title: 'Duration', name: 'duration' },\n { title: 'Delay', name: 'delay' },\n { title: 'Requested', name: 'requested' },\n { title: 'Started', name: 'started' },\n { title: 'Finished', name: 'finished' },\n];\n\nconst NOTIFICATION_HUB = '/notificationhub';\n\nconst JobList = (props: JobListProps) => {\n const { dataSources, token, disabledColumns, parameters, startTimeUtc, dateTimeFormat, timeZone } = props;\n const initialDateState = {\n from: new Date(startTimeUtc).toISOString(),\n to: new Date().toISOString(),\n };\n const initialJobData = {\n id: '',\n taskId: '',\n accountId: '',\n status: '',\n hostId: '',\n duration: '',\n delay: '',\n requested: '',\n started: '',\n finished: '',\n progress: 0,\n connectionJobLog: '',\n };\n\n const [job, setJob] = useState(initialJobData);\n const classes = JobPanelStyles(job?.id)();\n const [jobsData, setJobsData] = useState([]);\n const [loading, setLoading] = useState(true);\n const [windowHeight, setWindowHeight] = useState(window.innerHeight);\n const [textareaScrolled, setTextareaScrolled] = useState(true);\n const [date, setDate] = useState(initialDateState);\n const [selectedRow, setSelectedRow] = useState('');\n const [tableColumnExtensions] = useState([{ columnName: 'status', width: 120 }]);\n const latestJobs = useRef(null);\n\n latestJobs.current = jobsData;\n\n const [defaultColumnsNameArray] = useState(DEFAULT_COLUMNS.map((column) => column.name));\n\n const [tableGroupColumnExtension] = useState([\n { columnName: 'requested', showWhenGrouped: true },\n { columnName: 'started', showWhenGrouped: true },\n { columnName: 'finished', showWhenGrouped: true },\n ]);\n\n const [integratedGroupingColumnExtensions] = useState([\n { columnName: 'requested', criteria: dateGroupCriteria },\n { columnName: 'started', criteria: dateGroupCriteria },\n { columnName: 'finished', criteria: dateGroupCriteria },\n ]);\n\n const fetchJobList = () => {\n setLoading(true);\n\n const query = [\n {\n item: 'Requested',\n queryOperator: 'GreaterThan',\n value: date.from ? date.from : initialDateState.from,\n },\n {\n item: 'Requested',\n queryOperator: 'LessThan',\n value: date.to ? date.to : initialDateState.to,\n },\n ];\n\n executeJobQuery(dataSources, token, query).subscribe(\n (res) => {\n const rawJobs = res.map((s: { data }) => {\n const { id, taskId, hostId, accountId, status } = s.data;\n\n // Mapping to JobData.\n const dataMapping = {\n id,\n taskId,\n hostId,\n accountId,\n status,\n progress: s.data.progress || 0,\n requested: s.data.requested ? zonedTimeFromUTC(s.data.requested, timeZone, dateTimeFormat) : '',\n started: s.data.started ? zonedTimeFromUTC(s.data.started, timeZone, dateTimeFormat) : '',\n finished: s.data.finished ? zonedTimeFromUTC(s.data.finished, timeZone, dateTimeFormat) : '',\n duration: calcTimeDifference(s.data.started, s.data.finished),\n delay: calcTimeDifference(s.data.requested, s.data.started),\n connectionJobLog: s.data.connectionJobLog || '',\n };\n\n if (s.data.parameters) {\n for (const key of Object.keys(s.data.parameters)) {\n dataMapping[key] = s.data.parameters[key];\n }\n }\n\n return dataMapping;\n });\n\n setJobsData(rawJobs);\n\n setLoading(false);\n },\n (error) => {\n console.log(error);\n },\n );\n };\n\n const parameterHeader = parameters\n ? parameters.reduce(\n (acc, cur) => [\n ...acc,\n {\n title: cur.label,\n name: cur.parameter,\n },\n ],\n [],\n )\n : [];\n\n const [columns] = useState(DEFAULT_COLUMNS.concat(parameterHeader));\n\n const expandWithData = (row) => {\n const {\n id = '',\n taskId = '',\n accountId = '',\n status = '',\n hostId = '',\n duration = '',\n delay = '',\n requested = '',\n started = '',\n finished = '',\n progress = 0,\n connectionJobLog = '',\n } = row;\n\n if (job.id === id) {\n setJob(initialJobData);\n setSelectedRow('');\n } else {\n setLoading(true);\n setSelectedRow(id);\n const query = [\n {\n Item: 'Tag',\n Value: id,\n QueryOperator: 'Equal',\n },\n ];\n\n const sources = dataSources.map((item) => ({\n host: item.host,\n connection: item.connectionJobLog,\n }));\n\n fetchLogs(sources, token, query).subscribe(\n (res) => {\n const logs = res.map((item) => item.data);\n\n setJob({\n id,\n taskId,\n accountId,\n status,\n hostId,\n duration,\n delay,\n requested,\n started,\n finished,\n progress,\n connectionJobLog,\n logs,\n });\n\n setLoading(false);\n },\n (error) => {\n console.log(error);\n },\n );\n }\n };\n\n const closeTab = () => {\n setJob(initialJobData);\n };\n\n const clearDateFilter = () => {\n setDate(initialDateState);\n };\n\n useEffect(() => fetchJobList(), [date]);\n\n const TableRow = (props: any) => (\n expandWithData(props.tableRow.row)}\n />\n );\n\n const ToolbarRootComponent = (props: any) => (\n \n
{props.children}
\n
setDate(date)}\n onClearDateFilter={clearDateFilter}\n >\n \n setTextareaScrolled(!textareaScrolled)}\n color=\"primary\"\n name=\"textareaView\"\n inputProps={{ 'aria-label': 'textareaView checkbox' }}\n />\n }\n label=\"Log scrolled down\"\n />\n \n \n
\n );\n\n const jobUpdated = (job) => {\n const dataUpdated = JSON.parse(job.data);\n const jobs = [...latestJobs.current];\n\n console.log({ dataUpdated });\n\n const updatedJob = jobs.map((job) =>\n job.id === dataUpdated.Id\n ? {\n ...job,\n started:\n job.started || dataUpdated.Started ? zonedTimeFromUTC(dataUpdated.Started, timeZone, dateTimeFormat) : '',\n finished:\n job.finished || dataUpdated.Finished\n ? zonedTimeFromUTC(dataUpdated.Finished, timeZone, dateTimeFormat)\n : '',\n hostId: dataUpdated.HostId,\n status: dataUpdated.Status,\n duration:\n job.duration ||\n (dataUpdated.Started &&\n dataUpdated.Finished &&\n calcTimeDifference(dataUpdated.Started.split('.')[0], dataUpdated.Finished.split('.')[0])),\n delay:\n job.delay ||\n (dataUpdated.Started &&\n calcTimeDifference(dataUpdated.Requested.split('.')[0], dataUpdated.Started.split('.')[0])),\n progress: dataUpdated.Progress || 0,\n }\n : job,\n );\n\n setJobsData(updatedJob);\n };\n\n const jobAdded = (job) => {\n const dataAdded = JSON.parse(job.data);\n const jobs = [...latestJobs.current];\n\n console.log({ dataAdded });\n\n const addedJob = {\n taskId: dataAdded.TaskId,\n id: dataAdded.Id,\n hostId: dataAdded.HostId,\n accountId: dataAdded.AccountId,\n ScenarioId: dataAdded.Parameters?.ScenarioId,\n priority: dataAdded.Priority,\n requestedUtc: dataAdded.Requested,\n requested: dataAdded.Requested ? zonedTimeFromUTC(dataAdded.Requested, timeZone, dateTimeFormat) : '',\n status: dataAdded.Status,\n connectionJobLog: dataAdded.ConnectionJobLog || '',\n progress: dataAdded.Progress || 0,\n };\n\n jobs.push(addedJob);\n setJobsData(jobs);\n };\n\n const connectToSignalR = async () => {\n // Open connections\n try {\n await dataSources.forEach((source) => {\n if (!source.host) {\n throw new Error('Host not provided.');\n }\n\n const connection = new HubConnectionBuilder()\n .withUrl(source.host + NOTIFICATION_HUB, {\n accessTokenFactory: () => token,\n })\n .configureLogging(LogLevel.Information)\n .withAutomaticReconnect()\n .build();\n\n connection\n .start()\n .then(() => {\n connection.on('JobUpdated', jobUpdated);\n connection.on('JobAdded', jobAdded);\n\n connection.invoke(\n 'AddJobFilter',\n source.connection,\n [{ Item: 'Priority', QueryOperator: 'GreaterThan', Value: -1 }], // ability to change condition?\n );\n })\n .catch((e) => console.log('Connection failed: ', e));\n });\n } catch (err) {\n console.log('SignalR connection failed: ', err);\n }\n };\n\n useEffect(() => {\n const handleResize = () => {\n setWindowHeight(window.innerHeight);\n };\n\n window.addEventListener('resize', handleResize);\n\n connectToSignalR();\n\n return () => {\n window.removeEventListener('resize', handleResize);\n };\n }, []);\n\n return (\n \n
\n {loading && }\n\n \n \n \n\n \n \n\n \n \n \n\n \n\n \n\n \n \n\n \n \n \n\n \n \n \n \n
\n \n
\n
\n );\n};\n\nexport { JobList, JobListProps };\n","import { makeStyles, Theme } from '@material-ui/core/styles';\n\nexport const TreeViewStyles = makeStyles((theme: Theme) => ({\n root: {\n display: 'flex',\n alignItems: 'center',\n },\n typography: {\n display: 'flex',\n padding: '6px 4px',\n textAlign: 'center',\n justifyContent: 'space-evenly',\n position: 'relative',\n alignItems: 'center',\n fontSize: '0.8rem',\n },\n checkbox: {\n padding: '6px 4px',\n },\n}));\n","import { Checkbox, CircularProgress, Typography } from '@material-ui/core';\nimport { ChevronRight, KeyboardArrowDown } from '@material-ui/icons';\nimport TreeItem from '@material-ui/lab/TreeItem';\nimport TreeView from '@material-ui/lab/TreeView';\nimport React, { useEffect, useState } from 'react';\nimport { TreeViewStyles } from './styles';\nimport { TreeViewProps } from './types';\n\nconst DHITreeView = ({ list, onExpand, onChecked }: TreeViewProps) => {\n const [selected, setSelected] = useState([]);\n const [expanded, setExpanded] = useState([]);\n const classes = TreeViewStyles();\n\n const handleExpanded = (event, nodeIds) => {\n const difference = nodeIds.filter((x) => !expanded.includes(x));\n setExpanded(nodeIds);\n\n if (difference.length > 0) {\n onExpand(difference[0]);\n }\n };\n\n const checkBoxClicked = (event, checked, id) => {\n const existing = selected.some((item) => item === id);\n\n if (existing) {\n const removeSelection = selected.filter((item) => item !== id);\n setSelected([...removeSelection]);\n } else {\n setSelected([...selected, id]);\n onExpand(event.target.id);\n }\n };\n\n const structureLevel = (treeViewList) => {\n const elements = [];\n\n treeViewList?.forEach((treeList, i) => {\n const { value, children } = treeList;\n\n if (i.length !== 0) {\n const label = (\n \n {treeList.label === '' ? (\n \n ) : (\n <>\n {!children && (\n item === value)}\n className={classes.checkbox}\n onChange={(event, checked) => checkBoxClicked(event, checked, value)}\n />\n )}\n\n \n {treeList.label.replace('/', '')}\n \n >\n )}\n
\n );\n\n elements.push(\n children && children.length > 0 ? (\n \n {structureLevel(children)}\n \n ) : (\n <>\n \n >\n ),\n );\n } else if (children) {\n elements.push(structureLevel(children));\n }\n });\n\n return elements;\n };\n\n useEffect(() => {\n onChecked(selected);\n }, [selected]);\n\n return (\n }\n defaultExpandIcon={}\n expanded={expanded}\n >\n {structureLevel(list)}\n