diff --git a/tools/bulk/index.html b/tools/bulk/index.html
index f04f846a..98b3f3ac 100644
--- a/tools/bulk/index.html
+++ b/tools/bulk/index.html
@@ -12,7 +12,7 @@
-
+
diff --git a/tools/bulk/scripts.js b/tools/bulk/scripts.js
index 0f889fd3..bcaf752c 100644
--- a/tools/bulk/scripts.js
+++ b/tools/bulk/scripts.js
@@ -1,8 +1,14 @@
const log = document.getElementById('logger');
const adminVersion = new URLSearchParams(window.location.search).get('hlx-admin-version');
const adminVersionSuffix = adminVersion ? `?hlx-admin-version=${adminVersion}` : '';
+const simpleJobStatus = { data: { resources: [] }, links: { job: '' } };
+let startTime = null;
+let total = 0;
+let operation = '';
+let slow = false;
+let forceUpdate = false;
-const append = (string, status = 'unknown') => {
+const logAppend = (string, status = 'unknown') => {
const p = document.createElement('p');
p.textContent = string;
if (status !== 'unknown') {
@@ -13,6 +19,60 @@ const append = (string, status = 'unknown') => {
return p;
};
+function updateBulkStatus(element, jobStatus) {
+ const getProgressSummary = () => {
+ const progress = {
+ processed: 0,
+ success: 0,
+ warnings: 0,
+ errors: 0,
+ };
+ if (jobStatus.data && jobStatus.data.resources) {
+ jobStatus.data.resources.forEach((res) => {
+ if (res.status) progress.processed += 1;
+ if (res.status === 200 || res.status === 204) {
+ progress.success += 1;
+ } else if (res.status === 404) {
+ progress.warnings += 1;
+ } else if (res.status >= 500) {
+ progress.errors += 1;
+ }
+ });
+ }
+ return progress;
+ };
+
+ const {
+ processed,
+ success,
+ warnings,
+ errors,
+ } = getProgressSummary(jobStatus);
+ const VERB = {
+ preview: 'previewed',
+ live: 'published',
+ unpreview: 'unpreviewed',
+ unpublish: 'unpublished',
+ index: 'indexed',
+ cache: 'purged',
+ };
+ const [, , owner, repo] = new URL(jobStatus.links.job).pathname.split('/');
+ element.innerHTML = `${success} ${warnings} ${errors} ${processed}/${total} URL(s) bulk ${VERB[operation]} [${((new Date().valueOf() - startTime.valueOf()) / 1000).toFixed(2)}s]
+ `;
+ element.querySelector('.status-success').addEventListener('click', () => {
+ element.querySelector('.status-details').setAttribute('aria-expanded', 'true');
+ element.querySelector('.status-details textarea').textContent = jobStatus.data.resources.filter((r) => r.status === 200 || r.status === 204).map((r) => `https://main--${repo}--${owner}.aem.page${r.path}`).join('\n');
+ });
+ element.querySelector('.status-warning').addEventListener('click', () => {
+ element.querySelector('.status-details').setAttribute('aria-expanded', 'true');
+ element.querySelector('.status-details textarea').textContent = jobStatus.data.resources.filter((r) => r.status === 404).map((r) => `https://main--${repo}--${owner}.aem.page${r.path}`).join('\n');
+ });
+ element.querySelector('.status-error').addEventListener('click', () => {
+ element.querySelector('.status-details').setAttribute('aria-expanded', 'true');
+ element.querySelector('.status-details textarea').textContent = jobStatus.data.resources.filter((r) => r.status >= 500).map((r) => `https://main--${repo}--${owner}.aem.page${r.path}`).join('\n');
+ });
+}
+
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
@@ -20,16 +80,17 @@ function sleep(ms) {
}
document.getElementById('urls-form').addEventListener('submit', async (e) => {
+ document.getElementById('urls-form').disabled = true;
+ startTime = new Date();
e.preventDefault();
- let counter = 0;
const urls = document.getElementById('urls').value
.split('\n')
.map((u) => u.trim())
.filter((u) => u.length > 0);
- const total = urls.length;
- const operation = document.getElementById('operation').dataset.value;
- const slow = document.getElementById('slow').checked;
- const forceUpdate = document.getElementById('force').checked;
+ total = urls.length;
+ operation = document.getElementById('operation').dataset.value;
+ slow = document.getElementById('slow').checked;
+ forceUpdate = document.getElementById('force').checked;
const executeOperation = async (url) => {
const { hostname, pathname } = new URL(url);
@@ -48,34 +109,33 @@ document.getElementById('urls-form').addEventListener('submit', async (e) => {
const resp = await fetch(adminURL, {
method,
});
- resp.text().then(() => {
- counter += 1;
- append(`${counter}/${total}: ${adminURL}`, resp.status);
- document.getElementById('total').textContent = `${counter}/${total}`;
+ simpleJobStatus.data.resources.push({
+ status: resp.status,
+ path: pathname,
});
};
- const dequeue = async () => {
+ const dequeue = async (bulkLog) => {
while (urls.length) {
const url = urls.shift();
// eslint-disable-next-line no-await-in-loop
await executeOperation(url, total);
+ updateBulkStatus(bulkLog, simpleJobStatus);
// eslint-disable-next-line no-await-in-loop
if (slow) await sleep(1500);
}
+ updateBulkStatus(bulkLog, simpleJobStatus);
+ document.getElementById('urls-form').disabled = false;
};
const doBulkOperation = async () => {
if (total > 0) {
- const VERB = {
- preview: 'preview',
- live: 'publish',
- };
const { hostname } = new URL(urls[0]); // use first URL to determine project details
const [branch, repo, owner] = hostname.split('.')[0].split('--');
- const bulkText = `$1/${total} URL(s) bulk ${VERB[operation]}ed on ${owner}/${repo} ${forceUpdate ? '(force update)' : ''}`;
- const bulkLog = append(bulkText.replace('$1', 0));
+ const bulkLog = logAppend('');
+ updateBulkStatus(bulkLog, { data: { resources: [] }, links: { job: `https://admin.hlx.page/job/${owner}/${repo}/main/simplejob` } });
const paths = urls.map((url) => new URL(url).pathname);
+
const bulkResp = await fetch(`https://admin.hlx.page/${operation}/${owner}/${repo}/${branch}/*${adminVersionSuffix}`, {
method: 'POST',
body: JSON.stringify({
@@ -87,41 +147,24 @@ document.getElementById('urls-form').addEventListener('submit', async (e) => {
},
});
if (!bulkResp.ok) {
- append(`Failed to bulk ${VERB[operation]} ${paths.length} URLs on ${origin}: ${await bulkResp.text()}`);
+ logAppend(`Failed on ${origin}: ${await bulkResp.text()}`);
} else {
const { job } = await bulkResp.json();
const { name } = job;
const jobStatusPoll = window.setInterval(async () => {
try {
- const jobResp = await fetch(`https://admin.hlx.page/job/${owner}/${repo}/${branch}/${VERB[operation]}/${name}/details`);
+ const verb = operation === 'live' ? 'publish' : operation;
+ const jobResp = await fetch(`https://admin.hlx.page/job/${owner}/${repo}/${branch}/${verb}/${name}/details`);
const jobStatus = await jobResp.json();
- const {
- state,
- progress: {
- processed = 0,
- } = {},
- startTime,
- stopTime,
- data: {
- resources = [],
- } = {},
- } = jobStatus;
- if (state === 'stopped') {
+ if (jobStatus.state === 'stopped') {
// job done, stop polling
window.clearInterval(jobStatusPoll);
- // show job summary
- resources.forEach((res) => append(`${res.path} (${res.status})`, res.status));
- bulkLog.textContent = bulkText.replace('$1', processed);
- const duration = (new Date(stopTime).valueOf()
- - new Date(startTime).valueOf()) / 1000;
- append(`bulk ${operation} completed in ${duration}s`);
- } else {
- // show job progress
- bulkLog.textContent = bulkText.replace('$1', processed);
}
+ updateBulkStatus(bulkLog, jobStatus);
} catch (error) {
// eslint-disable-next-line no-console
console.error(`failed to get status for job ${name}: ${error}`);
+ document.getElementById('urls-form').disabled = false;
window.clearInterval(jobStatusPoll);
}
}, 1000);
@@ -133,13 +176,16 @@ document.getElementById('urls-form').addEventListener('submit', async (e) => {
// use bulk preview/publish API
doBulkOperation(urls);
} else {
- append(`URLs: ${urls.length}`);
let concurrency = ['live', 'unpublish', 'unpreview'].includes(operation) ? 40 : 3;
if (slow) {
concurrency = 1;
}
+ const bulkLog = logAppend('');
+ const [, repo, owner] = new URL(urls[0]).hostname.split('--');
+ simpleJobStatus.data = { resources: [], links: { job: `https://admin.hlx.page/job/${owner}/${repo}/main/simplejob` } };
+ updateBulkStatus(bulkLog, simpleJobStatus);
for (let i = 0; i < concurrency; i += 1) {
- dequeue(urls);
+ dequeue(bulkLog);
}
}
});
diff --git a/tools/bulk/styles.css b/tools/bulk/styles.css
index 30a5a179..58276881 100644
--- a/tools/bulk/styles.css
+++ b/tools/bulk/styles.css
@@ -40,4 +40,42 @@
.bulk-ops .status-light.http3::before {
color: var(--yellow-900);
-}
\ No newline at end of file
+}
+
+.status-pill {
+ display: inline-block;
+ padding: 0.5em 1em;
+ border-radius: 1.5em;
+ min-width: 5em;
+ color: #fff;
+ text-align: center;
+}
+
+.status-success {
+ background-color: var(--green-900);
+}
+
+.status-warning {
+ background-color: var(--yellow-900);
+}
+
+.status-error {
+ background-color: var(--red-900);
+}
+
+.status-details[aria-expanded="false"] {
+ display: none;
+}
+
+.status-details[aria-expanded="true"] {
+ margin-top: 1em;
+ display: block;
+}
+
+.status-details textarea {
+ width: 100%;
+ height: 400px;
+ font-family: var(--code-font-family);
+ font-size: var(--body-size-s);
+}
+