Skip to content

Commit

Permalink
Merge pull request #2 from mlbiam/main
Browse files Browse the repository at this point in the history
added dialogs for pulling URLs, throttling requested
  • Loading branch information
mlbiam authored Jan 5, 2025
2 parents de00df1 + adb6eb5 commit 1ede010
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 29 deletions.
2 changes: 1 addition & 1 deletion html/scale/src/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ function DashboardContent() {




fetch(configData.SERVER_URL + "sessioncheck")
.then(response => {
if (response.status == 200) {
Expand Down
111 changes: 83 additions & 28 deletions html/scale/src/Ops.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ import OpsWorkflows from './OpsWorkflows';
import Orgs from './Orgs';

import Alert from '@mui/material/Alert';
import { Checkbox } from '@mui/material';
import { Checkbox, DialogActions } from '@mui/material';
import { styled } from '@mui/material/styles';
import Semaphore from './Semaphore';

import configData from './config/config.json'

Expand Down Expand Up @@ -76,7 +77,9 @@ export default function Ops(props) {

const [forceRedraw,setForceRedraw] = React.useState(Math.random);


const [semaphore, setSemaphore] = React.useState(new Semaphore(10));

const [showLoadDialog,setShowLoadDialog] = React.useState(false);

var searchAttrs = {}
props.opsConfig.searchableAttributes.map(attrCfg => {
Expand Down Expand Up @@ -113,7 +116,48 @@ export default function Ops(props) {
},
}));

function fetchWorkflows(node) {
const fetchDelegate = async(url,wf) => {
const controller = semaphore.getAbortController(); // Get AbortController for cancellation
const options = {"signal" : controller.signal}; // Attach signal to the fetch options

await semaphore.acquire();

try {

const response = await fetch(url,options)

.then(response => {
return response.json();
})
.then(json => {
wf.canPreApprove = json.canPreApprove;
wf.canDelegate = json.canDelegate;

if (wf.canPreApprove) {
wf.tryPreApprove = props.opsConfig.approveChecked;
wf.showPreApprove = props.opsConfig.showPreApprove;

wf.approvedLabel = props.opsConfig.approvedLabel;
wf.deniedLabel = props.opsConfig.deniedLabel;
wf.reasonApprovedLabel = props.opsConfig.reasonApprovedLabel;
wf.reasonDeniedLabel = props.opsConfig.reasonDeniedLabel;
}

//setForceRedraw(Math.random)
})
} catch (err) {
if (err.name === 'AbortError') {
console.error('Fetch aborted:', url);
} else {
console.error('Fetch error:', err);
}
} finally {
semaphore.release();
}
}

const fetchWorkflows = async(node) => {

fetch(configData.SERVER_URL + "main/workflows/org/" + node)
.then(response => {

Expand All @@ -125,32 +169,17 @@ export default function Ops(props) {


})
.then(data => {
.then(async(data) => {
var wfs = data;


wfs.map(wf => {
fetch(configData.SERVER_URL + "main/workflows/candelegate?workflowName=" + wf.name + "&uuid=" + wf.uuid)

.then(response => {
return response.json();
})
.then(json => {
wf.canPreApprove = json.canPreApprove;
wf.canDelegate = json.canDelegate;

if (wf.canPreApprove) {
wf.tryPreApprove = props.opsConfig.approveChecked;
wf.showPreApprove = props.opsConfig.showPreApprove;

wf.approvedLabel = props.opsConfig.approvedLabel;
wf.deniedLabel = props.opsConfig.deniedLabel;
wf.reasonApprovedLabel = props.opsConfig.reasonApprovedLabel;
wf.reasonDeniedLabel = props.opsConfig.reasonDeniedLabel;
}
fetchDelegate(configData.SERVER_URL + "main/workflows/candelegate?workflowName=" + wf.name + "&uuid=" + wf.uuid,wf);
});

setForceRedraw(Math.random)
})
semaphore.waitUntilEmpty().then(x => {
setShowLoadDialog(false);

});


Expand All @@ -171,7 +200,7 @@ export default function Ops(props) {
wfAnnotations[annotationLabel] = vals;
}
})
});
})



Expand All @@ -180,12 +209,15 @@ export default function Ops(props) {
setWorkflows(newLinks);
setVisibleWorkflows(newLinks);
setFilter("");

})
}

function handleRequestAccessOrgClick(event, node) {
const handleRequestAccessOrgClick = async(event, node) => {
setCurrentOrg(props.orgsById[node]);
fetchWorkflows(node);
setShowLoadDialog(true);
await fetchWorkflows(node);


}

Expand All @@ -194,7 +226,11 @@ export default function Ops(props) {
filterWorkflows(event.target.value,selectedFilters);
}


const handelCancel = async() => {
semaphore.cancelAll();
await semaphore.waitUntilEmpty();
setShowLoadDialog(false);
}

function filterWorkflows(filterValue,filterAnnotations) {
if (! filterAnnotations) {
Expand Down Expand Up @@ -338,6 +374,7 @@ export default function Ops(props) {

return (
<React.Fragment>

<Dialog
open={showSubmitDialog}

Expand All @@ -353,6 +390,24 @@ export default function Ops(props) {
</DialogContent>
</Dialog>

<Dialog
open={showLoadDialog}

aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Loading Workflows</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Verifying Permissions
<LinearProgress />
</DialogContentText>
<DialogActions>
<Button onClick={handelCancel}>Cancel</Button>
</DialogActions>
</DialogContent>
</Dialog>

<Dialog
open={showUserDialog}
maxWidth={props.opsConfig.maxWidth}
Expand Down
24 changes: 24 additions & 0 deletions html/scale/src/RequestAccess.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,23 @@ import InputLabel from '@mui/material/InputLabel';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import MenuItem from '@mui/material/MenuItem';

import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import LinearProgress from '@mui/material/LinearProgress';
import Dialog from '@mui/material/Dialog';

export default function RequestAccess(props) {
const [workflows, setWorkflows] = React.useState({ wfs: [] })
const [visibleWorkflows, setVisibleWorkflows] = React.useState({ wfs: [] })
const [filter, setFilter] = React.useState("");
const [currentOrg, setCurrentOrg] = React.useState({});
const [annotationFilters,setAnnotationFilters] = React.useState({});
const [selectedFilters,setSelectedFilters] = React.useState({});
const [showLoadDialog, setShowLoadDialog] = React.useState(false);

function fetchWorkflows(node) {
setShowLoadDialog(true);
fetch(configData.SERVER_URL + "main/workflows/org/" + node)
.then(response => {

Expand Down Expand Up @@ -58,6 +66,7 @@ export default function RequestAccess(props) {
setWorkflows(newLinks);
setVisibleWorkflows(newLinks);
setFilter("");
setShowLoadDialog(false);
})
}

Expand Down Expand Up @@ -118,6 +127,21 @@ export default function RequestAccess(props) {

return (
<React.Fragment>
<Dialog
open={showLoadDialog}

aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">Loading</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
Please wait while we load what can be requested
<LinearProgress />
</DialogContentText>
</DialogContent>
</Dialog>

<Grid container spacing={0}>
{/* Chart */}

Expand Down
65 changes: 65 additions & 0 deletions html/scale/src/Semaphore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
class Semaphore {



constructor(maxConcurrency) {
this.maxConcurrency = maxConcurrency;
this.currentCount = 0;
this.queue = [];
this.activeAbortControllers = new Set(); // Track active AbortControllers

}

async acquire() {
if (this.currentCount < this.maxConcurrency) {
this.currentCount++;
} else {
await new Promise(resolve => this.queue.push(resolve));
}
}

release() {
if (this.queue.length > 0) {
const nextResolve = this.queue.shift();
nextResolve();
} else {
this.currentCount--;
}
}



async waitUntilEmpty() {
return new Promise(resolve => {
const checkQueue = () => {
if (this.queue.length === 0 && this.currentCount === 0) {
resolve();
} else {
setTimeout(checkQueue, 50); // Check again after a short delay
}
};
checkQueue();
});
}

getAbortController() {
const controller = new AbortController();
this.activeAbortControllers.add(controller); // Add to active controllers

controller.signal.addEventListener('abort', () => {
this.activeAbortControllers.delete(controller); // Remove when aborted
});

return controller;
}

cancelAll= () => {
for (const controller of this.activeAbortControllers) {
controller.abort(); // Abort all active controllers
}
this.activeAbortControllers.clear(); // Clear the set
}

}

export default Semaphore;

0 comments on commit 1ede010

Please sign in to comment.