Merged PR 1818: home page content update and storage admin updates
Related work items: #17346
This commit is contained in:
+79
-2
@@ -1693,14 +1693,91 @@ export const listBlobHierarchical = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function formatTimeSince(date) {
|
||||||
|
if (!date) return "Unknown date";
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const diffMs = now - date;
|
||||||
|
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (diffDays < 7) {
|
||||||
|
return `${diffDays} day${diffDays !== 1 ? "s" : ""} ago`;
|
||||||
|
} else {
|
||||||
|
const diffWeeks = Math.floor(diffDays / 7);
|
||||||
|
return `${diffWeeks} week${diffWeeks !== 1 ? "s" : ""} ago`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateDisplay(date) {
|
||||||
|
return date
|
||||||
|
? `${date.toLocaleString("en-GB")} (${formatTimeSince(date)})`
|
||||||
|
: "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()) {
|
||||||
// blobNames.push(blob.name);
|
const lastModifiedDate = blob.properties?.lastModified || null;
|
||||||
|
const createdOnDate = blob.properties?.createdOn || null;
|
||||||
|
|
||||||
|
let displayDate = "";
|
||||||
|
|
||||||
|
if (createdOnDate && lastModifiedDate) {
|
||||||
|
if (
|
||||||
|
createdOnDate.toDateString() === lastModifiedDate.toDateString()
|
||||||
|
) {
|
||||||
|
// Same day — show just one
|
||||||
|
displayDate = `Date: ${formatDateDisplay(lastModifiedDate)}`;
|
||||||
|
} else {
|
||||||
|
// Different days — show both
|
||||||
|
displayDate = `Created: ${formatDateDisplay(
|
||||||
|
createdOnDate
|
||||||
|
)}, Modified: ${formatDateDisplay(lastModifiedDate)}`;
|
||||||
|
}
|
||||||
|
} else if (lastModifiedDate) {
|
||||||
|
displayDate = `Modified: ${formatDateDisplay(lastModifiedDate)}`;
|
||||||
|
} else if (createdOnDate) {
|
||||||
|
displayDate = `Created: ${formatDateDisplay(createdOnDate)}`;
|
||||||
|
} else {
|
||||||
|
displayDate = "Unknown date";
|
||||||
|
}
|
||||||
|
|
||||||
blobNames.push({
|
blobNames.push({
|
||||||
name: blob.name,
|
name: blob.name,
|
||||||
size: blob.properties.contentLength || 0, // in bytes
|
size: blob.properties.contentLength || 0,
|
||||||
|
lastModified: lastModifiedDate
|
||||||
|
? lastModifiedDate.toISOString()
|
||||||
|
: null,
|
||||||
|
createdOn: createdOnDate ? createdOnDate.toISOString() : null,
|
||||||
|
displayDate,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return blobNames;
|
return blobNames;
|
||||||
|
|||||||
@@ -8,17 +8,7 @@ export default function CaseComment() {
|
|||||||
const { locale } = router;
|
const { locale } = router;
|
||||||
return (
|
return (
|
||||||
<div id="casescomment-card">
|
<div id="casescomment-card">
|
||||||
<div className="card-body card-body govuk-!-padding-top-0">
|
<div className="card-body card-body govuk-!-padding-top-4">
|
||||||
<h3 className="heading-small card-heading">
|
|
||||||
{t("home:casescomment-card-title")}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<p className="gouk-body-s">
|
|
||||||
{t("home:casescomment-card-paragraph-three")}
|
|
||||||
</p>
|
|
||||||
<p className="gouk-body-s">
|
|
||||||
{t("home:casescomment-card-paragraph-four")}
|
|
||||||
</p>
|
|
||||||
<p className="govuk-body-s">
|
<p className="govuk-body-s">
|
||||||
{t("home:casescomment-card-paragraph-one")}{" "}
|
{t("home:casescomment-card-paragraph-one")}{" "}
|
||||||
<a
|
<a
|
||||||
@@ -28,15 +18,7 @@ export default function CaseComment() {
|
|||||||
{t("home:login-contact-email")}
|
{t("home:login-contact-email")}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p className="govuk-body-s">
|
|
||||||
{t("home:casescomment-card-paragraph-two")}
|
|
||||||
</p>
|
|
||||||
<h3 className="heading-small card-heading">
|
|
||||||
{t("home:watch-case-card-title")}
|
|
||||||
</h3>
|
|
||||||
<p className="govuk-body-s">
|
|
||||||
{t("home:watch-case-paragraph-one")}
|
|
||||||
</p>
|
|
||||||
{/* <p className="govuk-body-s">
|
{/* <p className="govuk-body-s">
|
||||||
{t("home:casescomment-card-paragraph-two")}
|
{t("home:casescomment-card-paragraph-two")}
|
||||||
</p> */}
|
</p> */}
|
||||||
|
|||||||
@@ -78,49 +78,24 @@ let Login = (props) => {
|
|||||||
<p className="govuk-body-s">
|
<p className="govuk-body-s">
|
||||||
{t("home:signin-benefits-paragraph")}
|
{t("home:signin-benefits-paragraph")}
|
||||||
</p>
|
</p>
|
||||||
<h3 className="heading-small card-heading">
|
<ul className="cardList-nobullet">
|
||||||
{/* {t("home:login-card-title")} */}
|
<li className="govuk-body-s">
|
||||||
{t("home:login-card-title-temp")}
|
{" "}
|
||||||
</h3>{" "}
|
<strong>
|
||||||
{/* <div className="govuk-form-group govuk-!-margin-top-4">
|
{" "}
|
||||||
<div className="govuk-body">
|
{t("home:login-card-title-temp")}{" "}
|
||||||
{session && "Signed in as " + session.user?.email}
|
</strong>{" "}
|
||||||
</div>
|
{t("home:login-comingsoon-temp-1")}
|
||||||
<br />
|
</li>
|
||||||
|
<li>
|
||||||
{!session && (
|
<strong>{t("home:casescomment-card-title")}</strong>{" "}
|
||||||
<button
|
{t("home:casescomment-card-paragraph-two")}
|
||||||
className="govuk-button withChevron govuk-button-cta"
|
</li>
|
||||||
type="button"
|
<li>
|
||||||
onClick={
|
<strong> {t("home:watch-case-card-title")}</strong>{" "}
|
||||||
() => signInButton()
|
{t("home:watch-case-paragraph-one")}
|
||||||
// signIn("email", {
|
</li>
|
||||||
// callbackUrl:
|
</ul>
|
||||||
// (router.locale != "en"
|
|
||||||
// ? "/cy"
|
|
||||||
// : "") + "/",
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t("home:login-via-nextuth-label")}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{session && (
|
|
||||||
<button
|
|
||||||
className="govuk-button"
|
|
||||||
type="button"
|
|
||||||
onClick={() => signOut()}
|
|
||||||
>
|
|
||||||
Magic Link Sign Out
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div> */}
|
|
||||||
<p className="govuk-body-s">
|
|
||||||
{t("home:login-comingsoon-temp-1")}{" "}
|
|
||||||
</p>
|
|
||||||
{/* <p className="govuk-body-s">
|
|
||||||
{t("home:login-comingsoon-temp-1a")}{" "}
|
|
||||||
</p> */}
|
|
||||||
<div className="govuk-form-group govuk-!-margin-top-4">
|
<div className="govuk-form-group govuk-!-margin-top-4">
|
||||||
<p className="govuk-body-s">
|
<p className="govuk-body-s">
|
||||||
{t("home:login-comingsoon-temp-2")}
|
{t("home:login-comingsoon-temp-2")}
|
||||||
@@ -178,9 +153,17 @@ let Login = (props) => {
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<p className="govuk-body-s">
|
||||||
|
{t("home:casescomment-card-paragraph-one")}{" "}
|
||||||
|
<a
|
||||||
|
href={"mailto:" + t("home:login-contact-email")}
|
||||||
|
className="govuk-link--no-underline"
|
||||||
|
>
|
||||||
|
{t("home:login-contact-email")}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CaseComment />
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"login-card-title-temp": "Cyflwyno apêl newydd",
|
"login-card-title-temp": "Cyflwyno apêl newydd",
|
||||||
"login-comingsoon-temp-1": "Mae'r gwasanaeth hwn yn cael ei ddatblygu i gefnogi apeliadau a cheisiadau ar-lein. Gallwch chi godi apeliadau cynllunio ar hyn o bryd.",
|
"login-comingsoon-temp-1": "yn erbyn gwrthodiad eich Awdurdod Cynllunio Lleol i roi caniatâd cynllunio neu fethiant i benderfynu ar gais cynllunio mewn pryd.",
|
||||||
"login-comingsoon-temp-1a": "Ar hyn o bryd, gallwch gyflwyno Apeliadau Cynllunio a78, Apeliadau Cydsyniad Adeilad Rhestredig ac Ardal Gadwraeth, Apeliadau Deiliaid Tai a Mân Apeliadau Masnachol - HAS/CAS, Apeliadau Hysbysebion a Barnu'n Annilys.",
|
"login-comingsoon-temp-1a": "Ar hyn o bryd, gallwch gyflwyno Apeliadau Cynllunio a78, Apeliadau Cydsyniad Adeilad Rhestredig ac Ardal Gadwraeth, Apeliadau Deiliaid Tai a Mân Apeliadau Masnachol - HAS/CAS, Apeliadau Hysbysebion a Barnu'n Annilys.",
|
||||||
"login-comingsoon-temp-2": "Wrth i ni ddatblygu’r gwasanaeth ar-lein, dewch o hyd i:",
|
"login-comingsoon-temp-2": "Mae'r gwasanaeth hwn ar agor ar hyn o bryd i gyflwyno apeliadau newydd yn erbyn gwrthod caniatâd cynllunio neu fethu â phenderfynu ar gais cynllunio mewn pryd (apeliadau adran 78). Mae'r gwasanaeth yn cael ei ddatblygu i gefnogi apeliadau a cheisiadau ar-lein eraill. Yn y cyfamser, gallwch ddod o hyd i'r ffurflenni yma",
|
||||||
"login-comingsoon-forms-link-1": "https://llyw.cymru/ffurflenni-apelio",
|
"login-comingsoon-forms-link-1": "https://llyw.cymru/ffurflenni-apelio",
|
||||||
"login-comingsoon-forms-label-1": "ffurflenni apêl cynllunio a ffurflenni cais cynllunio",
|
"login-comingsoon-forms-label-1": "ffurflenni apêl cynllunio a ffurflenni cais cynllunio",
|
||||||
"login-comingsoon-forms-link-2": "https://llyw.cymru/trwyddedau-amgylcheddol",
|
"login-comingsoon-forms-link-2": "https://llyw.cymru/trwyddedau-amgylcheddol",
|
||||||
@@ -32,9 +32,9 @@
|
|||||||
"casesearch-card-advanced-link": "Chwiliad uwch",
|
"casesearch-card-advanced-link": "Chwiliad uwch",
|
||||||
"casesearch-search-validation-required": "Angenrheidiol",
|
"casesearch-search-validation-required": "Angenrheidiol",
|
||||||
"casesearch-search-validation-minlength": "Rhaid iddo gynnwys 4 nod neu fwy",
|
"casesearch-search-validation-minlength": "Rhaid iddo gynnwys 4 nod neu fwy",
|
||||||
"casescomment-card-title": "Sylw ar apêl neu gais",
|
"casescomment-card-title": "Sylwadau ar apeliadau neu geisiadau",
|
||||||
"casescomment-card-paragraph-one": "Fel arall, gallwch wneud sylwadau ar apêl (ac eithrio apêl gan ddeiliad tŷ neu apêl fasnachol) drwy e-bostio ",
|
"casescomment-card-paragraph-one": "Fel arall, gallwch ofyn am ffurflenni neu wneud sylwadau ar apêl yn ystod y cyfnod cynrychioliadau drwy e-bostio ",
|
||||||
"casescomment-card-paragraph-two": "Ni all partïon â buddiant wneud sylwadau ar apeliadau deiliaid tai a masnachol yn ystod y cam apelio. Bydd unrhyw sylwadau a wnaed yn ystod y cam ymgeisio yn cael eu darparu gan yr awdurdod cynllunio lleol a'u hystyried gan yr Arolygydd.",
|
"casescomment-card-paragraph-two": "yn ystod y cyfnod cynrychioliadau neu gyflwyno datganiadau achos. Cyfeiriwch at y dyddiadau cau penodol i'r achos. Ar gyfer apeliadau deiliaid tai a masnachol, ni all partïon â diddordeb wneud sylwadau yn ystod y cam apelio. Bydd unrhyw sylwadau a wneir yn ystod y cam ymgeisio yn cael eu darparu gan yr Awdurdod Cynllunio Lleol a'u hystyried gan yr Arolygydd.",
|
||||||
"inspectorate-card-visit-label": "Ymweld â'r Arolygiaeth Gynllunio",
|
"inspectorate-card-visit-label": "Ymweld â'r Arolygiaeth Gynllunio",
|
||||||
"inspectorate-card-visit-link": "Hafan",
|
"inspectorate-card-visit-link": "Hafan",
|
||||||
"inspectorate-card-alt-text": "Yr Arolygiaeth Gynllunio",
|
"inspectorate-card-alt-text": "Yr Arolygiaeth Gynllunio",
|
||||||
@@ -48,9 +48,9 @@
|
|||||||
"completed-uploading-files-label": "{{number}} ffeil wedi'u huwchlwytho",
|
"completed-uploading-files-label": "{{number}} ffeil wedi'u huwchlwytho",
|
||||||
"remove-this-file-label": "Tynnu'r ffeil hon",
|
"remove-this-file-label": "Tynnu'r ffeil hon",
|
||||||
"watch-case-card-title": "Gwylio achos",
|
"watch-case-card-title": "Gwylio achos",
|
||||||
"watch-case-paragraph-one": "Os oes gennych ddiddordeb mewn achos, dewiswch ei wylio o'ch dangosfwrdd.",
|
"watch-case-paragraph-one": "i gadw golwg ar y camau nesaf a chanlyniad yr apêl.",
|
||||||
"casescomment-card-paragraph-three": "Gallwch wneud sylwadau ar apeliadau a cheisiadau yn ystod y cyfnod cynrychioli neu gyflwyno datganiadau achos neu'r Holiadur LPA.",
|
"casescomment-card-paragraph-three": "Gallwch wneud sylwadau ar apeliadau a cheisiadau yn ystod y cyfnod cynrychioli neu gyflwyno datganiadau achos neu'r Holiadur LPA.",
|
||||||
"casescomment-card-paragraph-four": "Cyfeiriwch at y dyddiadau cau ar gyfer cynrychioli achosion unigol ar gyfer datganiadau a sylwadau.",
|
"casescomment-card-paragraph-four": "Cyfeiriwch at y dyddiadau cau ar gyfer cynrychioli achosion unigol ar gyfer datganiadau a sylwadau.",
|
||||||
"signin-benefits-title": "Buddion mewngofnodi",
|
"signin-benefits-title": "Mewngofnodwch i'ch porth",
|
||||||
"signin-benefits-paragraph": "Mae cofrestru ar y porth Gwaith Achos Cynllunio yn caniatáu ichi:"
|
"signin-benefits-paragraph": "Drwy fewngofnodi gyda'r botwm Mewngofnodi, byddwch yn gallu:"
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"login-card-title-temp": "Submit a new appeal",
|
"login-card-title-temp": "Submit a new appeal",
|
||||||
"login-comingsoon-temp-1": "This service is being developed to support online appeals and applications. You can currently raise planning appeals.",
|
"login-comingsoon-temp-1": "against your Local Planning Authority’s refusal of planning permission or failure to determine a planning application in time.",
|
||||||
"login-comingsoon-temp-1a": "You can currently raise Planning Appeals s78.",
|
"login-comingsoon-temp-1a": "You can currently raise Planning Appeals s78.",
|
||||||
"login-comingsoon-temp-2": "While we develop the online service, find:",
|
"login-comingsoon-temp-2": "This service is currently open to submit new appeals against refusal of planning permission or failure to determine a planning application in time (section 78 appeals). The service is being developed to support other online appeals and applications. In the meantime, you can find the forms here",
|
||||||
"login-comingsoon-forms-link-1": "https://gov.wales/planning-appeal-forms",
|
"login-comingsoon-forms-link-1": "https://gov.wales/planning-appeal-forms",
|
||||||
"login-comingsoon-forms-label-1": "planning appeal and application forms",
|
"login-comingsoon-forms-label-1": "planning appeal and application forms",
|
||||||
"login-comingsoon-forms-link-2": "https://gov.wales/environmental-permits",
|
"login-comingsoon-forms-link-2": "https://gov.wales/environmental-permits",
|
||||||
@@ -32,9 +32,9 @@
|
|||||||
"casesearch-card-advanced-link": "Advanced search",
|
"casesearch-card-advanced-link": "Advanced search",
|
||||||
"casesearch-search-validation-required": "Required",
|
"casesearch-search-validation-required": "Required",
|
||||||
"casesearch-search-validation-minlength": "Must be 4 characters or more",
|
"casesearch-search-validation-minlength": "Must be 4 characters or more",
|
||||||
"casescomment-card-title": "Comment on an appeal or application",
|
"casescomment-card-title": "Comment on appeals or applications",
|
||||||
"casescomment-card-paragraph-one": "Alternatively you can comment on an appeal (excluding householder or commercial) by emailing ",
|
"casescomment-card-paragraph-one": "Alternatively, you can request forms or comment on an appeal during the representation period by emailing ",
|
||||||
"casescomment-card-paragraph-two": "For householder and commercial appeals, interested parties are unable to comment at appeal stage. Any comments made at application stage will be provided by the Local Planning Authority and considered by the inspector.",
|
"casescomment-card-paragraph-two": "during the representation period or submit statements of case. Please refer to the case specific deadlines. For householder and commercial appeals, interested parties are unable to comment at appeal stage. Any comments made at application stage will be provided by the Local Planning Authority and considered by the Inspector.",
|
||||||
"inspectorate-card-visit-label": "Visit the Planning Inspectorate",
|
"inspectorate-card-visit-label": "Visit the Planning Inspectorate",
|
||||||
"inspectorate-card-visit-link": "Home",
|
"inspectorate-card-visit-link": "Home",
|
||||||
"inspectorate-card-alt-text": "The Planning Inspectorate",
|
"inspectorate-card-alt-text": "The Planning Inspectorate",
|
||||||
@@ -48,9 +48,9 @@
|
|||||||
"completed-uploading-files-label": "{{number}} files uploaded",
|
"completed-uploading-files-label": "{{number}} files uploaded",
|
||||||
"remove-this-file-label": "Remove this file",
|
"remove-this-file-label": "Remove this file",
|
||||||
"watch-case-card-title": "Watch a case",
|
"watch-case-card-title": "Watch a case",
|
||||||
"watch-case-paragraph-one": "If you are interested in a case you select to watch it from your dashboard.",
|
"watch-case-paragraph-one": "to keep track of the next steps and outcome of the appeal.",
|
||||||
"casescomment-card-paragraph-three": "You can make comments on appeals and applications during the representation period or submit statements of case or the LPA Questionnaire.",
|
"casescomment-card-paragraph-three": "You can make comments on appeals and applications during the representation period or submit statements of case or the LPA Questionnaire.",
|
||||||
"casescomment-card-paragraph-four": "Please refer to the individual case representation deadlines for statements and comments.",
|
"casescomment-card-paragraph-four": "Please refer to the individual case representation deadlines for statements and comments.",
|
||||||
"signin-benefits-title": "Sign in benefits",
|
"signin-benefits-title": "Sign in to your portal",
|
||||||
"signin-benefits-paragraph": "Registering on the Planning Casework portal allows you to:"
|
"signin-benefits-paragraph": "By signing in with the Sign in button, you will be able to:"
|
||||||
}
|
}
|
||||||
+355
-272
@@ -1,3 +1,6 @@
|
|||||||
|
import CookieBanner from "../components/cookieBanner";
|
||||||
|
import Footer from "../components/footer";
|
||||||
|
import Header from "../components/header";
|
||||||
import { DefaultAzureCredential } from "@azure/identity";
|
import { DefaultAzureCredential } from "@azure/identity";
|
||||||
import { BlobServiceClient, ContainerClient } from "@azure/storage-blob";
|
import { BlobServiceClient, ContainerClient } from "@azure/storage-blob";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
@@ -7,6 +10,7 @@ import {
|
|||||||
listBlobHierarchical,
|
listBlobHierarchical,
|
||||||
listContainersForUser,
|
listContainersForUser,
|
||||||
} from "../actions/azurestorage";
|
} from "../actions/azurestorage";
|
||||||
|
import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
import {
|
import {
|
||||||
@@ -19,8 +23,10 @@ import {
|
|||||||
getIP,
|
getIP,
|
||||||
sendRepCompleteMessage,
|
sendRepCompleteMessage,
|
||||||
deleteMyRepresentationsFromBlob,
|
deleteMyRepresentationsFromBlob,
|
||||||
|
deleteAwaitingSubmissionsFromBlob,
|
||||||
} from "../actions";
|
} from "../actions";
|
||||||
import CryptoJS from "crypto-js";
|
import CryptoJS from "crypto-js";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
function getUpToLastSlash(url) {
|
function getUpToLastSlash(url) {
|
||||||
let lastSlash = url.lastIndexOf("/");
|
let lastSlash = url.lastIndexOf("/");
|
||||||
@@ -47,6 +53,48 @@ async function streamToString(readableStream) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatTimeSince(date) {
|
||||||
|
if (!date) return "Unknown date";
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const diffMs = now - date;
|
||||||
|
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
if (diffDays < 7) {
|
||||||
|
return `${diffDays} day${diffDays !== 1 ? "s" : ""} ago`;
|
||||||
|
} else {
|
||||||
|
const diffWeeks = Math.floor(diffDays / 7);
|
||||||
|
return `${diffWeeks} week${diffWeeks !== 1 ? "s" : ""} ago`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)})`;
|
||||||
|
}
|
||||||
|
|
||||||
const deleteRepItem = (containerID, caseID, repfile) => {
|
const deleteRepItem = (containerID, caseID, repfile) => {
|
||||||
deleteMyRepresentationsFromBlob(containerID, caseID, repfile).then(
|
deleteMyRepresentationsFromBlob(containerID, caseID, repfile).then(
|
||||||
(data) => {
|
(data) => {
|
||||||
@@ -56,185 +104,312 @@ const deleteRepItem = (containerID, caseID, repfile) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteCaseItem = (containerID, caseID) => {
|
||||||
|
deleteAwaitingSubmissionsFromBlob(containerID, caseID).then((data) => {
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export default function StoragePage({ data }) {
|
export default function StoragePage({ data }) {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
|
const [showRepJson, setShowRepJson] = useState(true);
|
||||||
|
const [showTmpFile, setShowTmpFile] = useState(true);
|
||||||
|
|
||||||
|
const toggleAll = () => {
|
||||||
|
const newState = !(showRepJson || showTmpFile);
|
||||||
|
setShowRepJson(newState);
|
||||||
|
setShowTmpFile(newState);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className="govuk-width-container">
|
<div>
|
||||||
<div className="govuk-main-wrapper govuk-main-wrapper--auto-spacing">
|
<Head>
|
||||||
<h1>Storage Account Contents</h1>
|
<title>Storage Account Browser</title>
|
||||||
{data.map((user) => (
|
</Head>
|
||||||
<div key={user.id} className="govuk-grid-row">
|
<div id="page_wrapper">
|
||||||
{user.containers.map((c) => (
|
<CookieBanner />
|
||||||
<div key={c.container} className="card">
|
<Header />
|
||||||
<div className="card-body">
|
<div className="govuk-width-container">
|
||||||
<h3>
|
<div className="govuk-main-wrapper govuk-main-wrapper--auto-spacing">
|
||||||
{c.container} - {user.email}
|
<h1>Storage Account Contents</h1>
|
||||||
</h3>
|
|
||||||
{c.folders.map((folder) => (
|
{/* Toggle buttons */}
|
||||||
<div key={folder.folderPath}>
|
<div style={{ marginBottom: "0.2rem" }}>
|
||||||
<h4>
|
<button
|
||||||
{folder.folderPath || "Root"}
|
className="govuk-button govuk-button--secondary"
|
||||||
</h4>
|
onClick={() => setShowRepJson(!showRepJson)}
|
||||||
{/* {folder.hasRepJson && (
|
>
|
||||||
|
{showRepJson ? "Hide Reps" : "Show Reps"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="govuk-button govuk-button--secondary"
|
||||||
|
onClick={() => setShowTmpFile(!showTmpFile)}
|
||||||
|
style={{ marginLeft: "0.5rem" }}
|
||||||
|
>
|
||||||
|
{showTmpFile
|
||||||
|
? "Hide Temp Appeal"
|
||||||
|
: "Show Temp Appeal"}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="govuk-button govuk-button--secondary"
|
||||||
|
onClick={toggleAll}
|
||||||
|
style={{ marginLeft: "0.5rem" }}
|
||||||
|
>
|
||||||
|
Show/Hide All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{data.map((user) => (
|
||||||
|
<div key={user.id} className="govuk-grid-row">
|
||||||
|
{user.containers.map((c) => (
|
||||||
|
<div key={c.container} className="card">
|
||||||
|
<div className="card-body">
|
||||||
|
<h3>
|
||||||
|
{c.container} - {user.email}
|
||||||
|
</h3>
|
||||||
|
{c.folders.map((folder) => (
|
||||||
|
<div
|
||||||
|
key={folder.folderPath}
|
||||||
|
style={
|
||||||
|
showRepJson &&
|
||||||
|
folder.hasRepJson
|
||||||
|
? {
|
||||||
|
display:
|
||||||
|
"block",
|
||||||
|
}
|
||||||
|
: 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.hasRepJson && (
|
{folder.hasTmpFile && (
|
||||||
<div>
|
<>
|
||||||
{" "}
|
<button
|
||||||
<button
|
onClick={() => {
|
||||||
title={
|
confirm(
|
||||||
"Do you want to delete rep"
|
"Do you want to remove this unsubmitted appeal " +
|
||||||
}
|
showTopThreeArr[
|
||||||
className=" cardModuleRemoveCase govuk-link govuk-link--no-underline govuk-link--inverse "
|
key
|
||||||
onClick={() => {
|
]
|
||||||
confirm(
|
.ticketnumber +
|
||||||
"Do you want tyo delete rep " +
|
"?\n" +
|
||||||
folder.repJsonBlob.name.split(
|
t(
|
||||||
"/"
|
"case:do-you-want-to-delete-case-disclaimer-label"
|
||||||
)[0] +
|
)
|
||||||
"?\n"
|
) &&
|
||||||
) &&
|
(deleteCaseItem(
|
||||||
(deleteRepItem(
|
c.container,
|
||||||
c.container,
|
folder.folderPath
|
||||||
folder.repJsonBlob.name.split(
|
),
|
||||||
"/"
|
alert(
|
||||||
)[0],
|
"Temp case Deleted!"
|
||||||
folder.repJsonBlob.name.split(
|
),
|
||||||
"/"
|
window.location.reload());
|
||||||
)[1]
|
}}
|
||||||
),
|
>
|
||||||
alert(
|
Delete this TMP
|
||||||
"Rep Deleted!"
|
appeal
|
||||||
),
|
</button>
|
||||||
window.location.reload());
|
</>
|
||||||
}}
|
)}
|
||||||
>
|
{folder.hasRepJson && (
|
||||||
Delete Rep
|
<div>
|
||||||
</button>
|
{" "}
|
||||||
{folder.repComplete && (
|
<button
|
||||||
<button
|
title={
|
||||||
onClick={async () => {
|
"Do you want to delete rep"
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}}
|
className=" cardModuleRemoveCase govuk-link govuk-link--no-underline govuk-link--inverse "
|
||||||
>
|
onClick={() => {
|
||||||
Remove repComplete
|
confirm(
|
||||||
</button>
|
"Do you want tyo delete rep " +
|
||||||
)}
|
folder.repJsonBlob.name.split(
|
||||||
{folder.repComplete && (
|
"/"
|
||||||
<button
|
)[0] +
|
||||||
onClick={async () => {
|
"?\n"
|
||||||
confirm(
|
) &&
|
||||||
"Do you want resend the rep queue message " +
|
(deleteRepItem(
|
||||||
folder.repJsonBlob.name.split(
|
c.container,
|
||||||
"/"
|
folder.repJsonBlob.name.split(
|
||||||
)[0] +
|
"/"
|
||||||
" --- " +
|
)[0],
|
||||||
folder.repJsonBlob.name.split(
|
folder.repJsonBlob.name.split(
|
||||||
"/"
|
"/"
|
||||||
)[1]
|
)[1]
|
||||||
) &&
|
),
|
||||||
(sendRepCompleteMessage(
|
alert(
|
||||||
c.container,
|
"Rep Deleted!"
|
||||||
folder.repJsonBlob.name.split(
|
),
|
||||||
"/"
|
window.location.reload());
|
||||||
)[0],
|
}}
|
||||||
folder.repJsonBlob.name.split(
|
>
|
||||||
"/"
|
Delete Rep
|
||||||
)[1]
|
</button>
|
||||||
),
|
{folder.repComplete && (
|
||||||
alert(
|
<button
|
||||||
"rep queue message sent!"
|
onClick={async () => {
|
||||||
),
|
const response =
|
||||||
window.location.reload());
|
await fetch(
|
||||||
}}
|
"/api/file/editRepJson",
|
||||||
>
|
{
|
||||||
Recreate Queue
|
method: "POST",
|
||||||
Message
|
headers:
|
||||||
</button>
|
{
|
||||||
)}
|
"Content-Type":
|
||||||
</div>
|
"application/json",
|
||||||
)}
|
},
|
||||||
|
body: JSON.stringify(
|
||||||
<ul>
|
{
|
||||||
{folder.blobs.map((blob) => (
|
container:
|
||||||
<li key={blob.name}>
|
c.container,
|
||||||
<Link
|
blobName:
|
||||||
scroll={false}
|
folder
|
||||||
href={
|
.repJsonBlob
|
||||||
"/api/file/downloadblob?container=" +
|
.name,
|
||||||
c.container +
|
}
|
||||||
"&casefolderID=" +
|
),
|
||||||
folder.folderPath +
|
}
|
||||||
"&blobname=" +
|
);
|
||||||
blob.name
|
const result =
|
||||||
.substring(
|
await response.json();
|
||||||
blob.name.lastIndexOf(
|
if (
|
||||||
"/"
|
response.ok
|
||||||
) + 1
|
) {
|
||||||
)
|
alert(
|
||||||
.trim() +
|
"repComplete removed!"
|
||||||
"&hash=" +
|
),
|
||||||
blob.hashedfilepath
|
window.location.reload();
|
||||||
}
|
// Optionally refresh the page or update UI state here
|
||||||
className="govuk-body govuk-!-font-size-14 govuk-!-padding-left-5 govuk-link"
|
} else {
|
||||||
>
|
alert(
|
||||||
{blob.name} (
|
"Error: " +
|
||||||
{bytesToSize(
|
result.error
|
||||||
blob.contentLength ||
|
);
|
||||||
blob.size
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
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
|
||||||
|
} */}
|
||||||
|
{formatBlobDates(
|
||||||
|
blob
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
)
|
)
|
||||||
</Link>
|
)}
|
||||||
</li>
|
</ul>
|
||||||
))}
|
</div>
|
||||||
</ul>
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</div>
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
|
<Footer />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -299,112 +474,6 @@ export async function getServerSideProps({ req }) {
|
|||||||
user.email
|
user.email
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add hashedName for each blob
|
|
||||||
// const containersWithHashes = containers.map((container) => {
|
|
||||||
// console.log("eee ");
|
|
||||||
|
|
||||||
// const groupedByFolder = container.blobs.reduce((acc, blob) => {
|
|
||||||
// // Get folder path - everything before last slash, or "" if no slash
|
|
||||||
// const folderPath = getUpToLastSlash(blob.name.trim()) || "";
|
|
||||||
|
|
||||||
// const hashedfilepath = hashAPIPath(
|
|
||||||
// "/api/file/downloadblob?container=" +
|
|
||||||
// container.container +
|
|
||||||
// "&casefolderID=" +
|
|
||||||
// folderPath +
|
|
||||||
// "&blobname=" +
|
|
||||||
// blob.name
|
|
||||||
// .substring(blob.name.lastIndexOf("/") + 1)
|
|
||||||
// .trim(),
|
|
||||||
// process.env.HASHKEY
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const blobWithHash = { ...blob, hashedfilepath };
|
|
||||||
|
|
||||||
// // Find or create folder group
|
|
||||||
// let folderGroup = acc.find(
|
|
||||||
// (f) => f.folderPath === folderPath
|
|
||||||
// );
|
|
||||||
// if (!folderGroup) {
|
|
||||||
// folderGroup = { folderPath, blobs: [] };
|
|
||||||
// acc.push(folderGroup);
|
|
||||||
// }
|
|
||||||
// folderGroup.blobs.push(blobWithHash);
|
|
||||||
|
|
||||||
// return acc;
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
// console.log(groupedByFolder);
|
|
||||||
// return {
|
|
||||||
// ...container,
|
|
||||||
// blobs: container.blobs.map((blob) => ({
|
|
||||||
// ...blob,
|
|
||||||
// hashedfilepath: hashAPIPath(
|
|
||||||
// "/api/file/downloadblob?container=" +
|
|
||||||
// container.container +
|
|
||||||
// "&casefolderID=" +
|
|
||||||
// getUpToLastSlash(blob.name.trim()) +
|
|
||||||
// "&blobname=" +
|
|
||||||
// blob.name
|
|
||||||
// .substring(blob.name.lastIndexOf("/") + 1)
|
|
||||||
// .trim(),
|
|
||||||
// process.env.HASHKEY
|
|
||||||
// ),
|
|
||||||
// })),
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const containersWithHashes = containers.map((container) => {
|
|
||||||
// const groupedByFolder = container.blobs.reduce((acc, blob) => {
|
|
||||||
// const folderPath = getUpToLastSlash(blob.name.trim()) || "";
|
|
||||||
|
|
||||||
// const hashedfilepath = hashAPIPath(
|
|
||||||
// "/api/file/downloadblob?container=" +
|
|
||||||
// container.container +
|
|
||||||
// "&casefolderID=" +
|
|
||||||
// folderPath +
|
|
||||||
// "&blobname=" +
|
|
||||||
// blob.name
|
|
||||||
// .substring(blob.name.lastIndexOf("/") + 1)
|
|
||||||
// .trim(),
|
|
||||||
// process.env.HASHKEY
|
|
||||||
// );
|
|
||||||
|
|
||||||
// const blobWithHash = { ...blob, hashedfilepath };
|
|
||||||
|
|
||||||
// let folderGroup = acc.find(
|
|
||||||
// (f) => f.folderPath === folderPath
|
|
||||||
// );
|
|
||||||
// if (!folderGroup) {
|
|
||||||
// folderGroup = {
|
|
||||||
// folderPath,
|
|
||||||
// blobs: [],
|
|
||||||
// hasRepJson: false, // flag
|
|
||||||
// repJsonBlob: null, // save the rep.json blob if found
|
|
||||||
// };
|
|
||||||
// acc.push(folderGroup);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// folderGroup.blobs.push(blobWithHash);
|
|
||||||
|
|
||||||
// // Check if this blob ends with "rep.json"
|
|
||||||
// if (blob.name.toLowerCase().endsWith("rep.json")) {
|
|
||||||
// folderGroup.hasRepJson = true;
|
|
||||||
// folderGroup.repJsonBlob = blobWithHash;
|
|
||||||
// // You can also do some processing here if you want
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return acc;
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// ...container,
|
|
||||||
// folders: groupedByFolder,
|
|
||||||
// // Optionally remove flat blobs if you don't want them
|
|
||||||
// // blobs: undefined,
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
|
|
||||||
const containersWithHashes = await Promise.all(
|
const containersWithHashes = await Promise.all(
|
||||||
containers.map(async (container) => {
|
containers.map(async (container) => {
|
||||||
const groupedByFolder = await container.blobs.reduce(
|
const groupedByFolder = await container.blobs.reduce(
|
||||||
@@ -429,7 +498,16 @@ export async function getServerSideProps({ req }) {
|
|||||||
process.env.HASHKEY
|
process.env.HASHKEY
|
||||||
);
|
);
|
||||||
|
|
||||||
const blobWithHash = { ...blob, hashedfilepath };
|
const fileNameOnly = blob.name.substring(
|
||||||
|
blob.name.lastIndexOf("/") + 1
|
||||||
|
);
|
||||||
|
const isTmpFile = fileNameOnly.startsWith("TMP-");
|
||||||
|
|
||||||
|
const blobWithHash = {
|
||||||
|
...blob,
|
||||||
|
hashedfilepath,
|
||||||
|
isTmpFile,
|
||||||
|
};
|
||||||
|
|
||||||
let folderGroup = acc.find(
|
let folderGroup = acc.find(
|
||||||
(f) => f.folderPath === folderPath
|
(f) => f.folderPath === folderPath
|
||||||
@@ -440,13 +518,18 @@ export async function getServerSideProps({ req }) {
|
|||||||
blobs: [],
|
blobs: [],
|
||||||
hasRepJson: false,
|
hasRepJson: false,
|
||||||
repJsonBlob: null,
|
repJsonBlob: null,
|
||||||
repComplete: false, // new flag
|
repComplete: false,
|
||||||
|
hasTmpFile: false,
|
||||||
};
|
};
|
||||||
acc.push(folderGroup);
|
acc.push(folderGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
folderGroup.blobs.push(blobWithHash);
|
folderGroup.blobs.push(blobWithHash);
|
||||||
|
|
||||||
|
if (isTmpFile) {
|
||||||
|
folderGroup.hasTmpFile = true;
|
||||||
|
}
|
||||||
|
|
||||||
// If this blob ends with rep.json, fetch and parse JSON
|
// If this blob ends with rep.json, fetch and parse JSON
|
||||||
if (blob.name.toLowerCase().endsWith("rep.json")) {
|
if (blob.name.toLowerCase().endsWith("rep.json")) {
|
||||||
folderGroup.hasRepJson = true;
|
folderGroup.hasRepJson = true;
|
||||||
|
|||||||
@@ -329,4 +329,16 @@ div.cardModuleItem:last-of-type {
|
|||||||
// box-shadow: 0 2px 0 #0b0c0c;
|
// box-shadow: 0 2px 0 #0b0c0c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.card {
|
||||||
|
.cardList-nobullet {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 20px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user