diff --git a/esp/services/ws_workunits/ws_workunitsHelpers.hpp b/esp/services/ws_workunits/ws_workunitsHelpers.hpp index 219e2fe75f6..00713b56500 100644 --- a/esp/services/ws_workunits/ws_workunitsHelpers.hpp +++ b/esp/services/ws_workunits/ws_workunitsHelpers.hpp @@ -188,13 +188,15 @@ struct WUComponentLogOptions } else if (!isEmptyString(end)) { - if (isEmptyString(end)) + if (isEmptyString(start)) throw makeStringException(ECLWATCH_INVALID_INPUT, "ZapLogFilter: Empty 'Absolute TimeRange Start' detected!"); } else { if (relativeTimeBufferSecs > 0 ) wuLogSearchTimeBuffSecs = relativeTimeBufferSecs; + else + throw makeStringException(ECLWATCH_INVALID_INPUT, "ZapLogFilter: Invalid 'TimeRange' detected!"); } } @@ -208,9 +210,9 @@ struct WUComponentLogOptions logDataFormat = logAccessFormatFromName(requestedLogDataFormat.str()); StringBuffer start; // Absolute query time range start in YYYY-DD-MMTHH:MM:SS - zapHttpRequest->getParameter("LogFilter_AbsoluteTimeRange_Start", start); + zapHttpRequest->getParameter("LogFilter_AbsoluteTimeRange_StartDate", start); StringBuffer end; // Absolute query time range end in YYYY-DD-MMTHH:MM:SS - zapHttpRequest->getParameter("LogFilter_AbsoluteTimeRange_End", end); + zapHttpRequest->getParameter("LogFilter_AbsoluteTimeRange_EndDate", end); // Query time range based on WU Time +- Buffer in seconds unsigned bufferSecs = (unsigned)zapHttpRequest->getParameterInt("LogFilter_RelativeTimeRangeBuffer", 0); diff --git a/esp/src/eclwatch/ShowIndividualPermissionsWidget.js b/esp/src/eclwatch/ShowIndividualPermissionsWidget.js index fef10399d8b..ae3692d7ce6 100644 --- a/esp/src/eclwatch/ShowIndividualPermissionsWidget.js +++ b/esp/src/eclwatch/ShowIndividualPermissionsWidget.js @@ -181,6 +181,7 @@ define([ evt.preventDefault(); context.calcPermissionState(evt.cell.column.field, evt.value, evt.cell.row.data); evt.grid.store.put(evt.cell.row.data); + const t = window.setTimeout(() => { context.grid.refresh(); window.clearTimeout(t); }, 100); }); return retVal; }, diff --git a/esp/src/package-lock.json b/esp/src/package-lock.json index 06de90bbe29..363b1424c2d 100644 --- a/esp/src/package-lock.json +++ b/esp/src/package-lock.json @@ -18,7 +18,7 @@ "@hpcc-js/chart": "2.83.3", "@hpcc-js/codemirror": "2.62.0", "@hpcc-js/common": "2.71.17", - "@hpcc-js/comms": "2.92.2", + "@hpcc-js/comms": "2.92.3", "@hpcc-js/dataflow": "8.1.6", "@hpcc-js/eclwatch": "2.74.5", "@hpcc-js/graph": "2.85.15", @@ -1859,12 +1859,12 @@ } }, "node_modules/@hpcc-js/comms": { - "version": "2.92.2", - "resolved": "https://registry.npmjs.org/@hpcc-js/comms/-/comms-2.92.2.tgz", - "integrity": "sha512-9AbPnCYuTF6OhbSiG5QMDA2vuF457YL88h2ltuxPOjsOxp9Dp5VFlTkh88vW1W3Yph/+faGhiqUSvLMgFIwXEA==", + "version": "2.92.3", + "resolved": "https://registry.npmjs.org/@hpcc-js/comms/-/comms-2.92.3.tgz", + "integrity": "sha512-0mrIb4kXGTVnvHBpRZk+yas108sSXgIKq6HAGSv/ZJFCXEoKCynoaSH+DFaD9jcfZrVezn4fbjJtqYxXln86tA==", "dependencies": { - "@hpcc-js/ddl-shim": "^2.20.6", - "@hpcc-js/util": "^2.51.0", + "@hpcc-js/ddl-shim": "^2.20.7", + "@hpcc-js/util": "^2.51.1", "@xmldom/xmldom": "0.8.10", "abort-controller": "3.0.0", "node-fetch": "2.7.0", @@ -1873,6 +1873,14 @@ "undici": "5.28.4" } }, + "node_modules/@hpcc-js/comms/node_modules/@hpcc-js/util": { + "version": "2.51.1", + "resolved": "https://registry.npmjs.org/@hpcc-js/util/-/util-2.51.1.tgz", + "integrity": "sha512-BJuqg6FGqcV4RR8/BU5e7fASDtkl0Na7dWY+Th7r5ciWKI5AXsO0GtlgwDBt2uLUOlcGOMpYozmdbGSCoSHAvQ==", + "dependencies": { + "tslib": "2.6.3" + } + }, "node_modules/@hpcc-js/comms/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1892,15 +1900,20 @@ } ] }, + "node_modules/@hpcc-js/comms/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + }, "node_modules/@hpcc-js/dataflow": { "version": "8.1.6", "resolved": "https://registry.npmjs.org/@hpcc-js/dataflow/-/dataflow-8.1.6.tgz", "integrity": "sha512-BMmpA6CP00nRDdeq7MS/LRN+e08dyBnnLWEITK5zuEn8D9sFo4ZJlmrKNp+Lcox/m+CuNqWMTG9Z5c+hK2jUyw==" }, "node_modules/@hpcc-js/ddl-shim": { - "version": "2.20.6", - "resolved": "https://registry.npmjs.org/@hpcc-js/ddl-shim/-/ddl-shim-2.20.6.tgz", - "integrity": "sha512-0N9KwRr2rzSJEhfXjEDmT2IBf2Bm6QJ4ReOzhu1wTN3O3gKkUXxNxJMDePFyCNaeHaNWGEQOxewf1q9NtrSkbA==", + "version": "2.20.7", + "resolved": "https://registry.npmjs.org/@hpcc-js/ddl-shim/-/ddl-shim-2.20.7.tgz", + "integrity": "sha512-n+MQBW9zgfhN6zCTaZSiZfMAJfhR6bw4Fuo4fMhQdF2x17Yu/DbN8MReNvyq2OOBmxkwcp28/VxYnsJeppWMQw==", "hasInstallScript": true, "dependencies": { "ajv": "6.12.6" diff --git a/esp/src/package.json b/esp/src/package.json index 9f825f692bf..6030c085361 100644 --- a/esp/src/package.json +++ b/esp/src/package.json @@ -44,7 +44,7 @@ "@hpcc-js/chart": "2.83.3", "@hpcc-js/codemirror": "2.62.0", "@hpcc-js/common": "2.71.17", - "@hpcc-js/comms": "2.92.2", + "@hpcc-js/comms": "2.92.3", "@hpcc-js/dataflow": "8.1.6", "@hpcc-js/eclwatch": "2.74.5", "@hpcc-js/graph": "2.85.15", diff --git a/esp/src/src-react/components/FileDetails.tsx b/esp/src/src-react/components/FileDetails.tsx index 599339bc7da..c936e617142 100644 --- a/esp/src/src-react/components/FileDetails.tsx +++ b/esp/src/src-react/components/FileDetails.tsx @@ -41,10 +41,10 @@ export const FileDetails: React.FunctionComponent = ({ }) => { const [file] = useFile(cluster, logicalFile); React.useEffect(() => { - if (file?.NodeGroup && cluster === undefined) { + if (file?.NodeGroup && cluster === undefined && !file?.isSuperfile) { replaceUrl(`/files/${file.NodeGroup}/${logicalFile}`); } - }, [cluster, file?.NodeGroup, logicalFile]); + }, [cluster, file?.NodeGroup, file?.isSuperfile, logicalFile]); const [defFile] = useDefFile(cluster, logicalFile, WsDfu.DFUDefFileFormat.def); const [xmlFile] = useDefFile(cluster, logicalFile, WsDfu.DFUDefFileFormat.xml); diff --git a/esp/src/src-react/components/LogicalFileSummary.tsx b/esp/src/src-react/components/LogicalFileSummary.tsx index 469f2c57b83..a2551783357 100644 --- a/esp/src/src-react/components/LogicalFileSummary.tsx +++ b/esp/src/src-react/components/LogicalFileSummary.tsx @@ -181,7 +181,7 @@ export const LogicalFileSummary: React.FunctionComponent ({ label: "", type: "link", value: row.Name, href: `#/files/${row.Name}` })) }, + "SuperOwner": { label: nlsHPCC.SuperFile, type: "links", links: file?.Superfiles?.DFULogicalFile?.map(row => ({ label: "", type: "link", value: row.Name, href: `#/files/${row.NodeGroup !== null ? row.NodeGroup : undefined}/${row.Name}` })) }, "NodeGroup": { label: nlsHPCC.ClusterName, type: "string", value: file?.NodeGroup, readonly: true }, "Description": { label: nlsHPCC.Description, type: "string", value: description }, "JobName": { label: nlsHPCC.JobName, type: "string", value: file?.JobName, readonly: true }, diff --git a/esp/src/src-react/components/Metrics.tsx b/esp/src/src-react/components/Metrics.tsx index ee05e3055e3..03f1e4d3a72 100644 --- a/esp/src/src-react/components/Metrics.tsx +++ b/esp/src/src-react/components/Metrics.tsx @@ -450,6 +450,7 @@ export const Metrics: React.FunctionComponent = ({ const propsTable2 = useConst(() => new Table() .columns([nlsHPCC.Property, nlsHPCC.Value]) .columnWidth("auto") + .sortable(true) ); const updatePropsTable2 = React.useCallback((selection: IScope[]) => { diff --git a/esp/src/src-react/components/MetricsPropertiesTables.tsx b/esp/src/src-react/components/MetricsPropertiesTables.tsx index 470516b55f7..83d8a894cff 100644 --- a/esp/src/src-react/components/MetricsPropertiesTables.tsx +++ b/esp/src/src-react/components/MetricsPropertiesTables.tsx @@ -23,6 +23,7 @@ export const MetricsPropertiesTables: React.FunctionComponent new Table() .columns([nlsHPCC.Property, nlsHPCC.Value, "Avg", "Min", "Max", "Delta", "StdDev", "SkewMin", "SkewMax", "NodeMin", "NodeMax"]) .columnWidth("auto") + .sortable(true) ); React.useEffect(() => { diff --git a/esp/src/src-react/components/PermissionsEditor.tsx b/esp/src/src-react/components/PermissionsEditor.tsx new file mode 100644 index 00000000000..b1d453f2354 --- /dev/null +++ b/esp/src/src-react/components/PermissionsEditor.tsx @@ -0,0 +1,205 @@ +import * as React from "react"; +import { Checkbox, CommandBar, ContextualMenuItemType, ICommandBarItemProps, Label, Stack } from "@fluentui/react"; +import { WsAccess, AccessService } from "@hpcc-js/comms"; +import { scopedLogger } from "@hpcc-js/util"; +import nlsHPCC from "src/nlsHPCC"; +import { useConfirm } from "../hooks/confirm"; +import { HolyGrail } from "../layouts/HolyGrail"; +import { ShortVerticalDivider } from "./Common"; +import { AddGroupResourceForm } from "./forms/AddGroupResource"; + +const logger = scopedLogger("src-react/components/PermissionsEditor.tsx"); + +const service = new AccessService({ baseUrl: "" }); + +// from ShowIndividualPermissionsWidget.js +const calcPermissionState = (field, value, row) => { + switch (field) { + case "allow_access": + row.allow_full = value && row.allow_read && row.allow_write; + if (value) + calcPermissionState("deny_access", false, row); + break; + case "allow_read": + row.allow_full = row.allow_access && value && row.allow_write; + if (value) + calcPermissionState("deny_read", false, row); + break; + case "allow_write": + row.allow_full = row.allow_access && row.allow_read && value; + if (value) + calcPermissionState("deny_write", false, row); + break; + case "allow_full": + row.allow_access = value; + row.allow_read = value; + row.allow_write = value; + if (value) + calcPermissionState("deny_full", false, row); + break; + case "deny_access": + row.deny_full = value && row.deny_read && row.deny_write; + if (value) + calcPermissionState("allow_access", false, row); + break; + case "deny_read": + row.deny_full = row.deny_access && value && row.deny_write; + if (value) + calcPermissionState("allow_read", false, row); + break; + case "deny_write": + row.deny_full = row.deny_access && row.deny_read && value; + if (value) + calcPermissionState("allow_write", false, row); + break; + case "deny_full": + row.deny_access = value; + row.deny_read = value; + row.deny_write = value; + if (value) + calcPermissionState("allow_full", false, row); + break; + } + row[field] = value; +}; + +interface PermissionsEditorProps { + BaseDn?: string; + Name?: string; +} + +export const PermissionsEditor: React.FunctionComponent = ({ + BaseDn, + Name +}) => { + + const [data, setData] = React.useState([]); + const [selectedIndex, setSelectedIndex] = React.useState(-1); + const [showAddGroup, setShowAddGroup] = React.useState(false); + + const refreshData = React.useCallback(() => { + service.ResourcePermissions({ BasednName: BaseDn, name: Name }) + .then(({ Permissions }: WsAccess.ResourcePermissionsResponse) => { + setData(Permissions?.Permission.map(Permission => { + return { + account_name: Permission.account_name, + allow_access: Permission.allow_access ?? false, + allow_read: Permission.allow_read ?? false, + allow_write: Permission.allow_write ?? false, + allow_full: Permission.allow_full ?? false, + deny_access: Permission.deny_access ?? false, + deny_read: Permission.deny_read ?? false, + deny_write: Permission.deny_write ?? false, + deny_full: Permission.deny_full ?? false, + }; + })); + setSelectedIndex(-1); + }) + .catch(err => logger.error(err)) + ; + }, [BaseDn, Name]); + + const [DeleteConfirm, setShowDeleteConfirm] = useConfirm({ + title: nlsHPCC.Delete, + message: nlsHPCC.DeleteSelectedGroups + "\n\n" + data[selectedIndex]?.account_name, + onSubmit: React.useCallback(() => { + service.PermissionAction({ + action: "delete", + BasednName: BaseDn, + rname: Name, + account_name: data[selectedIndex]?.account_name + }) + .then(() => refreshData()) + .catch(err => logger.error(err)) + ; + }, [BaseDn, data, Name, refreshData, selectedIndex]) + }); + + // Command Bar --- + const buttons = React.useMemo((): ICommandBarItemProps[] => [ + { + key: "refresh", text: nlsHPCC.Refresh, iconProps: { iconName: "Refresh" }, + onClick: () => refreshData() + }, + { key: "divider_1", itemType: ContextualMenuItemType.Divider, onRender: () => }, + { + key: "add", text: nlsHPCC.Add, + onClick: () => setShowAddGroup(true) + }, + { key: "divider_2", itemType: ContextualMenuItemType.Divider, onRender: () => }, + { + key: "delete", text: nlsHPCC.Delete, disabled: selectedIndex < 0, + onClick: () => setShowDeleteConfirm(true) + }, + ], [refreshData, selectedIndex, setShowDeleteConfirm]); + + React.useEffect(() => { + refreshData(); + }, [refreshData]); + + const onRowSelect = React.useCallback((evt, index) => { + if (evt.target.checked) { + setSelectedIndex(index); + } else { + setSelectedIndex(-1); + } + }, [setSelectedIndex]); + + const onPermissionCheckboxClick = React.useCallback((evt, permission, prop) => { + calcPermissionState(prop, evt.target.checked, permission); + service.PermissionAction({ + action: "update", + BasednName: BaseDn, + rname: Name, + account_type: 1, + ...permission + }).then(({ retcode, retmsg }) => { + if (retcode === 0) { + setData(prevState => { + const newState = Array.from(prevState); + return newState; + }); + } else if (retcode === -1) { + logger.error(retmsg); + refreshData(); + } + }).catch(err => logger.error(err)); + }, [BaseDn, Name, refreshData]); + + return <> + } + main={ +
+ + + + + + + + + + + + {data?.map((permission, index) => ( + + onRowSelect(ev, index)} /> + {permission.account_name} + onPermissionCheckboxClick(ev, permission, "allow_access")} /> + onPermissionCheckboxClick(ev, permission, "allow_read")} /> + onPermissionCheckboxClick(ev, permission, "allow_write")} /> + onPermissionCheckboxClick(ev, permission, "allow_full")} /> + onPermissionCheckboxClick(ev, permission, "deny_access")} /> + onPermissionCheckboxClick(ev, permission, "deny_read")} /> + onPermissionCheckboxClick(ev, permission, "deny_write")} /> + onPermissionCheckboxClick(ev, permission, "deny_full")} /> + + ))} +
+ } + /> + + + ; +}; \ No newline at end of file diff --git a/esp/src/src-react/components/Security.tsx b/esp/src/src-react/components/Security.tsx index fac4c8ed7a6..260f7b6239a 100644 --- a/esp/src/src-react/components/Security.tsx +++ b/esp/src/src-react/components/Security.tsx @@ -4,10 +4,10 @@ import { SizeMe } from "react-sizeme"; import { pushUrl } from "../util/history"; import { Groups } from "./Groups"; import { Permissions } from "./Permissions"; +import { PermissionsEditor } from "./PermissionsEditor"; import { Users } from "./Users"; import { useBuildInfo } from "../hooks/platform"; import { pivotItemStyle } from "../layouts/pivot"; -import { DojoAdapter } from "../layouts/DojoAdapter"; import nlsHPCC from "src/nlsHPCC"; interface SecurityProps { @@ -59,7 +59,7 @@ export const Security: React.FunctionComponent = ({ } {name && baseDn && - + } diff --git a/esp/src/src-react/components/SubFiles.tsx b/esp/src/src-react/components/SubFiles.tsx index 7337454931f..e7088869472 100644 --- a/esp/src/src-react/components/SubFiles.tsx +++ b/esp/src/src-react/components/SubFiles.tsx @@ -5,7 +5,7 @@ import nlsHPCC from "src/nlsHPCC"; import { QuerySortItem } from "src/store/Store"; import * as WsDfu from "src/WsDfu"; import { useConfirm } from "../hooks/confirm"; -import { useFile } from "../hooks/file"; +import { useFile, useSubfiles } from "../hooks/file"; import { FluentGrid, useCopyButtons, useFluentStoreState, FluentColumns } from "./controls/Grid"; import { ShortVerticalDivider } from "./Common"; import { pushUrl } from "../util/history"; @@ -28,7 +28,8 @@ export const SubFiles: React.FunctionComponent = ({ sort = defaultSort }) => { - const [file, , , refresh] = useFile(cluster, logicalFile); + const [file] = useFile(cluster, logicalFile); + const [subfiles, refreshSubfiles] = useSubfiles(cluster, logicalFile); const [uiState, setUIState] = React.useState({ ...defaultUIState }); const [data, setData] = React.useState([]); const { @@ -101,26 +102,30 @@ export const SubFiles: React.FunctionComponent = ({ message: nlsHPCC.RemoveSubfiles2, items: selection.map(item => item.Name), onSubmit: React.useCallback(() => { - WsDfu.SuperfileAction("remove", file.Name, selection, false).then(() => refresh()); - }, [file, refresh, selection]) + WsDfu.SuperfileAction("remove", file.Name, selection, false).then(() => refreshSubfiles()); + }, [file, refreshSubfiles, selection]) }); React.useEffect(() => { - const subfiles = []; + const files = []; const promises = []; - file?.subfiles?.Item.forEach(item => { + subfiles?.Item.forEach(item => { const logicalFile = ESPLogicalFile.Get("", item); promises.push(logicalFile.getInfo2({ onAfterSend: function (response) { } })); - subfiles.push(logicalFile); + files.push(logicalFile); }); - Promise.all(promises).then(logicalFiles => { - setData(subfiles); - }); - }, [file?.subfiles]); + if (promises.length) { + Promise.all(promises).then(logicalFiles => { + setData(files); + }); + } else { + setData(files); + } + }, [file, subfiles]); const buttons = React.useMemo((): ICommandBarItemProps[] => [ { diff --git a/esp/src/src-react/components/SuperFiles.tsx b/esp/src/src-react/components/SuperFiles.tsx index b1cbe3bdd0b..b9cb69e99e4 100644 --- a/esp/src/src-react/components/SuperFiles.tsx +++ b/esp/src/src-react/components/SuperFiles.tsx @@ -43,11 +43,11 @@ export const SuperFiles: React.FunctionComponent = ({ label: nlsHPCC.Name, sortable: true, formatter: (name, row) => { - return {name}; + return {name}; } } }; - }, [cluster]); + }, []); // Command Bar --- const buttons = React.useMemo((): ICommandBarItemProps[] => [ diff --git a/esp/src/src-react/components/forms/AddGroupResource.tsx b/esp/src/src-react/components/forms/AddGroupResource.tsx new file mode 100644 index 00000000000..0df103bf4a7 --- /dev/null +++ b/esp/src/src-react/components/forms/AddGroupResource.tsx @@ -0,0 +1,129 @@ +import * as React from "react"; +import { Checkbox, DefaultButton, PrimaryButton, TextField, } from "@fluentui/react"; +import { AccessService } from "@hpcc-js/comms"; +import { scopedLogger } from "@hpcc-js/util"; +import { useForm, Controller } from "react-hook-form"; +import nlsHPCC from "src/nlsHPCC"; +import { MessageBox } from "../../layouts/MessageBox"; + +const logger = scopedLogger("src-react/components/forms/AddGroupResource.tsx"); + +const service = new AccessService({ baseUrl: "" }); + +interface AddGroupResourceFormValues { + account_name: string; + allow_access: boolean; + allow_read: boolean; + allow_write: boolean; + allow_full: boolean; +} + +const defaultValues: AddGroupResourceFormValues = { + account_name: "", + allow_access: false, + allow_read: false, + allow_write: false, + allow_full: false +}; + +interface AddGroupResourceFormProps { + rname: string; + BasednName: string; + refreshGrid?: () => void; + showForm: boolean; + setShowForm: (_: boolean) => void; +} + +export const AddGroupResourceForm: React.FunctionComponent = ({ + rname, + BasednName, + refreshGrid, + showForm, + setShowForm +}) => { + + const { handleSubmit, control, reset } = useForm({ defaultValues }); + + const closeForm = React.useCallback(() => { + setShowForm(false); + }, [setShowForm]); + + const onSubmit = React.useCallback(() => { + handleSubmit( + (data, evt) => { + const request: any = data; + + request["action"] = "update"; + request["account_type"] = "1"; + request["rname"] = rname; + request["BasednName"] = BasednName; + + service.PermissionAction(request) + .then(() => { + closeForm(); + reset(defaultValues); + if (refreshGrid) refreshGrid(); + }) + .catch(err => logger.error(err)) + ; + }, + logger.info + )(); + }, [BasednName, closeForm, handleSubmit, refreshGrid, reset, rname]); + + return + + { reset(defaultValues); closeForm(); }} /> + }> + } + rules={{ + required: nlsHPCC.ValidationErrorRequired + }} + /> +
+ } + /> +
+
+ } + /> +
+
+ } + /> +
+
+ } + /> +
+
; +}; diff --git a/esp/src/src-react/components/forms/AddPermission.tsx b/esp/src/src-react/components/forms/AddPermission.tsx index 39f7df0f866..5b44b6eb7e2 100644 --- a/esp/src/src-react/components/forms/AddPermission.tsx +++ b/esp/src/src-react/components/forms/AddPermission.tsx @@ -67,7 +67,7 @@ export const AddPermissionForm: React.FunctionComponent )(); }, [closeForm, handleSubmit, refreshGrid, reset]); - return { reset(defaultValues); closeForm(); }} /> diff --git a/esp/src/src-react/components/forms/ZAPDialog.tsx b/esp/src/src-react/components/forms/ZAPDialog.tsx index d43147c8f3c..3d4e3e3c5c7 100644 --- a/esp/src/src-react/components/forms/ZAPDialog.tsx +++ b/esp/src/src-react/components/forms/ZAPDialog.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Checkbox, DefaultButton, Dropdown, Icon, IDropdownProps, IOnRenderComboBoxLabelProps, IStackTokens, ITextFieldProps, mergeStyleSets, PrimaryButton, Stack, TextField, TooltipHost } from "@fluentui/react"; +import { Checkbox, DefaultButton, Dropdown, Icon, IDropdownProps, IOnRenderComboBoxLabelProps, IStackTokens, ITextFieldProps, mergeStyleSets, PrimaryButton, Spinner, Stack, TextField, TooltipHost } from "@fluentui/react"; import { useForm, Controller } from "react-hook-form"; import { LogType } from "@hpcc-js/comms"; import { scopedLogger } from "@hpcc-js/util"; @@ -164,6 +164,8 @@ export const ZAPDialog: React.FunctionComponent = ({ }), [theme]); const [emailDisabled, setEmailDisabled] = React.useState(true); + const [submitDisabled, setSubmitDisabled] = React.useState(false); + const [spinnerHidden, setSpinnerHidden] = React.useState(true); const [columnMode, setColumnMode] = React.useState(ColumnMode.DEFAULT); const [logFormat, setLogFormat] = React.useState(LogFormat.CSV); const [showCustomColumns, setShowCustomColumns] = React.useState(false); @@ -184,6 +186,8 @@ export const ZAPDialog: React.FunctionComponent = ({ const logFilter = data.LogFilter; delete data.LogFilter; + setSubmitDisabled(true); + setSpinnerHidden(false); for (const key in data) { formData.append(key, data[key]); @@ -223,6 +227,8 @@ export const ZAPDialog: React.FunctionComponent = ({ link.click(); link.remove(); + setSubmitDisabled(false); + setSpinnerHidden(true); closeForm(); if (logAccessorMessage !== "") { @@ -253,7 +259,8 @@ export const ZAPDialog: React.FunctionComponent = ({ return - + + closeForm()} /> }> { - const file = LogicalFile.attach({ baseUrl: "" }, cluster, name); + const file = LogicalFile.attach({ baseUrl: "" }, cluster === "undefined" ? undefined : cluster, name); let active = true; let handle; const fetchInfo = singletonDebounce(file, "fetchInfo"); @@ -87,3 +87,23 @@ export function useFileHistory(cluster: string, name: string): [WsDfu.Origin[], return [history, eraseHistory, increment]; } + +export function useSubfiles(cluster: string, name: string): [WsDfu.subfiles, () => void] { + + const [file] = useFile(cluster, name); + const [subfiles, setSubfiles] = React.useState({ Item: [] }); + const [count, increment] = useCounter(); + + React.useEffect(() => { + if (file) { + file.fetchInfo() + .then(response => { + setSubfiles(response.subfiles ?? { Item: [] }); + }) + .catch(err => logger.error(err)) + ; + } + }, [file, count]); + + return [subfiles, increment]; +} diff --git a/esp/src/src/nls/hpcc.ts b/esp/src/src/nls/hpcc.ts index 3e73e55a068..da88ed6119c 100644 --- a/esp/src/src/nls/hpcc.ts +++ b/esp/src/src/nls/hpcc.ts @@ -31,6 +31,7 @@ export = { AddBinding: "Add Binding", AddFile: "Add File", AddGroup: "Add Group", + AddResource: "Add Resource", AddtionalProcessesToFilter: "Addtional Processes To Filter", AdditionalResources: "Additional Resources", AddPart: "Add Part",