Merged PR 1819: updated watched case filter

Related work items: #18356
This commit is contained in:
Robert Bond
2025-08-14 07:40:28 +00:00
4 changed files with 441 additions and 295 deletions
-27
View File
@@ -1715,33 +1715,6 @@ function formatDateDisplay(date) {
: "Unknown"; : "Unknown";
} }
function formatBlobDates(blob) {
const created = blob.createdOn ? new Date(blob.createdOn) : null;
const modified = blob.lastModified ? new Date(blob.lastModified) : null;
if (!created && !modified) return "Unknown";
// If both exist and are the same
if (created && modified && created.getTime() === modified.getTime()) {
return `${created.toLocaleString("en-GB")} (${formatTimeSince(
created
)})`;
}
// If modified exists and is newer than created
if (created && modified && modified.getTime() > created.getTime()) {
return `Created: ${created.toLocaleString("en-GB")} (${formatTimeSince(
created
)}), Modified: ${modified.toLocaleString("en-GB")} (${formatTimeSince(
modified
)})`;
}
// Only one date exists
const date = modified || created;
return `${date.toLocaleString("en-GB")} (${formatTimeSince(date)})`;
}
export async function listBlobHierarchicalForUser(containerClient) { export async function listBlobHierarchicalForUser(containerClient) {
const blobNames = []; const blobNames = [];
for await (const blob of containerClient.listBlobsFlat()) { for await (const blob of containerClient.listBlobsFlat()) {
+21 -2
View File
@@ -882,8 +882,27 @@ const ViewAllResults = (props) => {
getWatchedCasesProxy( getWatchedCasesProxy(
props.accountDetails.loggedinUserId props.accountDetails.loggedinUserId
).then((data) => { ).then((data) => {
setWatchedCases(data), let showWatchedCases = (submittedArr) => {
getDetailsProxy(data, "myWatchedCases") const required = submittedArr.value.filter((el) => {
return (
el.pinswg_representationsubmitted == null
);
});
let newObj = {};
return Object.assign(newObj, {
"@odata.count": required.length,
"value": required,
});
};
let filteredWatchedCases = showWatchedCases(data);
setWatchedCases(filteredWatchedCases),
getDetailsProxy(
filteredWatchedCases,
"myWatchedCases"
)
.then((data) => { .then((data) => {
setWatchedCasesDetails(data); setWatchedCasesDetails(data);
}) })
+29
View File
@@ -207,6 +207,35 @@ const SignIn = (props) => {
}; };
export async function getServerSideProps(context) { export async function getServerSideProps(context) {
const { query, req, res } = context;
// Load allowed IPs from env and trim spaces
const ALLOWED_IPS = process.env.ALLOWED_IPS
? process.env.ALLOWED_IPS.split(",").map((ip) => ip.trim())
: [];
// Always allow localhost addresses
const LOCALHOST_IPS = ["127.0.0.1", "::1"];
// Get IP address from headers or socket
const forwarded = req.headers["x-forwarded-for"];
const ip =
typeof forwarded === "string"
? forwarded.split(",")[0]
: req.socket.remoteAddress;
console.log("Visitor IP:", ip);
// Check whitelist + localhost
if (![...ALLOWED_IPS, ...LOCALHOST_IPS].includes(ip)) {
return {
redirect: {
destination: "/403", // custom "Access Denied" page
permanent: false,
},
};
}
if (process.env.SHOWLOGIN == false) { if (process.env.SHOWLOGIN == false) {
return { return {
redirect: { redirect: {
+391 -266
View File
@@ -110,11 +110,11 @@ const deleteCaseItem = (containerID, caseID) => {
}); });
}; };
export default function StoragePage({ data }) { export default function StoragePage(props) {
console.log(data); const { data, userData } = props;
const [showRepJson, setShowRepJson] = useState(true); const [showRepJson, setShowRepJson] = useState(true);
const [showTmpFile, setShowTmpFile] = useState(true); const [showTmpFile, setShowTmpFile] = useState(true);
const [whichTab, setWhichTab] = useState("storage");
const toggleAll = () => { const toggleAll = () => {
const newState = !(showRepJson || showTmpFile); const newState = !(showRepJson || showTmpFile);
@@ -130,283 +130,398 @@ export default function StoragePage({ data }) {
<CookieBanner /> <CookieBanner />
<Header /> <Header />
<div className="govuk-width-container"> <div className="govuk-width-container">
<div className="govuk-main-wrapper govuk-main-wrapper--auto-spacing"> <div className="js-enabled govuk-template__body govuk-frontend-supported">
<h1>Storage Account Contents</h1> <div className="govuk-tabs" data-module="govuk-tabs">
<h2 className="govuk-tabs__title">Contents</h2>
<ul className="govuk-tabs__list">
<li
className={
whichTab == "storage"
? "govuk-tabs__list-item govuk-tabs__list-item--selected"
: "govuk-tabs__list-item "
}
>
<a
className="govuk-tabs__tab"
href="#storage"
onClick={() => setWhichTab("storage")}
>
Storage Account
</a>
</li>
<li
className={
whichTab == "accounts"
? "govuk-tabs__list-item govuk-tabs__list-item--selected"
: "govuk-tabs__list-item "
}
>
<a
className="govuk-tabs__tab"
href="#accounts"
onClick={() => setWhichTab("accounts")}
>
User Accounts
</a>
</li>
</ul>
<div
className={
whichTab == "storage"
? "govuk-tabs__panel "
: "govuk-tabs__panel govuk-tabs__panel--hidden"
}
id="storage"
>
<h1>Storage Account Contents</h1>
{/* Toggle buttons */} {/* Toggle buttons */}
<div style={{ marginBottom: "0.2rem" }}> <div style={{ marginBottom: "0.2rem" }}>
<button <button
className="govuk-button govuk-button--secondary" className="govuk-button govuk-button--secondary"
onClick={() => setShowRepJson(!showRepJson)} onClick={() =>
> setShowRepJson(!showRepJson)
{showRepJson ? "Hide Reps" : "Show Reps"} }
</button> >
<button {showRepJson
className="govuk-button govuk-button--secondary" ? "Hide Reps"
onClick={() => setShowTmpFile(!showTmpFile)} : "Show Reps"}
style={{ marginLeft: "0.5rem" }} </button>
> <button
{showTmpFile className="govuk-button govuk-button--secondary"
? "Hide Temp Appeal" onClick={() =>
: "Show Temp Appeal"} setShowTmpFile(!showTmpFile)
</button> }
<button style={{ marginLeft: "0.5rem" }}
className="govuk-button govuk-button--secondary" >
onClick={toggleAll} {showTmpFile
style={{ marginLeft: "0.5rem" }} ? "Hide Temp Appeal"
> : "Show Temp Appeal"}
Show/Hide All </button>
</button> <button
</div> className="govuk-button govuk-button--secondary"
onClick={toggleAll}
style={{ marginLeft: "0.5rem" }}
>
Show/Hide All
</button>
</div>
{data.map((user) => ( {data.map((user) => (
<div key={user.id} className="govuk-grid-row"> <div
{user.containers.map((c) => ( key={user.id}
<div key={c.container} className="card"> className="govuk-grid-row"
<div className="card-body"> >
<h3> {user.containers.map((c) => (
{c.container} - {user.email} <div
</h3> key={c.container}
{c.folders.map((folder) => ( className="card"
<div >
key={folder.folderPath} <div className="card-body">
style={ <h3>
showRepJson && {c.container} -{" "}
folder.hasRepJson <Link
? { href={
display: "/auth/signin2?email=" +
"block", user.email
} }
: showTmpFile && >
folder.hasTmpFile {user.email}
? { </Link>
display: </h3>
"block", {c.folders.map((folder) => (
} <div
: { key={
display: folder.folderPath
"none", }
} style={
} showRepJson &&
> folder.hasRepJson
<h4> ? {
{folder.folderPath || display:
"Root"} "block",
</h4> }
{/* {folder.hasRepJson && ( : showTmpFile &&
folder.hasTmpFile
? {
display:
"block",
}
: {
display:
"none",
}
}
>
<h4>
{folder.folderPath ||
"Root"}
</h4>
{/* {folder.hasRepJson && (
<p> <p>
rep.json file found:{" "} rep.json file found:{" "}
{folder.repJsonBlob.name} {folder.repJsonBlob.name}
</p> </p>
)} */} )} */}
{folder.hasTmpFile && ( {folder.hasTmpFile && (
<> <>
<button <button
onClick={() => { onClick={() => {
confirm( confirm(
"Do you want to remove this unsubmitted appeal " + "Do you want to remove this unsubmitted appeal " +
showTopThreeArr[ showTopThreeArr[
key key
] ]
.ticketnumber + .ticketnumber +
"?\n" + "?\n" +
t( t(
"case:do-you-want-to-delete-case-disclaimer-label" "case:do-you-want-to-delete-case-disclaimer-label"
) )
) && ) &&
(deleteCaseItem( (deleteCaseItem(
c.container, c.container,
folder.folderPath folder.folderPath
), ),
alert( alert(
"Temp case Deleted!" "Temp case Deleted!"
), ),
window.location.reload()); window.location.reload());
}} }}
>
Delete this TMP
appeal
</button>
</>
)}
{folder.hasRepJson && (
<div>
{" "}
<button
title={
"Do you want to delete rep"
}
className=" cardModuleRemoveCase govuk-link govuk-link--no-underline govuk-link--inverse "
onClick={() => {
confirm(
"Do you want tyo delete rep " +
folder.repJsonBlob.name.split(
"/"
)[0] +
"?\n"
) &&
(deleteRepItem(
c.container,
folder.repJsonBlob.name.split(
"/"
)[0],
folder.repJsonBlob.name.split(
"/"
)[1]
),
alert(
"Rep Deleted!"
),
window.location.reload());
}}
>
Delete Rep
</button>
{folder.repComplete && (
<button
onClick={async () => {
const response =
await fetch(
"/api/file/editRepJson",
{
method: "POST",
headers:
{
"Content-Type":
"application/json",
},
body: JSON.stringify(
{
container:
c.container,
blobName:
folder
.repJsonBlob
.name,
}
),
}
);
const result =
await response.json();
if (
response.ok
) {
alert(
"repComplete removed!"
),
window.location.reload();
// Optionally refresh the page or update UI state here
} else {
alert(
"Error: " +
result.error
);
}
}}
>
Remove
repComplete
</button>
)}
{folder.repComplete && (
<button
onClick={async () => {
confirm(
"Do you want resend the rep queue message " +
folder.repJsonBlob.name.split(
"/"
)[0] +
" --- " +
folder.repJsonBlob.name.split(
"/"
)[1]
) &&
(sendRepCompleteMessage(
c.container,
folder.repJsonBlob.name.split(
"/"
)[0],
folder.repJsonBlob.name.split(
"/"
)[1]
),
alert(
"rep queue message sent!"
),
window.location.reload());
}}
>
Recreate
Queue
Message
</button>
)}
</div>
)}
<ul>
{folder.blobs.map(
(blob) => (
<li
key={
blob.name
}
>
<Link
scroll={
false
}
href={
"/api/file/downloadblob?container=" +
c.container +
"&casefolderID=" +
folder.folderPath +
"&blobname=" +
blob.name
.substring(
blob.name.lastIndexOf(
"/"
) +
1
)
.trim() +
"&hash=" +
blob.hashedfilepath
}
className="govuk-body govuk-!-font-size-14 govuk-link"
> >
{ Delete
blob.name this TMP
}{" "} appeal
( </button>
{bytesToSize( </>
blob.contentLength || )}
blob.size {folder.hasRepJson && (
)} <div>
) {" "}
</Link>{" "} <button
{blob.lastModified && ( title={
<span className="govuk-!-font-size-14"> "Do you want to delete rep"
{/* { }
className=" cardModuleRemoveCase govuk-link govuk-link--no-underline govuk-link--inverse "
onClick={() => {
confirm(
"Do you want tyo delete rep " +
folder.repJsonBlob.name.split(
"/"
)[0] +
"?\n"
) &&
(deleteRepItem(
c.container,
folder.repJsonBlob.name.split(
"/"
)[0],
folder.repJsonBlob.name.split(
"/"
)[1]
),
alert(
"Rep Deleted!"
),
window.location.reload());
}}
>
Delete
Rep
</button>
{folder.repComplete && (
<button
onClick={async () => {
const response =
await fetch(
"/api/file/editRepJson",
{
method: "POST",
headers:
{
"Content-Type":
"application/json",
},
body: JSON.stringify(
{
container:
c.container,
blobName:
folder
.repJsonBlob
.name,
}
),
}
);
const result =
await response.json();
if (
response.ok
) {
alert(
"repComplete removed!"
),
window.location.reload();
// Optionally refresh the page or update UI state here
} else {
alert(
"Error: " +
result.error
);
}
}}
>
Remove
repComplete
</button>
)}
{folder.repComplete && (
<button
onClick={async () => {
confirm(
"Do you want resend the rep queue message " +
folder.repJsonBlob.name.split(
"/"
)[0] +
" --- " +
folder.repJsonBlob.name.split(
"/"
)[1]
) &&
(sendRepCompleteMessage(
c.container,
folder.repJsonBlob.name.split(
"/"
)[0],
folder.repJsonBlob.name.split(
"/"
)[1]
),
alert(
"rep queue message sent!"
),
window.location.reload());
}}
>
Recreate
Queue
Message
</button>
)}
</div>
)}
<ul>
{folder.blobs.map(
(blob) => (
<li
key={
blob.name
}
>
<Link
scroll={
false
}
href={
"/api/file/downloadblob?container=" +
c.container +
"&casefolderID=" +
folder.folderPath +
"&blobname=" +
blob.name
.substring(
blob.name.lastIndexOf(
"/"
) +
1
)
.trim() +
"&hash=" +
blob.hashedfilepath
}
className="govuk-body govuk-!-font-size-14 govuk-link"
>
{
blob.name
}{" "}
(
{bytesToSize(
blob.contentLength ||
blob.size
)}
)
</Link>{" "}
{blob.lastModified && (
<span className="govuk-!-font-size-14">
{/* {
blob.displayDate blob.displayDate
} */} } */}
{formatBlobDates( {formatBlobDates(
blob blob
)}
</span>
)} )}
</span> </li>
)} )
</li> )}
) </ul>
)} </div>
</ul> ))}
</div> </div>
))} </div>
</div> ))}
</div> </div>
))} ))}
</div> </div>
))} <div
className={
whichTab == "accounts"
? "govuk-tabs__panel "
: "govuk-tabs__panel govuk-tabs__panel--hidden"
}
id="accounts"
>
<h1>User Accounts </h1>
<p>
Number of accounts created:{" "}
{userData.length}
</p>
<dl className=" govuk-summary-list govuk-!-margin-bottom-9 results">
<div class="govuk-summary-list__row results-headings">
<dd className="govuk-summary-list__key govuk-!-font-size-16 ">
Name
</dd>
<dd className="govuk-summary-list__key govuk-!-font-size-16">
Last accessed
</dd>
</div>
{userData.map((user) => (
<div
key={user.id}
className=" govuk-summary-list__row"
>
<dd className="govuk-summary-list__value govuk-!-font-size-16 ">
<Link
target="_blank"
href={
"/auth/signin2?email=" +
user.email
}
>
{user.email}
</Link>
</dd>
<dd className="govuk-summary-list__value govuk-!-font-size-16 ">
Last accessed: {user.created}{" "}
</dd>
</div>
))}
</dl>
</div>
</div>
</div> </div>
</div> </div>
<Footer /> <Footer />
@@ -587,5 +702,15 @@ export async function getServerSideProps({ req }) {
}) })
); );
return { props: { data: data.filter(Boolean) } }; let userData = await Promise.all(
users
.filter((user) => user.email && user.email.trim() !== "")
.map(async (user) => ({
id: user.id,
email: user.email,
created: user.emailVerified?.toLocaleString("en-GB") || "",
}))
);
return { props: { data: data.filter(Boolean), userData: userData } };
} }