Use JavaScript modules

This commit is contained in:
Martchus 2022-01-23 01:56:17 +01:00
parent 1bb8a55169
commit 2abc407c77
14 changed files with 409 additions and 330 deletions

View File

@ -13,26 +13,17 @@
<link rel="stylesheet" type="text/css" href="css/layout.css" /> <link rel="stylesheet" type="text/css" href="css/layout.css" />
<link rel="stylesheet" type="text/css" href="css/genericrendering.css" /> <link rel="stylesheet" type="text/css" href="css/genericrendering.css" />
<link rel="stylesheet" type="text/css" href="css/specifics.css" /> <link rel="stylesheet" type="text/css" href="css/specifics.css" />
<script src="js/ajaxhelper.js"></script> <script type="module" src="js/main.js"></script>
<script src="js/terminal.js"></script>
<script src="js/genericrendering.js"></script>
<script src="js/customrendering.js"></script>
<script src="js/globalstatuspage.js"></script>
<script src="js/packagesearchpage.js"></script>
<script src="js/packagedetailspage.js"></script>
<script src="js/buildactionspage.js"></script>
<script src="js/singlepage.js"></script>
<script src="js/utils.js"></script>
<!-- include xterm.js --> <!-- include xterm.js -->
<link rel="stylesheet" type="text/css" href="node_modules/xterm/css/xterm.css" /> <link rel="stylesheet" type="text/css" href="node_modules/xterm/css/xterm.css" />
<script src="node_modules/xterm/lib/xterm.js"></script> <script src="node_modules/xterm/lib/xterm.js"></script>
<script src="node_modules/xterm-addon-search/out/SearchAddon.js"></script> <script src="node_modules/xterm-addon-search/out/SearchAddon.js"></script>
</head> </head>
<body onload="initPage();" onhashchange="handleHashChange();"> <body>
<header> <header>
<nav> <nav>
<div> <div>
<a href="#" onclick="document.getElementById('about-dialog').style.display = 'block'; return false;"> <a href="#" id="logo-link">
<img src="img/logo.svg" alt="Logo" /> <img src="img/logo.svg" alt="Logo" />
</a> </a>
Repository Manager for Arch Linux<br /> Repository Manager for Arch Linux<br />
@ -65,7 +56,7 @@
</section> </section>
<section id="package-search-section"> <section id="package-search-section">
<h2>Package search</h2> <h2>Package search</h2>
<form id="package-search-form" onsubmit="searchForPackages(); return false;" action="/packages" method="GET"> <form id="package-search-form" action="/packages" method="GET">
<table class="form-row"> <table class="form-row">
<tr> <tr>
<th>Package name:</th> <th>Package name:</th>
@ -109,13 +100,13 @@
<fieldset> <fieldset>
<legend>Actions</legend> <legend>Actions</legend>
<div> <div>
<button type="button" style="background-image: url(img/icon/select-all.svg)" onclick="alterFormSelection(this.form, 'check-all');" class="icon-button"> <button type="button" name="selectall" style="background-image: url(img/icon/select-all.svg)" class="icon-button">
Select all packages Select all packages
</button> </button>
<button type="button" style="background-image: url(img/icon/select-off.svg)" onclick="alterFormSelection(this.form, 'uncheck-all');" class="icon-button"> <button type="button" name="unselectall" style="background-image: url(img/icon/select-off.svg)" class="icon-button">
Unselect all package Unselect all package
</button> </button>
<button type="button" style="background-image: url(img/icon/plus.svg)" onclick="fillBuildActionFromPackageSearch();" class="icon-button"> <button type="button" name="startselected" style="background-image: url(img/icon/plus.svg)" class="icon-button">
Start build action from selection Start build action from selection
</button> </button>
</div> </div>
@ -131,15 +122,13 @@
<section id="build-action-section"> <section id="build-action-section">
<h2> <h2>
Build actions Build actions
<span class="heading-actions"> <span class="heading-actions" id="build-action-toolbar">
<a href="#" title="Save state manually" <a href="#" title="Save state manually"
class="icon-link" class="icon-link"
onclick="return triggerToolbarAction(this);"
data-action="/dump/cache-file" data-action="/dump/cache-file"
data-method="POST"><img src="img/icon/content-save.svg" alt="Save state manually" class="icon" /></a> data-method="POST"><img src="img/icon/content-save.svg" alt="Save state manually" class="icon" /></a>
<a href="#" title="Stop service" <a href="#" title="Stop service"
class="icon-link" class="icon-link"
onclick="return triggerToolbarAction(this);"
data-action="/quit" data-action="/quit"
data-method="POST" data-method="POST"
data-confirmation="Do you really want to stop the service?"><img src="img/icon/power.svg" alt="Save state manually" class="icon" /></a> data-confirmation="Do you really want to stop the service?"><img src="img/icon/power.svg" alt="Save state manually" class="icon" /></a>
@ -152,22 +141,22 @@
<fieldset> <fieldset>
<legend>Modify selected actions</legend> <legend>Modify selected actions</legend>
<div> <div>
<button type="button" style="background-image: url(img/icon/select-all.svg)" onclick="alterFormSelection(this.form, 'check-all');" class="icon-button"> <button type="button" name="selectall" style="background-image: url(img/icon/select-all.svg)" class="icon-button">
Select all actions Select all actions
</button> </button>
<button type="button" style="background-image: url(img/icon/select-off.svg)" onclick="alterFormSelection(this.form, 'uncheck-all');" class="icon-button"> <button type="button" name="unselectall" style="background-image: url(img/icon/select-off.svg)" class="icon-button">
Unselect all actions Unselect all actions
</button> </button>
<button type="button" style="background-image: url(img/icon/magnify.svg)" onclick="showSelectedActions();" class="icon-button"> <button type="button" name="showselected" style="background-image: url(img/icon/magnify.svg)" class="icon-button">
Show selected actions Show selected actions
</button> </button>
<button type="button" style="background-image: url(img/icon/delete.svg)" onclick="deleteSelectedActions();" class="icon-button"> <button type="button" name="deleteselected" style="background-image: url(img/icon/delete.svg)" class="icon-button">
Delete selected actions Delete selected actions
</button> </button>
</div> </div>
</fieldset> </fieldset>
</form> </form>
<form id ="build-action-form" onsubmit="submitBuildAction(); return false;" action="/build-action" method="POST"> <form id ="build-action-form" action="/build-action" method="POST">
<fieldset> <fieldset>
<legend>Start new build action</legend> <legend>Start new build action</legend>
<div> <div>
@ -175,7 +164,7 @@
<div class="form-split-50"> <div class="form-split-50">
<label for="build-action-task">Predefined task:</label> <label for="build-action-task">Predefined task:</label>
<br /> <br />
<select name="task" id="build-action-task" onchange="handleBuildActionPresetChange();"> <select name="task" id="build-action-task">
<option id="build-action-task-none" data-ignore="1" style="font-style: italic;">None</option> <option id="build-action-task-none" data-ignore="1" style="font-style: italic;">None</option>
</select> </select>
</div> </div>
@ -186,7 +175,7 @@
<div class="form-split-50"> <div class="form-split-50">
<label for="build-action-type">Action:</label> <label for="build-action-type">Action:</label>
<br /> <br />
<select name="type" id="build-action-type" onchange="handleBuildActionTypeChange();"></select> <select name="type" id="build-action-type"></select>
</div> </div>
<div class="form-split-50"> <div class="form-split-50">
<label for="build-action-directory">Directory:</label> <label for="build-action-directory">Directory:</label>
@ -262,7 +251,7 @@
</section> </section>
</main> </main>
<div id="about-dialog" style="display: none;"> <div id="about-dialog" style="display: none;">
<a href="#" onclick="document.getElementById('about-dialog').style.display = 'none'; return false;" class="close-button">×</a> <a href="#" onclick="this.parentNode.style.display = 'none'; return false;" class="close-button">×</a>
<div id="about-text"> <div id="about-text">
<h1>Repository Manager for Arch Linux</h1> <h1>Repository Manager for Arch Linux</h1>
<p><img src="img/logo.svg" alt="Logo" /></p> <p><img src="img/logo.svg" alt="Logo" /></p>
@ -271,4 +260,4 @@
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View File

@ -1,8 +1,8 @@
const apiPrefix = 'api/v0'; export const apiPrefix = 'api/v0';
let authError = false; let authError = false;
/// \brief Makes an AJAX query with basic error handling. /// \brief Makes an AJAX query with basic error handling.
function queryRoute(method, path, callback) export function queryRoute(method, path, callback)
{ {
const ajaxRequest = new XMLHttpRequest(); const ajaxRequest = new XMLHttpRequest();
ajaxRequest.onreadystatechange = function() { ajaxRequest.onreadystatechange = function() {
@ -34,13 +34,13 @@ function queryRoute(method, path, callback)
} }
/// \brief Makes an AJAX query for the specified form. /// \brief Makes an AJAX query for the specified form.
function startFormQuery(formId, handler) export function startFormQuery(formId, handler)
{ {
return startFormQueryEx(formId, handler).ajaxRequest; return startFormQueryEx(formId, handler).ajaxRequest;
} }
/// \brief Makes an AJAX query for the specified form. /// \brief Makes an AJAX query for the specified form.
function startFormQueryEx(formId, handler) export function startFormQueryEx(formId, handler)
{ {
const form = document.getElementById(formId); const form = document.getElementById(formId);
const params = makeFormQueryParameter(form); const params = makeFormQueryParameter(form);
@ -105,7 +105,7 @@ function makeFormQueryParameter(form)
return "?" + params.join("&"); return "?" + params.join("&");
} }
function makeIdParams(ids) export function makeIdParams(ids)
{ {
if (!Array.isArray(ids)) { if (!Array.isArray(ids)) {
ids = [ids]; ids = [ids];
@ -114,7 +114,7 @@ function makeIdParams(ids)
} }
/// \brief Shows an alert for the specified AJAX request. /// \brief Shows an alert for the specified AJAX request.
function showAjaxError(xhr, action) export function showAjaxError(xhr, action)
{ {
let errorMessage; let errorMessage;
try { try {
@ -129,7 +129,7 @@ function showAjaxError(xhr, action)
} }
/// \brief Returns whether the specified AJAX request failed and shows an alert it it did. /// \brief Returns whether the specified AJAX request failed and shows an alert it it did.
function checkForAjaxError(xhr, action) export function checkForAjaxError(xhr, action)
{ {
if (xhr.status === 200) { if (xhr.status === 200) {
return false; return false;

View File

@ -1,9 +1,31 @@
function initBuildActionsForm() import * as AjaxHelper from './ajaxhelper.js';
import * as CustomRendering from './customrendering.js';
import * as GenericRendering from './genericrendering.js';
import * as SinglePageHelper from './singlepage.js';
import * as Terminal from './terminal.js';
import * as Utils from './utils.js';
export function initBuildActionsForm()
{ {
const buildActionsForm = document.getElementById('build-action-form'); const buildActionsForm = document.getElementById('build-action-form');
if (buildActionsForm.dataset.initialized) { if (buildActionsForm.dataset.initialized) {
return true; return true;
} }
buildActionsForm.onsubmit = submitBuildAction;
buildActionsForm.task.onchange = handleBuildActionPresetChange;
buildActionsForm.type.onchange = handleBuildActionTypeChange;
Array.from(document.getElementById('build-action-toolbar').getElementsByTagName('a')).forEach(a => {
a.onclick = triggerToolbarAction;
});
const listFormElements = document.getElementById('build-actions-list-form').elements;
listFormElements.selectall.onclick = function () {
Utils.alterFormSelection(this.form, 'check-all');
};
listFormElements.unselectall.onclick = function () {
Utils.alterFormSelection(this.form, 'uncheck-all');
};
listFormElements.showselected.onclick = showSelectedActions;
listFormElements.deleteselected.onclick = deleteSelectedActions;
queryBuildActions(); queryBuildActions();
handleBuildActionTypeChange(); handleBuildActionTypeChange();
buildActionsForm.dataset.initialized = true; buildActionsForm.dataset.initialized = true;
@ -12,21 +34,21 @@ function initBuildActionsForm()
function queryBuildActions() function queryBuildActions()
{ {
queryRoute('GET', '/build-action', showBuildActions); AjaxHelper.queryRoute('GET', '/build-action', showBuildActions);
return true; return true;
} }
function queryBuildActionDetails(ids) function queryBuildActionDetails(ids)
{ {
queryRoute('GET', '/build-action/details?' + makeIdParams(ids), showBuildActionDetails); AjaxHelper.queryRoute('GET', '/build-action/details?' + AjaxHelper.makeIdParams(ids), showBuildActionDetails);
return true; return true;
} }
function cloneBuildAction(ids) function cloneBuildAction(ids)
{ {
queryRoute('POST', '/build-action/clone?' + makeIdParams(ids), function (xhr, success) { AjaxHelper.queryRoute('POST', '/build-action/clone?' + AjaxHelper.makeIdParams(ids), function (xhr, success) {
if (!success) { if (!success) {
return showAjaxError(xhr, 'clone build action'); return AjaxHelper.showAjaxError(xhr, 'clone build action');
} }
const cloneIDs = JSON.parse(xhr.responseText); const cloneIDs = JSON.parse(xhr.responseText);
if (cloneIDs.length === 1 && typeof cloneIDs[0] === 'number') { if (cloneIDs.length === 1 && typeof cloneIDs[0] === 'number') {
@ -42,24 +64,24 @@ function cloneBuildAction(ids)
function deleteBuildAction(ids) function deleteBuildAction(ids)
{ {
queryRoute('DELETE', '/build-action?' + makeIdParams(ids), function (xhr, success) { AjaxHelper.queryRoute('DELETE', '/build-action?' + AjaxHelper.makeIdParams(ids), function (xhr, success) {
success ? window.alert('Build action has been deleted.') : showAjaxError(xhr, 'delete build action'); success ? window.alert('Build action has been deleted.') : AjaxHelper.showAjaxError(xhr, 'delete build action');
}); });
return true; return true;
} }
function stopBuildAction(ids) function stopBuildAction(ids)
{ {
queryRoute('POST', '/build-action/stop?' + makeIdParams(ids), function (xhr, success) { AjaxHelper.queryRoute('POST', '/build-action/stop?' + AjaxHelper.makeIdParams(ids), function (xhr, success) {
success ? window.alert('Build action has been stopped.') : showAjaxError(xhr, 'stop build action'); success ? window.alert('Build action has been stopped.') : AjaxHelper.showAjaxError(xhr, 'stop build action');
}); });
return true; return true;
} }
function startBuildAction(ids) function startBuildAction(ids)
{ {
queryRoute('POST', '/build-action/start?' + makeIdParams(ids), function (xhr, success) { AjaxHelper.queryRoute('POST', '/build-action/start?' + AjaxHelper.makeIdParams(ids), function (xhr, success) {
success ? window.alert('Build action has been started.') : showAjaxError(xhr, 'start build action'); success ? window.alert('Build action has been started.') : AjaxHelper.showAjaxError(xhr, 'start build action');
}); });
return true; return true;
} }
@ -98,7 +120,7 @@ function prepareBuildActionBasedOnExistingOne(existingBuildAction)
}); });
} }
function handleBuildActionTypeChange() export function handleBuildActionTypeChange()
{ {
if (!globalInfo) { if (!globalInfo) {
return; return;
@ -129,14 +151,14 @@ function handleBuildActionTypeChange()
}); });
} }
function handleBuildActionPresetChange() export function handleBuildActionPresetChange()
{ {
if (!globalInfo) { if (!globalInfo) {
return; return;
} }
const task = document.getElementById('build-action-task').value; const task = document.getElementById('build-action-task').value;
const taskInfo = globalInfo.presets.tasks[task]; const taskInfo = globalInfo.presets.tasks[task];
const taskInfoElement = getAndEmptyElement('build-action-task-info'); const taskInfoElement = Utils.getAndEmptyElement('build-action-task-info');
const actionSelect = document.getElementById('build-action-type'); const actionSelect = document.getElementById('build-action-type');
if (!taskInfo) { if (!taskInfo) {
actionSelect.disabled = false; actionSelect.disabled = false;
@ -170,33 +192,33 @@ function renderBuildActionActions(actionValue, buildAction, refresh)
container.className = 'table-row-actions'; container.className = 'table-row-actions';
} }
const id = buildAction.id; const id = buildAction.id;
container.appendChild(renderIconLink(refresh ? 'table-refresh' : 'magnify', buildAction, function() { container.appendChild(CustomRendering.renderIconLink(refresh ? 'table-refresh' : 'magnify', buildAction, function() {
queryBuildActionDetails(id); queryBuildActionDetails(id);
return false; return false;
}, refresh ? 'Refresh details table' : 'Show details', undefined, '#build-action-details-section?' + id)); }, refresh ? 'Refresh details table' : 'Show details', undefined, '#build-action-details-section?' + id));
container.appendChild(renderIconLink('restart', buildAction, function() { container.appendChild(CustomRendering.renderIconLink('restart', buildAction, function() {
if (window.confirm('Do you really want to clone/restart action ' + id + '?')) { if (window.confirm('Do you really want to clone/restart action ' + id + '?')) {
cloneBuildAction(id); cloneBuildAction(id);
} }
}, 'Clone ' + id)); }, 'Clone ' + id));
container.appendChild(renderIconLink('plus', buildAction, function() { container.appendChild(CustomRendering.renderIconLink('plus', buildAction, function() {
prepareBuildActionBasedOnExistingOne(buildAction); prepareBuildActionBasedOnExistingOne(buildAction);
switchToBuildActions(); switchToBuildActions();
}, 'Create new build action based on ' + id)); }, 'Create new build action based on ' + id));
container.appendChild(renderIconLink('delete', buildAction, function() { container.appendChild(CustomRendering.renderIconLink('delete', buildAction, function() {
if (window.confirm('Do you really want to delete action ' + id + '?')) { if (window.confirm('Do you really want to delete action ' + id + '?')) {
deleteBuildAction(id); deleteBuildAction(id);
} }
}, 'Delete ' + id)); }, 'Delete ' + id));
if (buildAction.status !== 0 && buildAction.status !== 4) { if (buildAction.status !== 0 && buildAction.status !== 4) {
container.appendChild(renderIconLink('stop', buildAction, function() { container.appendChild(CustomRendering.renderIconLink('stop', buildAction, function() {
if (window.confirm('Do you really want to stop/decline action ' + id + '?')) { if (window.confirm('Do you really want to stop/decline action ' + id + '?')) {
stopBuildAction(id); stopBuildAction(id);
} }
}, 'Stop/decline ' + id)); }, 'Stop/decline ' + id));
} }
if (buildAction.status === 0) { if (buildAction.status === 0) {
container.appendChild(renderIconLink('play', buildAction, function() { container.appendChild(CustomRendering.renderIconLink('play', buildAction, function() {
if (window.confirm('Do you really want to start action ' + id + '?')) { if (window.confirm('Do you really want to start action ' + id + '?')) {
startBuildAction(id); startBuildAction(id);
} }
@ -211,16 +233,16 @@ function showBuildActions(ajaxRequest)
window.functionsPostponedUntilGlobalInfo.push(showBuildActions.bind(this, ...arguments)); window.functionsPostponedUntilGlobalInfo.push(showBuildActions.bind(this, ...arguments));
return; return;
} }
const buildActionsList = getAndEmptyElement('build-actions-list'); const buildActionsList = Utils.getAndEmptyElement('build-actions-list');
if (ajaxRequest.status !== 200) { if (ajaxRequest.status !== 200) {
buildActionsList.appendChild(document.createTextNode('Unable to load build actions: ' + ajaxRequest.responseText)); buildActionsList.appendChild(document.createTextNode('Unable to load build actions: ' + ajaxRequest.responseText));
buildActionsList.appendChild(renderReloadButton(queryBuildActions)); buildActionsList.appendChild(CustomRendering.renderReloadButton(queryBuildActions));
return; return;
} }
const responseJson = JSON.parse(ajaxRequest.responseText); const responseJson = JSON.parse(ajaxRequest.responseText);
if (!Array.isArray(responseJson)) { if (!Array.isArray(responseJson)) {
buildActionsList.appendChild(document.createTextNode('Unable to load build actions: response is no array')); buildActionsList.appendChild(document.createTextNode('Unable to load build actions: response is no array'));
buildActionsList.appendChild(renderReloadButton(queryBuildActions)); buildActionsList.appendChild(CustomRendering.renderReloadButton(queryBuildActions));
return; return;
} }
@ -285,7 +307,7 @@ function showBuildActions(ajaxRequest)
}; };
// render the table // render the table
const container = renderTableFromJsonArray({ const container = GenericRendering.renderTableFromJsonArray({
rows: responseJson, rows: responseJson,
rowsPerPage: 10, rowsPerPage: 10,
columnHeaders: ['', 'ID', 'Task', 'Type', 'Status', 'Result', 'Created', 'Started', 'Runtime', 'Directory', 'Source repo', 'Destination repo', 'Packages', 'Actions'], columnHeaders: ['', 'ID', 'Task', 'Type', 'Status', 'Result', 'Created', 'Started', 'Runtime', 'Directory', 'Source repo', 'Destination repo', 'Packages', 'Actions'],
@ -293,46 +315,46 @@ function showBuildActions(ajaxRequest)
columnSortAccessors: [null, null, null, null, null, null, '_cc'], columnSortAccessors: [null, null, null, null, null, null, '_cc'],
customRenderer: { customRenderer: {
checkbox: function(value, row) { checkbox: function(value, row) {
return renderCheckBoxForTableRow(value, row, function(row) { return GenericRendering.renderCheckBoxForTableRow(value, row, function(row) {
return row.name; return row.name;
}); });
}, },
actions: renderBuildActionActions, actions: renderBuildActionActions,
id: function(value, row) { id: function(value, row) {
return renderLink(value, row, function() { return GenericRendering.renderLink(value, row, function() {
queryBuildActionDetails(row.id); queryBuildActionDetails(row.id);
return false; return false;
}, 'Show details', undefined, '#build-action-details-section?' + row.id); }, 'Show details', undefined, '#build-action-details-section?' + row.id);
}, },
taskName: function (value) { taskName: function (value) {
if (!value) { if (!value) {
return renderNoneInGrey(); return GenericRendering.renderNoneInGrey();
} }
return document.createTextNode(getProperty(globalInfo.presets.tasks[value], 'name', value)); return document.createTextNode(Utils.getProperty(globalInfo.presets.tasks[value], 'name', value));
}, },
status: function(value) { status: function(value) {
return document.createTextNode(getProperty(globalInfo.buildActionStates[value], 'name', 'Invalid/unknown')); return document.createTextNode(Utils.getProperty(globalInfo.buildActionStates[value], 'name', 'Invalid/unknown'));
}, },
result: function(value) { result: function(value) {
return renderNoneInGrey(getProperty(globalInfo.buildActionResults[value], 'name', 'Invalid/unknown')); return GenericRendering.renderNoneInGrey(Utils.getProperty(globalInfo.buildActionResults[value], 'name', 'Invalid/unknown'));
}, },
type: function(value) { type: function(value) {
return document.createTextNode(getProperty(globalInfo.buildActionTypes[value], 'name', 'Invalid/debugging')); return document.createTextNode(Utils.getProperty(globalInfo.buildActionTypes[value], 'name', 'Invalid/debugging'));
}, },
created: renderShortTimeStamp, created: GenericRendering.renderShortTimeStamp,
started: renderShortTimeStamp, started: GenericRendering.renderShortTimeStamp,
finished: function (value, row) { finished: function (value, row) {
return renderTimeSpan(row.started, value); return GenericRendering.renderTimeSpan(row.started, value);
}, },
sourceDbs: renderArrayElidedAsCommaSeparatedString, sourceDbs: GenericRendering.renderArrayElidedAsCommaSeparatedString,
destinationDbs: renderArrayElidedAsCommaSeparatedString, destinationDbs: GenericRendering.renderArrayElidedAsCommaSeparatedString,
packageNames: function(value) { packageNames: function(value) {
return renderNoneInGrey(!Array.isArray(value) || value.length <= 0 ? 'none' : value.length); return GenericRendering.renderNoneInGrey(!Array.isArray(value) || value.length <= 0 ? 'none' : value.length);
}, },
note: function(rows) { note: function(rows) {
const note = document.createElement('p'); const note = document.createElement('p');
note.appendChild(document.createTextNode(rows.length + ' build actions present ')); note.appendChild(document.createTextNode(rows.length + ' build actions present '));
note.appendChild(renderReloadButton(queryBuildActions)); note.appendChild(CustomRendering.renderReloadButton(queryBuildActions));
return note; return note;
}, },
}, },
@ -396,15 +418,15 @@ function showBuildActions(ajaxRequest)
function switchToBuildActionDetails(buildActionIds) function switchToBuildActionDetails(buildActionIds)
{ {
sections['build-action-details'].state.ids = buildActionIds; SinglePageHelper.sections['build-action-details'].state.ids = buildActionIds;
updateHashPreventingSectionInitializer(!Array.isArray(buildActionIds) || buildActionIds.length === 0 SinglePageHelper.updateHashPreventingSectionInitializer(!Array.isArray(buildActionIds) || buildActionIds.length === 0
? '#build-action-details-section' ? '#build-action-details-section'
: '#build-action-details-section?' + encodeURIComponent(buildActionIds.join(','))); : '#build-action-details-section?' + encodeURIComponent(buildActionIds.join(',')));
} }
function switchToBuildActions() function switchToBuildActions()
{ {
updateHashPreventingSectionInitializer('#build-action-section'); SinglePageHelper.updateHashPreventingSectionInitializer('#build-action-section');
} }
function showBuildActionDetails(ajaxRequest) function showBuildActionDetails(ajaxRequest)
@ -413,7 +435,7 @@ function showBuildActionDetails(ajaxRequest)
window.functionsPostponedUntilGlobalInfo.push(showBuildActionDetails.bind(this, ...arguments)); window.functionsPostponedUntilGlobalInfo.push(showBuildActionDetails.bind(this, ...arguments));
return; return;
} }
if (checkForAjaxError(ajaxRequest, 'show build action')) { if (AjaxHelper.checkForAjaxError(ajaxRequest, 'show build action')) {
return; return;
} }
const responseJSON = JSON.parse(ajaxRequest.responseText); const responseJSON = JSON.parse(ajaxRequest.responseText);
@ -422,8 +444,8 @@ function showBuildActionDetails(ajaxRequest)
function showBuildActionDetails2(buildActions) function showBuildActionDetails2(buildActions)
{ {
const buildActionResults = getAndEmptyElement('build-action-results'); const buildActionResults = Utils.getAndEmptyElement('build-action-results');
let buildActionActions = getAndEmptyElement('build-action-details-actions'); let buildActionActions = Utils.getAndEmptyElement('build-action-details-actions');
buildActions.forEach(function (buildActionDetails) { buildActions.forEach(function (buildActionDetails) {
if (!buildActionActions) { if (!buildActionActions) {
buildActionActions = document.createElement('span'); buildActionActions = document.createElement('span');
@ -439,33 +461,33 @@ function showBuildActionDetails2(buildActions)
function renderBuildActionDetailsTable(buildActionDetails) function renderBuildActionDetailsTable(buildActionDetails)
{ {
return renderTableFromJsonObject({ return GenericRendering.renderTableFromJsonObject({
data: buildActionDetails, data: buildActionDetails,
displayLabels: ['ID', 'Task', 'Type', 'Status', 'Result', 'Result data', 'Created', 'Started', 'Finished', 'Start after', 'Directory', 'Source repo', 'Destination repo', 'Packages', 'Flags', 'Settings', 'Log files', 'Artefacts', 'Output'], displayLabels: ['ID', 'Task', 'Type', 'Status', 'Result', 'Result data', 'Created', 'Started', 'Finished', 'Start after', 'Directory', 'Source repo', 'Destination repo', 'Packages', 'Flags', 'Settings', 'Log files', 'Artefacts', 'Output'],
fieldAccessors: ['id', 'taskName', 'type', 'status', 'result', 'resultData', 'created', 'started', 'finished', 'startAfter', 'directory', 'sourceDbs', 'destinationDbs', 'packageNames', 'flags', 'settings', 'logfiles', 'artefacts', 'output'], fieldAccessors: ['id', 'taskName', 'type', 'status', 'result', 'resultData', 'created', 'started', 'finished', 'startAfter', 'directory', 'sourceDbs', 'destinationDbs', 'packageNames', 'flags', 'settings', 'logfiles', 'artefacts', 'output'],
customRenderer: { customRenderer: {
taskName: function (value) { taskName: function (value) {
if (!value) { if (!value) {
return renderNoneInGrey(); return GenericRendering.renderNoneInGrey();
} }
return document.createTextNode(getProperty(globalInfo.presets.tasks[value], 'name', value)); return document.createTextNode(Utils.getProperty(globalInfo.presets.tasks[value], 'name', value));
}, },
status: function(value) { status: function(value) {
return document.createTextNode(getProperty(globalInfo.buildActionStates[value], 'name', 'Invalid/unknown')); return document.createTextNode(Utils.getProperty(globalInfo.buildActionStates[value], 'name', 'Invalid/unknown'));
}, },
result: function(value) { result: function(value) {
return renderNoneInGrey(getProperty(globalInfo.buildActionResults[value], 'name', 'Invalid/unknown')); return GenericRendering.renderNoneInGrey(Utils.getProperty(globalInfo.buildActionResults[value], 'name', 'Invalid/unknown'));
}, },
type: function(value) { type: function(value) {
return document.createTextNode(getProperty(globalInfo.buildActionTypes[value], 'name', 'Invalid/debugging')); return document.createTextNode(Utils.getProperty(globalInfo.buildActionTypes[value], 'name', 'Invalid/debugging'));
}, },
created: renderTimeStamp, created: GenericRendering.renderTimeStamp,
started: renderTimeStamp, started: GenericRendering.renderTimeStamp,
finished: renderTimeStamp, finished: GenericRendering.renderTimeStamp,
startAfter: renderArrayAsCommaSeparatedString, startAfter: GenericRendering.renderArrayAsCommaSeparatedString,
sourceDbs: renderArrayAsCommaSeparatedString, sourceDbs: GenericRendering.renderArrayAsCommaSeparatedString,
destinationDbs: renderArrayAsCommaSeparatedString, destinationDbs: GenericRendering.renderArrayAsCommaSeparatedString,
packageNames: renderArrayAsCommaSeparatedString, packageNames: GenericRendering.renderArrayAsCommaSeparatedString,
flags: function(value, row) { flags: function(value, row) {
const flagNames = []; const flagNames = [];
const typeInfo = globalInfo.buildActionTypes[row.type]; const typeInfo = globalInfo.buildActionTypes[row.type];
@ -474,19 +496,19 @@ function renderBuildActionDetailsTable(buildActionDetails)
flagNames.push(flag.name); flagNames.push(flag.name);
} }
}); });
return renderArrayAsCommaSeparatedString(flagNames); return GenericRendering.renderArrayAsCommaSeparatedString(flagNames);
}, },
settings: function(value, row) { settings: function(value, row) {
const typeInfo = globalInfo.buildActionTypes[row.type]; const typeInfo = globalInfo.buildActionTypes[row.type];
if (typeInfo.settingNames.length === 0) { if (typeInfo.settingNames.length === 0) {
return renderNoneInGrey(); return GenericRendering.renderNoneInGrey();
} }
return renderTableFromJsonObject({ return GenericRendering.renderTableFromJsonObject({
data: value, data: value,
displayLabels: typeInfo.settingNames, displayLabels: typeInfo.settingNames,
fieldAccessors: typeInfo.settingParams, fieldAccessors: typeInfo.settingParams,
defaultRenderer: function (arg1, arg2, arg3) { defaultRenderer: function (arg1, arg2, arg3) {
return renderNoneInGrey(arg1, arg2, arg3, 'default/none'); return GenericRendering.renderNoneInGrey(arg1, arg2, arg3, 'default/none');
}, },
}); });
}, },
@ -495,7 +517,7 @@ function renderBuildActionDetailsTable(buildActionDetails)
case 3: { // update info case 3: { // update info
const formElement = document.createElement('form'); const formElement = document.createElement('form');
formElement.className = 'update-info-form'; formElement.className = 'update-info-form';
formElement.appendChild(renderTableFromJsonObject({ formElement.appendChild(GenericRendering.renderTableFromJsonObject({
data: value.data, data: value.data,
relatedRow: row, relatedRow: row,
displayLabels: ['Version updates', 'Package updates', 'Downgrades', 'Orphans'], displayLabels: ['Version updates', 'Package updates', 'Downgrades', 'Orphans'],
@ -530,15 +552,15 @@ function renderBuildActionDetailsTable(buildActionDetails)
return formElement; return formElement;
} }
case 4: // build preparation info case 4: // build preparation info
return renderTableFromJsonObject({ return GenericRendering.renderTableFromJsonObject({
data: value.data, data: value.data,
displayLabels: ['Error', 'Warnings', 'Database config', 'Batches', 'Cyclic leftovers', 'Build data'], displayLabels: ['Error', 'Warnings', 'Database config', 'Batches', 'Cyclic leftovers', 'Build data'],
fieldAccessors: ['error', 'warnings', 'dbConfig', 'batches', 'cyclicLeftovers', 'buildData'], fieldAccessors: ['error', 'warnings', 'dbConfig', 'batches', 'cyclicLeftovers', 'buildData'],
customRenderer: { customRenderer: {
buildData: renderBuildPreparationResultData, buildData: renderBuildPreparationResultData,
cyclicLeftovers: renderArrayAsCommaSeparatedString, cyclicLeftovers: GenericRendering.renderArrayAsCommaSeparatedString,
batches: function(batch) { batches: function(batch) {
return renderCustomList(batch, renderArrayAsCommaSeparatedString); return GenericRendering.renderCustomList(batch, GenericRendering.renderArrayAsCommaSeparatedString);
}, },
}, },
}); });
@ -546,7 +568,7 @@ function renderBuildActionDetailsTable(buildActionDetails)
const container = document.createElement('div'); const container = document.createElement('div');
container.className = 'repo-problems'; container.className = 'repo-problems';
for (const [database, problems] of Object.entries(value.data)) { for (const [database, problems] of Object.entries(value.data)) {
const table = renderTableFromJsonArray({ const table = GenericRendering.renderTableFromJsonArray({
rows: problems, rows: problems,
columnHeaders: ['Related package', 'Problem description'], columnHeaders: ['Related package', 'Problem description'],
columnAccessors: ['pkg', 'desc'], columnAccessors: ['pkg', 'desc'],
@ -555,16 +577,16 @@ function renderBuildActionDetailsTable(buildActionDetails)
desc: function(value) { desc: function(value) {
switch(value.index) { switch(value.index) {
case 1: case 1:
return renderTableFromJsonObject({ return GenericRendering.renderTableFromJsonObject({
data: value.data, data: value.data,
displayLabels: ['Missing dependencies', 'Missing libraries'], displayLabels: ['Missing dependencies', 'Missing libraries'],
fieldAccessors: ['deps', 'libs'], fieldAccessors: ['deps', 'libs'],
customRenderer: { customRenderer: {
deps: renderDependency, deps: CustomRendering.renderDependency,
}, },
}); });
default: default:
return renderStandardTableCell(value.data); return GenericRendering.renderStandardTableCell(value.data);
} }
}, },
}, },
@ -575,7 +597,7 @@ function renderBuildActionDetailsTable(buildActionDetails)
return container; return container;
} }
default: default:
return renderStandardTableCell(value.data); return GenericRendering.renderStandardTableCell(value.data);
} }
}, },
logfiles: renderBuildActionLogFiles, logfiles: renderBuildActionLogFiles,
@ -583,12 +605,12 @@ function renderBuildActionDetailsTable(buildActionDetails)
output: function(value, row) { output: function(value, row) {
const isFinished = row.status === 4; const isFinished = row.status === 4;
if (!value && isFinished) { if (!value && isFinished) {
return renderNoneInGrey(); return GenericRendering.renderNoneInGrey();
} }
const targetElement = document.createElement('div'); const targetElement = document.createElement('div');
if (isFinished) { if (isFinished) {
const terminal = makeTerminal(); const terminal = Terminal.makeTerminal();
setupTerminalLater(terminal, targetElement, value); Terminal.setupTerminalLater(terminal, targetElement, value);
return targetElement; return targetElement;
} }
const streamingSetup = setupTerminalForStreaming({ const streamingSetup = setupTerminalForStreaming({
@ -610,15 +632,15 @@ function setupTerminalForStreaming(args)
{ {
const id = args.id; // a page-unique ID to make up DOM element IDs as needed const id = args.id; // a page-unique ID to make up DOM element IDs as needed
const targetElement = args.targetElement; // the DOM element to stream contents into const targetElement = args.targetElement; // the DOM element to stream contents into
const path = args.path; // the path to GET contents from via streamRouteIntoTerminal() const path = args.path; // the path to GET contents from via Terminal.streamRouteIntoTerminal()
let terminal; let terminal;
let ajaxRequest; let ajaxRequest;
return { return {
elements: [targetElement], elements: [targetElement],
startStreaming: function () { startStreaming: function () {
terminal = makeTerminal(); terminal = Terminal.makeTerminal();
ajaxRequest = streamRouteIntoTerminal('GET', path, terminal); ajaxRequest = Terminal.streamRouteIntoTerminal('GET', path, terminal);
setupTerminalLater(terminal, targetElement); Terminal.setupTerminalLater(terminal, targetElement);
}, },
stopStreaming: function () { stopStreaming: function () {
ajaxRequest.abort(); ajaxRequest.abort();
@ -635,25 +657,15 @@ function setupTerminalForStreaming(args)
}; };
} }
function fillBuildActionFromPackageSearch()
{
const packageNamesTextArea = document.getElementById('build-action-form')['package-names'];
const data = getFormTableData('package-results-form');
if (data === undefined) {
return;
}
packageNamesTextArea.value = getSelectedRowProperties(data, 'name').join(' ');
location.hash = '#build-action-section';
}
function submitBuildAction() function submitBuildAction()
{ {
startFormQuery('build-action-form', handleBuildActionResponse); AjaxHelper.startFormQuery('build-action-form', handleBuildActionResponse);
return false;
} }
function handleBuildActionResponse(ajaxRequest) function handleBuildActionResponse(ajaxRequest)
{ {
const results = getAndEmptyElement('build-action-results'); const results = Utils.getAndEmptyElement('build-action-results');
if (ajaxRequest.status !== 200) { if (ajaxRequest.status !== 200) {
results.appendChild(document.createTextNode('unable to create build action: ' + ajaxRequest.responseText)); results.appendChild(document.createTextNode('unable to create build action: ' + ajaxRequest.responseText));
switchToBuildActionDetails(); switchToBuildActionDetails();
@ -664,7 +676,7 @@ function handleBuildActionResponse(ajaxRequest)
function renderBuildActionLogFiles(array, obj) function renderBuildActionLogFiles(array, obj)
{ {
return renderCustomList(array, function(arrayElement) { return GenericRendering.renderCustomList(array, function(arrayElement) {
const params = 'id=' + encodeURIComponent(obj.id) + '&name=' + encodeURIComponent(arrayElement); const params = 'id=' + encodeURIComponent(obj.id) + '&name=' + encodeURIComponent(arrayElement);
const logFilePath = '/build-action/logfile?' + params; const logFilePath = '/build-action/logfile?' + params;
const newWindowPath = 'log.html#' + params; const newWindowPath = 'log.html#' + params;
@ -675,25 +687,25 @@ function renderBuildActionLogFiles(array, obj)
path: logFilePath, path: logFilePath,
}); });
const basicElements = streamingSetup.elements; const basicElements = streamingSetup.elements;
const openInNewWindowLinkElement = renderIconLink('dock-window', obj, function() { window.open(newWindowPath); }, 'Open in new window'); const openInNewWindowLinkElement = CustomRendering.renderIconLink('dock-window', obj, function() { window.open(newWindowPath); }, 'Open in new window');
const downloadLinkElement = renderIconLink('download', obj, function() { window.open(apiPrefix + logFilePath); }, 'Download log'); const downloadLinkElement = CustomRendering.renderIconLink('download', obj, function() { window.open(AjaxHelper.apiPrefix + logFilePath); }, 'Download log');
const stopStreamingLinkElement = renderIconLink('stop', obj, function() { const stopStreamingLinkElement = CustomRendering.renderIconLink('stop', obj, function() {
if (!streamingSetup.isStreaming()) { if (!streamingSetup.isStreaming()) {
return; return;
} }
streamingSetup.stopStreaming(); streamingSetup.stopStreaming();
targetElement.style.display = stopStreamingLinkElement.style.display = 'none'; targetElement.style.display = stopStreamingLinkElement.style.display = 'none';
emptyDomElement(targetElement); Utils.emptyDomElement(targetElement);
}, 'Close log'); }, 'Close log');
const startStreamingLinkElement = renderLink(arrayElement, obj, function() { const startStreamingLinkElement = GenericRendering.renderLink(arrayElement, obj, function() {
if (streamingSetup.isStreaming()) { if (streamingSetup.isStreaming()) {
return; return;
} }
emptyDomElement(targetElement); Utils.emptyDomElement(targetElement);
streamingSetup.startStreaming(); streamingSetup.startStreaming();
targetElement.style.display = 'block'; targetElement.style.display = 'block';
stopStreamingLinkElement.style.display = 'inline-block'; stopStreamingLinkElement.style.display = 'inline-block';
}, 'Show log file', apiPrefix + logFilePath); }, 'Show log file', AjaxHelper.apiPrefix + logFilePath);
targetElement.style.display = stopStreamingLinkElement.style.display = 'none'; targetElement.style.display = stopStreamingLinkElement.style.display = 'none';
[downloadLinkElement, stopStreamingLinkElement].forEach(function(element) { [downloadLinkElement, stopStreamingLinkElement].forEach(function(element) {
element.classList.add('streaming-link'); element.classList.add('streaming-link');
@ -704,9 +716,9 @@ function renderBuildActionLogFiles(array, obj)
function renderBuildActionArtefacts(array, obj) function renderBuildActionArtefacts(array, obj)
{ {
return renderCustomList(array, function(arrayElement) { return GenericRendering.renderCustomList(array, function(arrayElement) {
const path = apiPrefix + '/build-action/artefact?id=' + encodeURIComponent(obj.id) + '&name=' + encodeURIComponent(arrayElement); const path = AjaxHelper.apiPrefix + '/build-action/artefact?id=' + encodeURIComponent(obj.id) + '&name=' + encodeURIComponent(arrayElement);
return renderLink(arrayElement, obj, function() { return GenericRendering.renderLink(arrayElement, obj, function() {
window.open(path); window.open(path);
}, 'Download artefact', path); }, 'Download artefact', path);
}); });
@ -751,12 +763,12 @@ function renderUpdateInfoWithCheckbox(id, packageName, newPackageName, versionIn
function renderPackageList(packageList) function renderPackageList(packageList)
{ {
return renderCustomList(packageList, renderPackage); return GenericRendering.renderCustomList(packageList, CustomRendering.renderPackage);
} }
function renderBuildPreparationBuildData(buildDataForPackage) function renderBuildPreparationBuildData(buildDataForPackage)
{ {
return renderTableFromJsonObject({ return GenericRendering.renderTableFromJsonObject({
data: buildDataForPackage, data: buildDataForPackage,
displayLabels: ['Error', 'Warnings', 'Has source', 'Source directory', 'Original/local source directory', 'New packages', 'Existing packages', 'Specified index'], displayLabels: ['Error', 'Warnings', 'Has source', 'Source directory', 'Original/local source directory', 'New packages', 'Existing packages', 'Specified index'],
fieldAccessors: ['error', 'warnings', 'hasSource', 'sourceDirectory', 'originalSourceDirectory', 'packages', 'existingPackages', 'specifiedIndex'], fieldAccessors: ['error', 'warnings', 'hasSource', 'sourceDirectory', 'originalSourceDirectory', 'packages', 'existingPackages', 'specifiedIndex'],
@ -769,7 +781,7 @@ function renderBuildPreparationBuildData(buildDataForPackage)
function makeVersionsString(packages) function makeVersionsString(packages)
{ {
const versions = packages.map(package => package.version); const versions = packages.map(packageObj => packageObj.version);
if (versions.length === 0) { if (versions.length === 0) {
return '?'; return '?';
} else if (versions.length === 1) { } else if (versions.length === 1) {
@ -786,7 +798,7 @@ function renderBuildPreparationResultData(buildPreparationData)
const heading = document.createElement('h4'); const heading = document.createElement('h4');
let table; let table;
heading.className = 'compact-heading'; heading.className = 'compact-heading';
heading.appendChild(renderLink(packageName, undefined, function() { heading.appendChild(GenericRendering.renderLink(packageName, undefined, function() {
if (table === undefined) { if (table === undefined) {
table = renderBuildPreparationBuildData(buildDataForPackage); table = renderBuildPreparationBuildData(buildDataForPackage);
heading.insertAdjacentElement('afterEnd', table); heading.insertAdjacentElement('afterEnd', table);
@ -808,13 +820,13 @@ function renderBuildPreparationResultData(buildPreparationData)
function renderOrphanPackage(value, obj, level, row) function renderOrphanPackage(value, obj, level, row)
{ {
return renderCustomList(value, function(package) { return GenericRendering.renderCustomList(value, function(packageObj) {
const packageName = package.name; const packageName = packageObj.name;
return renderUpdateInfoWithCheckbox( return renderUpdateInfoWithCheckbox(
'update-info-checkbox-' + packageName + '-' + package.version, 'update-info-checkbox-' + packageName + '-' + packageObj.version,
packageName, packageName,
undefined, undefined,
package.version, packageObj.version,
row.sourceDbs, row.sourceDbs,
); );
}, function(package1, package2) { }, function(package1, package2) {
@ -824,7 +836,7 @@ function renderOrphanPackage(value, obj, level, row)
function renderUpdateOrDowngrade(value, obj, level, row) function renderUpdateOrDowngrade(value, obj, level, row)
{ {
return renderCustomList(value, function(updateInfo) { return GenericRendering.renderCustomList(value, function(updateInfo) {
const oldVersion = updateInfo.oldVersion; const oldVersion = updateInfo.oldVersion;
const newVersion = updateInfo.newVersion; const newVersion = updateInfo.newVersion;
const packageName = oldVersion.name; const packageName = oldVersion.name;
@ -843,12 +855,15 @@ function renderUpdateOrDowngrade(value, obj, level, row)
function triggerToolbarAction(toolbarElement) function triggerToolbarAction(toolbarElement)
{ {
if (!toolbarElement || toolbarElement instanceof PointerEvent) {
toolbarElement = this;
}
const confirmQuestion = toolbarElement.dataset.confirmation; const confirmQuestion = toolbarElement.dataset.confirmation;
if (confirmQuestion !== undefined && !window.confirm(confirmQuestion)) { if (confirmQuestion !== undefined && !window.confirm(confirmQuestion)) {
return false; return false;
} }
toolbarElement.disabled = true; toolbarElement.disabled = true;
queryRoute(toolbarElement.dataset.method, toolbarElement.dataset.action, function(ajaxRequest) { AjaxHelper.queryRoute(toolbarElement.dataset.method, toolbarElement.dataset.action, function(ajaxRequest) {
window.alert(ajaxRequest.responseText); window.alert(ajaxRequest.responseText);
toolbarElement.disabled = false; toolbarElement.disabled = false;
}); });
@ -857,11 +872,11 @@ function triggerToolbarAction(toolbarElement)
function deleteSelectedActions() function deleteSelectedActions()
{ {
const data = getFormTableData('build-actions-list'); const data = Utils.getFormTableData('build-actions-list');
if (data === undefined) { if (data === undefined) {
return; return;
} }
const ids = getSelectedRowProperties(data, 'id'); const ids = Utils.getSelectedRowProperties(data, 'id');
if (ids.length && window.confirm('Do you really want to delete the build action(s) ' + ids.join(', ') + '?')) { if (ids.length && window.confirm('Do you really want to delete the build action(s) ' + ids.join(', ') + '?')) {
deleteBuildAction(ids); deleteBuildAction(ids);
} }
@ -869,23 +884,23 @@ function deleteSelectedActions()
function showSelectedActions() function showSelectedActions()
{ {
const data = getFormTableData('build-actions-list'); const data = Utils.getFormTableData('build-actions-list');
if (data === undefined) { if (data === undefined) {
return; return;
} }
const ids = getSelectedRowProperties(data, 'id'); const ids = Utils.getSelectedRowProperties(data, 'id');
if (ids.length) { if (ids.length) {
queryBuildActionDetails(ids); queryBuildActionDetails(ids);
} }
} }
function initBuildActionDetails(sectionElement, sectionData, newHashParts) export function initBuildActionDetails(sectionElement, sectionData, newHashParts)
{ {
const currentBuildActionIds = sectionData.state.ids; const currentBuildActionIds = sectionData.state.ids;
const hasCurrentlyBuildActions = Array.isArray(currentBuildActionIds) && currentBuildActionIds.length !== 0; const hasCurrentlyBuildActions = Array.isArray(currentBuildActionIds) && currentBuildActionIds.length !== 0;
if (!newHashParts.length) { if (!newHashParts.length) {
if (hasCurrentlyBuildActions) { if (hasCurrentlyBuildActions) {
updateHashPreventingChangeHandler('#build-action-details-section?' + encodeURIComponent(currentBuildActionIds.join(','))); SinglePageHelper.updateHashPreventingChangeHandler('#build-action-details-section?' + encodeURIComponent(currentBuildActionIds.join(',')));
} }
return true; return true;
} }

View File

@ -1,8 +1,11 @@
import * as GenericRendering from './genericrendering.js';
import * as Utils from './utils.js';
/// \brief Renders a dependency object. /// \brief Renders a dependency object.
function renderDependency(value) export function renderDependency(value)
{ {
if (value.length < 1) { if (value.length < 1) {
return renderArrayAsCommaSeparatedString(value); return GenericRendering.renderArrayAsCommaSeparatedString(value);
} }
const list = document.createElement('ul'); const list = document.createElement('ul');
list.className = 'dependency-list'; list.className = 'dependency-list';
@ -29,7 +32,7 @@ function renderDependency(value)
} }
/// \brief Renders a "Reload" button invoking the specified \a handler when clicked. /// \brief Renders a "Reload" button invoking the specified \a handler when clicked.
function renderReloadButton(handler) export function renderReloadButton(handler)
{ {
const reloadButton = document.createElement('button'); const reloadButton = document.createElement('button');
reloadButton.className = 'icon-button icon-reload'; reloadButton.className = 'icon-button icon-reload';
@ -40,7 +43,7 @@ function renderReloadButton(handler)
} }
/// \brief Renders an icon. /// \brief Renders an icon.
function renderIcon(iconName) export function renderIcon(iconName)
{ {
const icon = document.createElement('span'); const icon = document.createElement('span');
icon.className = 'icon icon-' + iconName; icon.className = 'icon icon-' + iconName;
@ -48,9 +51,57 @@ function renderIcon(iconName)
} }
/// \brief Renders an icon link which will invoke the specified \a handler when clicked. /// \brief Renders an icon link which will invoke the specified \a handler when clicked.
function renderIconLink(value, row, handler, tooltip, href, middleClickHref) export function renderIconLink(value, row, handler, tooltip, href, middleClickHref)
{ {
const link = renderLink(renderIcon(value), row, handler, tooltip, href, middleClickHref); const link = GenericRendering.renderLink(renderIcon(value), row, handler, tooltip, href, middleClickHref);
link.className = 'icon-link'; link.className = 'icon-link';
return link; return link;
} }
const labelsWithoutBasics = ['Architecture', 'Repository', 'Description', 'Upstream URL', 'License(s)', 'Groups', 'Package size', 'Installed size', 'Packager', 'Build date', 'Dependencies', 'Optional dependencies', 'Make dependencies', 'Check dependencies', 'Provides', 'Replaces', 'Conflicts', 'Contained libraries', 'Needed libraries', 'Files'];
const fieldsWithoutBasics = ['packageInfo.arch', 'db', 'description', 'upstreamUrl', 'licenses', 'groups', 'packageInfo.size', 'installInfo.installedSize', 'packageInfo.packager', 'packageInfo.buildDate', 'dependencies', 'optionalDependencies', 'sourceInfo.makeDependencies', 'sourceInfo.checkDependencies', 'provides', 'replaces', 'conflicts', 'libprovides', 'libdepends', 'packageInfo.files'];
const labelsWithBasics = ['Name', 'Version', ...labelsWithoutBasics];
const fieldsWithBasics = ['name', 'version', ...fieldsWithoutBasics];
export function renderPackage(packageObj, withoutBasics)
{
const table = GenericRendering.renderTableFromJsonObject({
data: packageObj,
displayLabels: withoutBasics ? labelsWithoutBasics : labelsWithBasics,
fieldAccessors: withoutBasics ? fieldsWithoutBasics : fieldsWithBasics,
customRenderer: {
db: function(value, row) {
return document.createTextNode(Utils.makeRepoName(value, row.dbArch));
},
upstreamUrl: function(value, row) {
return GenericRendering.renderLink(value, row, function(value) {
window.open(value);
});
},
licenses: GenericRendering.renderArrayAsCommaSeparatedString,
groups: GenericRendering.renderArrayAsCommaSeparatedString,
dependencies: renderDependency,
optionalDependencies: renderDependency,
provides: renderDependency,
replaces: renderDependency,
conflicts: renderDependency,
libprovides: GenericRendering.renderArrayAsCommaSeparatedString,
libdepends: GenericRendering.renderArrayAsCommaSeparatedString,
'sourceInfo.makeDependencies': renderDependency,
'sourceInfo.checkDependencies': renderDependency,
'packageInfo.arch': function(value, row) {
const sourceInfo = row.sourceInfo;
const sourceArchs = sourceInfo !== undefined ? sourceInfo.archs : undefined;
if (Array.isArray(sourceArchs) && sourceArchs.length) {
return GenericRendering.renderArrayAsCommaSeparatedString(sourceArchs);
} else {
return GenericRendering.renderNoneInGrey(value);
}
},
'packageInfo.size': GenericRendering.renderDataSize,
'installInfo.installedSize': GenericRendering.renderDataSize,
},
});
table.className = 'package-details-table';
return table;
}

View File

@ -1,5 +1,5 @@
/// \brief Renders the specified \a value as text or a grey 'none' if the value is 'none' or empty. /// \brief Renders the specified \a value as text or a grey 'none' if the value is 'none' or empty.
function renderNoneInGrey(value, row, elementName, noneText) export function renderNoneInGrey(value, row, elementName, noneText)
{ {
const noValue = value === undefined || value === null || value === 'none' || value === 'None' || const noValue = value === undefined || value === null || value === 'none' || value === 'None' ||
value === '' || value === 18446744073709552000; value === '' || value === 18446744073709552000;
@ -18,7 +18,7 @@ function renderNoneInGrey(value, row, elementName, noneText)
/// \brief Renders a standard table cell. /// \brief Renders a standard table cell.
/// \remarks This is the default renderer used by renderTableFromJsonArray() and renderTableFromJsonObject(). /// \remarks This is the default renderer used by renderTableFromJsonArray() and renderTableFromJsonObject().
function renderStandardTableCell(data, allData, level) export function renderStandardTableCell(data, allData, level)
{ {
const dataType = typeof data; const dataType = typeof data;
if (dataType !== 'object') { if (dataType !== 'object') {
@ -43,7 +43,7 @@ function renderStandardTableCell(data, allData, level)
} }
/// \brief Renders a custom list. /// \brief Renders a custom list.
function renderCustomList(array, customRenderer, compareFunction) export function renderCustomList(array, customRenderer, compareFunction)
{ {
if (!Array.isArray(array) || array.length < 1) { if (!Array.isArray(array) || array.length < 1) {
return renderNoneInGrey(); return renderNoneInGrey();
@ -68,7 +68,7 @@ function renderCustomList(array, customRenderer, compareFunction)
} }
/// \brief Renders a list of links. /// \brief Renders a list of links.
function renderLinkList(array, obj, handler) export function renderLinkList(array, obj, handler)
{ {
return renderCustomList(array, function(arrayElement) { return renderCustomList(array, function(arrayElement) {
return renderLink(array, obj, function() { return renderLink(array, obj, function() {
@ -78,7 +78,7 @@ function renderLinkList(array, obj, handler)
} }
/// \brief Returns a 'time ago' string used by the time stamp rendering functions. /// \brief Returns a 'time ago' string used by the time stamp rendering functions.
function formatTimeAgoString(date) export function formatTimeAgoString(date)
{ {
const seconds = Math.floor((new Date() - date) / 1000); const seconds = Math.floor((new Date() - date) / 1000);
let interval = Math.floor(seconds / 31536000); let interval = Math.floor(seconds / 31536000);
@ -105,13 +105,13 @@ function formatTimeAgoString(date)
} }
/// \brief Returns a Date object from the specified time stamp. /// \brief Returns a Date object from the specified time stamp.
function dateFromTimeStamp(timeStamp) export function dateFromTimeStamp(timeStamp)
{ {
return new Date(timeStamp + 'Z'); return new Date(timeStamp + 'Z');
} }
/// \brief Renders a short time stamp, e.g. "12 hours ago" with the exact date as tooltip. /// \brief Renders a short time stamp, e.g. "12 hours ago" with the exact date as tooltip.
function renderShortTimeStamp(timeStamp) export function renderShortTimeStamp(timeStamp)
{ {
const date = dateFromTimeStamp(timeStamp); const date = dateFromTimeStamp(timeStamp);
if (date.getFullYear() === 1) { if (date.getFullYear() === 1) {
@ -124,7 +124,7 @@ function renderShortTimeStamp(timeStamp)
} }
/// \brief Renders a time stamp, e.g. "12 hours ago" with the exact date in brackets. /// \brief Renders a time stamp, e.g. "12 hours ago" with the exact date in brackets.
function renderTimeStamp(timeStamp) export function renderTimeStamp(timeStamp)
{ {
const date = dateFromTimeStamp(timeStamp); const date = dateFromTimeStamp(timeStamp);
if (date.getFullYear() === 1) { if (date.getFullYear() === 1) {
@ -134,7 +134,7 @@ function renderTimeStamp(timeStamp)
} }
/// \brief Renders a time delta from 2 time stamps. /// \brief Renders a time delta from 2 time stamps.
function renderTimeSpan(startTimeStamp, endTimeStamp) export function renderTimeSpan(startTimeStamp, endTimeStamp)
{ {
const startDate = dateFromTimeStamp(startTimeStamp); const startDate = dateFromTimeStamp(startTimeStamp);
if (startDate.getFullYear() === 1) { if (startDate.getFullYear() === 1) {
@ -159,7 +159,7 @@ function renderTimeSpan(startTimeStamp, endTimeStamp)
} }
/// \brief Renders a link which will invoke the specified \a handler when clicked. /// \brief Renders a link which will invoke the specified \a handler when clicked.
function renderLink(value, row, handler, tooltip, href, middleClickHref) export function renderLink(value, row, handler, tooltip, href, middleClickHref)
{ {
const linkElement = document.createElement('a'); const linkElement = document.createElement('a');
const linkText = typeof value === 'object' ? value : document.createTextNode(value); const linkText = typeof value === 'object' ? value : document.createTextNode(value);
@ -188,13 +188,13 @@ function renderLink(value, row, handler, tooltip, href, middleClickHref)
} }
/// \brief Renders the specified array as comma-separated string or 'none' if the array is empty. /// \brief Renders the specified array as comma-separated string or 'none' if the array is empty.
function renderArrayAsCommaSeparatedString(value) export function renderArrayAsCommaSeparatedString(value)
{ {
return renderNoneInGrey(!Array.isArray(value) || value.length <= 0 ? 'none' : value.join(', ')); return renderNoneInGrey(!Array.isArray(value) || value.length <= 0 ? 'none' : value.join(', '));
} }
/// \brief Renders the specified array as a possibly elided comma-separated string or 'none' if the array is empty. /// \brief Renders the specified array as a possibly elided comma-separated string or 'none' if the array is empty.
function renderArrayElidedAsCommaSeparatedString(value) export function renderArrayElidedAsCommaSeparatedString(value)
{ {
if (!Array.isArray(value) || value.length <= 0) { if (!Array.isArray(value) || value.length <= 0) {
return renderNoneInGrey('none'); return renderNoneInGrey('none');
@ -203,7 +203,7 @@ function renderArrayElidedAsCommaSeparatedString(value)
} }
/// \brief Renders the specified value, possibly eliding the end. /// \brief Renders the specified value, possibly eliding the end.
function renderTextPossiblyElidingTheEnd(value) export function renderTextPossiblyElidingTheEnd(value)
{ {
const limit = 50; const limit = 50;
if (value.length < limit) { if (value.length < limit) {
@ -223,7 +223,7 @@ function renderTextPossiblyElidingTheEnd(value)
} }
/// \brief Renders the specified \a sizeInByte using an appropriate unit. /// \brief Renders the specified \a sizeInByte using an appropriate unit.
function renderDataSize(sizeInByte, row, includeBytes) export function renderDataSize(sizeInByte, row, includeBytes)
{ {
if (typeof(sizeInByte) !== 'number') { if (typeof(sizeInByte) !== 'number') {
return renderNoneInGrey('none'); return renderNoneInGrey('none');
@ -264,7 +264,7 @@ function accessProperty(object, accessor)
} }
/// \brief Renders a checkbox for selecting a table row. /// \brief Renders a checkbox for selecting a table row.
function renderCheckBoxForTableRow(value, row, computeCheckBoxValue) export function renderCheckBoxForTableRow(value, row, computeCheckBoxValue)
{ {
const checkbox = document.createElement('input'); const checkbox = document.createElement('input');
checkbox.type = 'checkbox'; checkbox.type = 'checkbox';
@ -275,7 +275,7 @@ function renderCheckBoxForTableRow(value, row, computeCheckBoxValue)
} }
/// \brief Returns a table for the specified JSON array. /// \brief Returns a table for the specified JSON array.
function renderTableFromJsonArray(args) export function renderTableFromJsonArray(args)
{ {
// handle arguments // handle arguments
const rows = args.rows; const rows = args.rows;
@ -568,7 +568,7 @@ function renderTableFromJsonArray(args)
} }
/// \brief Returns a table for the specified JSON object. /// \brief Returns a table for the specified JSON object.
function renderTableFromJsonObject(args) export function renderTableFromJsonObject(args)
{ {
// handle arguments // handle arguments
const data = args.data; const data = args.data;
@ -619,7 +619,7 @@ function renderTableFromJsonObject(args)
} }
/// \brief Returns a heading for each key and values via renderStandardTableCell(). /// \brief Returns a heading for each key and values via renderStandardTableCell().
function renderObjectWithHeadings(object, row, level) export function renderObjectWithHeadings(object, row, level)
{ {
const elements = []; const elements = [];
for (const [key, value] of Object.entries(object)) { for (const [key, value] of Object.entries(object)) {

View File

@ -1,12 +1,20 @@
function queryGlobalStatus() import * as AjaxHelper from './ajaxhelper.js';
import * as BuildActionsPage from './buildactionspage.js';
import * as CustomRendering from './customrendering.js';
import * as GenericRendering from './genericrendering.js';
import * as Utils from './utils.js';
const status = {repoNames: undefined};
export function queryGlobalStatus()
{ {
queryRoute('GET', '/status', handleGlobalStatusUpdate); AjaxHelper.queryRoute('GET', '/status', handleGlobalStatusUpdate);
return true; return true;
} }
function handleGlobalStatusUpdate(ajaxRequest) function handleGlobalStatusUpdate(ajaxRequest)
{ {
const globalStatus = getAndEmptyElement('global-status'); const globalStatus = Utils.getAndEmptyElement('global-status');
let responseText = ajaxRequest.responseText; let responseText = ajaxRequest.responseText;
if (ajaxRequest.status === 500) { if (ajaxRequest.status === 500) {
responseText = 'internal server error'; responseText = 'internal server error';
@ -18,24 +26,24 @@ function handleGlobalStatusUpdate(ajaxRequest)
const responseJson = JSON.parse(responseText); const responseJson = JSON.parse(responseText);
const applicationVersion = responseJson.version; const applicationVersion = responseJson.version;
if (applicationVersion) { if (applicationVersion) {
getAndEmptyElement('application-version').appendChild(document.createTextNode(applicationVersion)); Utils.getAndEmptyElement('application-version').appendChild(document.createTextNode(applicationVersion));
} }
const dbStats = responseJson.config.dbStats; const dbStats = responseJson.config.dbStats;
const table = renderTableFromJsonArray({ const table = GenericRendering.renderTableFromJsonArray({
rows: dbStats, rows: dbStats,
columnHeaders: ['Arch', 'Database', 'Package count', 'Last update', 'Synced from mirror'], columnHeaders: ['Arch', 'Database', 'Package count', 'Last update', 'Synced from mirror'],
columnAccessors: ['arch', 'name', 'packageCount', 'lastUpdate', 'syncFromMirror'], columnAccessors: ['arch', 'name', 'packageCount', 'lastUpdate', 'syncFromMirror'],
customRenderer: { customRenderer: {
name: function (value, row) { name: function (value, row) {
return renderLink(value, row, showRepository); return GenericRendering.renderLink(value, row, showRepository);
}, },
lastUpdate: renderShortTimeStamp, lastUpdate: GenericRendering.renderShortTimeStamp,
note: function(rows) { note: function(rows) {
const note = document.createElement('div'); const note = document.createElement('div');
const totalPackageCount = rows.reduce((acc, cur) => acc + cur.packageCount, 0); const totalPackageCount = rows.reduce((acc, cur) => acc + cur.packageCount, 0);
note.className = 'form-row'; note.className = 'form-row';
note.appendChild(document.createTextNode(rows.length + ' databases and ' + totalPackageCount + ' packages ')); note.appendChild(document.createTextNode(rows.length + ' databases and ' + totalPackageCount + ' packages '));
note.appendChild(renderReloadButton(queryGlobalStatus)); note.appendChild(CustomRendering.renderReloadButton(queryGlobalStatus));
return note; return note;
}, },
}, },
@ -44,13 +52,13 @@ function handleGlobalStatusUpdate(ajaxRequest)
// update database selections // update database selections
const repoSelections = [ const repoSelections = [
getAndEmptyElement('build-action-source-repo', {'build-action-source-repo-none': 'keep'}), Utils.getAndEmptyElement('build-action-source-repo', {'build-action-source-repo-none': 'keep'}),
getAndEmptyElement('build-action-destination-repo', {'build-action-destination-repo-none': 'keep'}), Utils.getAndEmptyElement('build-action-destination-repo', {'build-action-destination-repo-none': 'keep'}),
getAndEmptyElement('package-search-db', {'package-search-db-any': 'keep'}), Utils.getAndEmptyElement('package-search-db', {'package-search-db-any': 'keep'}),
]; ];
status.repoNames = []; status.repoNames = [];
dbStats.forEach(function (dbInfo) { dbStats.forEach(function (dbInfo) {
const repoName = makeRepoName(dbInfo.name, dbInfo.arch); const repoName = Utils.makeRepoName(dbInfo.name, dbInfo.arch);
status.repoNames.push(repoName); status.repoNames.push(repoName);
repoSelections.forEach(function (selection) { repoSelections.forEach(function (selection) {
const option = document.createElement('option'); const option = document.createElement('option');
@ -74,9 +82,9 @@ function handleGlobalStatusUpdate(ajaxRequest)
}); });
// update build action form and make settingNames/settingParams arrays for build action details // update build action form and make settingNames/settingParams arrays for build action details
const buildActionTypeSelect = getAndEmptyElement('build-action-type'); const buildActionTypeSelect = Utils.getAndEmptyElement('build-action-type');
const buildActionFlagsContainer = getAndEmptyElement('build-action-flags'); const buildActionFlagsContainer = Utils.getAndEmptyElement('build-action-flags');
const buildActionSettingsTable = getAndEmptyElement('build-action-settings'); const buildActionSettingsTable = Utils.getAndEmptyElement('build-action-settings');
let optgroupElements = {}; let optgroupElements = {};
const makeOrReuseOptgroup = function (label) { const makeOrReuseOptgroup = function (label) {
const existingOptgroupElement = optgroupElements[label]; const existingOptgroupElement = optgroupElements[label];
@ -148,7 +156,7 @@ function handleGlobalStatusUpdate(ajaxRequest)
}); });
// update presets/tasks form // update presets/tasks form
const buildActionPresetSelect = getAndEmptyElement('build-action-task', {'build-action-task-none': 'keep'}); const buildActionPresetSelect = Utils.getAndEmptyElement('build-action-task', {'build-action-task-none': 'keep'});
const presets = responseJson.presets; const presets = responseJson.presets;
globalInfo.presets = presets; globalInfo.presets = presets;
optgroupElements = {}; optgroupElements = {};
@ -167,8 +175,8 @@ function handleGlobalStatusUpdate(ajaxRequest)
window.functionsPostponedUntilGlobalInfo.forEach(function(f) { f(); }); window.functionsPostponedUntilGlobalInfo.forEach(function(f) { f(); });
window.functionsPostponedUntilGlobalInfo = []; window.functionsPostponedUntilGlobalInfo = [];
handleBuildActionTypeChange(); BuildActionsPage.handleBuildActionTypeChange();
handleBuildActionPresetChange(); BuildActionsPage.handleBuildActionPresetChange();
} }
function showRepository(dbName, dbInfo) function showRepository(dbName, dbInfo)

View File

@ -1,21 +1,27 @@
import * as Terminal from './terminal.js';
import * as Utils from './utils.js';
function initLog() function initLog()
{ {
const hashParts = hashAsObject(); const hashParts = Utils.hashAsObject();
const id = hashParts.id; const id = hashParts.id;
const name = hashParts.name; const name = hashParts.name;
const mainElement = getAndEmptyElement('log-container'); const mainElement = Utils.getAndEmptyElement('log-container');
if (id === undefined || id === '' || name === undefined || name === '') { if (id === undefined || id === '' || name === undefined || name === '') {
document.title += ' - logfile'; document.title += ' - logfile';
mainElement.appendChild(document.createTextNode('id or name invalid')); mainElement.appendChild(document.createTextNode('id or name invalid'));
return; return;
} }
const path = '/build-action/logfile?id=' + encodeURIComponent(id) + '&name=' + encodeURIComponent(name); const path = '/build-action/logfile?id=' + encodeURIComponent(id) + '&name=' + encodeURIComponent(name);
const terminal = makeTerminal(); const terminal = Terminal.makeTerminal();
const ajaxRequest = streamRouteIntoTerminal('GET', path, terminal); const ajaxRequest = Terminal.streamRouteIntoTerminal('GET', path, terminal);
document.title += ' - ' + name; document.title += ' - ' + name;
terminal.resize(180, 500); terminal.resize(180, 500);
window.setTimeout(function() { window.setTimeout(function() {
addSearchToTerminal(terminal, mainElement); Terminal.addSearchToTerminal(terminal, mainElement);
terminal.open(mainElement); terminal.open(mainElement);
}); });
} }
document.body.onhashchange = initLog;
initLog();

24
srv/static/js/main.js Normal file
View File

@ -0,0 +1,24 @@
import * as BuildActionsPage from './buildactionspage.js';
import * as PackageDetailsPage from './packagedetailspage.js';
import * as PackageSearchPage from './packagesearchpage.js';
import * as SinglePage from './singlepage.js';
SinglePage.initPage({
'global': {
},
'package-search': {
initializer: PackageSearchPage.initPackageSearch,
state: {params: undefined},
},
'package-details': {
initializer: PackageDetailsPage.initPackageDetails,
state: {package: undefined},
},
'build-action': {
initializer: BuildActionsPage.initBuildActionsForm,
},
'build-action-details': {
initializer: BuildActionsPage.initBuildActionDetails,
state: {id: undefined},
},
});

View File

@ -1,10 +1,16 @@
function initPackageDetails(sectionElement, sectionData, newPackages) import * as AjaxHelper from './ajaxhelper.js';
import * as CustomRendering from './customrendering.js';
import * as GenericRendering from './genericrendering.js';
import * as SinglePageHelper from './singlepage.js';
import * as Utils from './utils.js';
export function initPackageDetails(sectionElement, sectionData, newPackages)
{ {
const currentPackage = sectionData.state.package; const currentPackage = sectionData.state.package;
const hasNewPackages = newPackages.length >= 1; const hasNewPackages = newPackages.length >= 1;
if (!hasNewPackages) { if (!hasNewPackages) {
if (currentPackage !== undefined) { if (currentPackage !== undefined) {
updateHashPreventingChangeHandler('#package-details-section?' + encodeURIComponent(currentPackage)); SinglePageHelper.updateHashPreventingChangeHandler('#package-details-section?' + encodeURIComponent(currentPackage));
} }
return true; return true;
} }
@ -13,12 +19,12 @@ function initPackageDetails(sectionElement, sectionData, newPackages)
return true; return true;
} }
const packageParts = packageStr.split('/'); const packageParts = packageStr.split('/');
const package = { const packageObj = {
db: packageParts[0], db: packageParts[0],
name: packageParts[1] name: packageParts[1]
}; };
queryRoute('GET', '/packages?details=1&name=' + encodeURIComponent(packageStr), function(ajaxRequest) { AjaxHelper.queryRoute('GET', '/packages?details=1&name=' + encodeURIComponent(packageStr), function(ajaxRequest) {
showPackageDetails(ajaxRequest, package); showPackageDetails(ajaxRequest, packageObj);
}); });
return true; return true;
} }
@ -30,21 +36,21 @@ function makePackageID(row)
function queryPackageDetails(value, row) function queryPackageDetails(value, row)
{ {
queryRoute('GET', '/packages?details=1&name=' + encodeURIComponent(makePackageID(row)), function(ajaxRequest) { AjaxHelper.queryRoute('GET', '/packages?details=1&name=' + encodeURIComponent(makePackageID(row)), function(ajaxRequest) {
showPackageDetails(ajaxRequest, row); showPackageDetails(ajaxRequest, row);
}); });
} }
function switchToPackageDetails(packageID) function switchToPackageDetails(packageID)
{ {
sections['package-details'].state.package = packageID; SinglePageHelper.sections['package-details'].state.package = packageID;
updateHashPreventingSectionInitializer('#package-details-section?' + encodeURIComponent(packageID)); SinglePageHelper.updateHashPreventingSectionInitializer('#package-details-section?' + encodeURIComponent(packageID));
} }
function showPackageDetails(ajaxRequest, row) function showPackageDetails(ajaxRequest, row)
{ {
const packageID = makePackageID(row); const packageID = makePackageID(row);
const packageDetailsContainer = getAndEmptyElement('package-details-container'); const packageDetailsContainer = Utils.getAndEmptyElement('package-details-container');
if (ajaxRequest.status !== 200) { if (ajaxRequest.status !== 200) {
packageDetailsContainer.appendChild(document.createTextNode('unable query package details: ' + ajaxRequest.responseText)); packageDetailsContainer.appendChild(document.createTextNode('unable query package details: ' + ajaxRequest.responseText));
return; return;
@ -55,62 +61,14 @@ function showPackageDetails(ajaxRequest, row)
packageDetailsContainer.appendChild(document.createTextNode('unable query package details: package not present')); packageDetailsContainer.appendChild(document.createTextNode('unable query package details: package not present'));
return; return;
} }
const package = responseJson[0]; const packageObj = responseJson[0];
const heading = document.createElement('h3'); const heading = document.createElement('h3');
heading.appendChild(document.createTextNode(package.name)); heading.appendChild(document.createTextNode(packageObj.name));
heading.appendChild(document.createTextNode(' ' + package.version)); heading.appendChild(document.createTextNode(' ' + packageObj.version));
packageDetailsContainer.appendChild(heading); packageDetailsContainer.appendChild(heading);
package.db = row.db; packageObj.db = row.db;
package.dbArch = row.dbArch; packageObj.dbArch = row.dbArch;
packageDetailsContainer.appendChild(renderPackage(package, true)); packageDetailsContainer.appendChild(CustomRendering.renderPackage(packageObj, true));
switchToPackageDetails(packageID); switchToPackageDetails(packageID);
} }
const labelsWithoutBasics = ['Architecture', 'Repository', 'Description', 'Upstream URL', 'License(s)', 'Groups', 'Package size', 'Installed size', 'Packager', 'Build date', 'Dependencies', 'Optional dependencies', 'Make dependencies', 'Check dependencies', 'Provides', 'Replaces', 'Conflicts', 'Contained libraries', 'Needed libraries', 'Files'];
const fieldsWithoutBasics = ['packageInfo.arch', 'db', 'description', 'upstreamUrl', 'licenses', 'groups', 'packageInfo.size', 'installInfo.installedSize', 'packageInfo.packager', 'packageInfo.buildDate', 'dependencies', 'optionalDependencies', 'sourceInfo.makeDependencies', 'sourceInfo.checkDependencies', 'provides', 'replaces', 'conflicts', 'libprovides', 'libdepends', 'packageInfo.files'];
const labelsWithBasics = ['Name', 'Version', ...labelsWithoutBasics];
const fieldsWithBasics = ['name', 'version', ...fieldsWithoutBasics];
function renderPackage(package, withoutBasics)
{
const table = renderTableFromJsonObject({
data: package,
displayLabels: withoutBasics ? labelsWithoutBasics : labelsWithBasics,
fieldAccessors: withoutBasics ? fieldsWithoutBasics : fieldsWithBasics,
customRenderer: {
db: function(value, row) {
return document.createTextNode(makeRepoName(value, row.dbArch));
},
upstreamUrl: function(value, row) {
return renderLink(value, row, function(value) {
window.open(value);
});
},
licenses: renderArrayAsCommaSeparatedString,
groups: renderArrayAsCommaSeparatedString,
dependencies: renderDependency,
optionalDependencies: renderDependency,
provides: renderDependency,
replaces: renderDependency,
conflicts: renderDependency,
libprovides: renderArrayAsCommaSeparatedString,
libdepends: renderArrayAsCommaSeparatedString,
'sourceInfo.makeDependencies': renderDependency,
'sourceInfo.checkDependencies': renderDependency,
'packageInfo.arch': function(value, row) {
const sourceInfo = row.sourceInfo;
const sourceArchs = sourceInfo !== undefined ? sourceInfo.archs : undefined;
if (Array.isArray(sourceArchs) && sourceArchs.length) {
return renderArrayAsCommaSeparatedString(sourceArchs);
} else {
return renderNoneInGrey(value);
}
},
'packageInfo.size': renderDataSize,
'installInfo.installedSize': renderDataSize,
},
});
table.className = 'package-details-table';
return table;
}

View File

@ -1,14 +1,38 @@
function initPackageSearch(sectionElement, sectionData, newParams) import * as AjaxHelper from './ajaxhelper.js';
import * as GenericRendering from './genericrendering.js';
import * as SinglePageHelper from './singlepage.js';
import * as PackageDetailsPage from './packagedetailspage.js';
import * as Utils from './utils.js';
export function initPackageSearch(sectionElement, sectionData, newParams)
{ {
const searchForm = document.getElementById('package-search-form');
if (!searchForm.dataset.initialized) {
searchForm.onsubmit = function() {
searchForPackages();
return false;
};
const packageResultsFormElements = document.getElementById('package-results-form').elements;
packageResultsFormElements.selectall.onclick = function () {
Utils.alterFormSelection(this.form, 'check-all');
};
packageResultsFormElements.unselectall.onclick = function () {
Utils.alterFormSelection(this.form, 'uncheck-all');
};
packageResultsFormElements.startselected.onclick = function () {
fillBuildActionFromPackageSearch();
};
searchForm.dataset.initialized = true;
}
const currentParams = sectionData.state.params; const currentParams = sectionData.state.params;
const hasNewParams = newParams.length >= 1; const hasNewParams = newParams.length >= 1;
if (!hasNewParams) { if (!hasNewParams) {
if (currentParams !== undefined) { if (currentParams !== undefined) {
updateHashPreventingChangeHandler('#package-search-section?' + encodeURIComponent(currentParams)); SinglePageHelper.updateHashPreventingChangeHandler('#package-search-section?' + encodeURIComponent(currentParams));
} }
return true; return true;
} }
const searchParams = sections['package-search'].state.params = newParams[0]; const searchParams = SinglePageHelper.sections['package-search'].state.params = newParams[0];
if (currentParams === searchParams) { if (currentParams === searchParams) {
return true; return true;
} }
@ -20,6 +44,17 @@ function initPackageSearch(sectionElement, sectionData, newParams)
return true; return true;
} }
function fillBuildActionFromPackageSearch()
{
const packageNamesTextArea = document.getElementById('build-action-form')['package-names'];
const data = Utils.getFormTableData('package-results-form');
if (data === undefined) {
return;
}
packageNamesTextArea.value = Utils.getSelectedRowProperties(data, 'name').join(' ');
location.hash = '#build-action-section';
}
function searchForPackagesFromParams(searchParams) function searchForPackagesFromParams(searchParams)
{ {
const params = new URLSearchParams(searchParams); const params = new URLSearchParams(searchParams);
@ -41,39 +76,39 @@ function searchForPackagesFromParams(searchParams)
formElement.value = value; formElement.value = value;
} }
}); });
const res = startFormQueryEx('package-search-form', showPackageSearchResults); const res = AjaxHelper.startFormQueryEx('package-search-form', showPackageSearchResults);
sections['package-search'].state.params = res.params; SinglePageHelper.sections['package-search'].state.params = res.params;
return res; return res;
} }
function searchForPackages() function searchForPackages()
{ {
const res = startFormQueryEx('package-search-form', showPackageSearchResults); const res = AjaxHelper.startFormQueryEx('package-search-form', showPackageSearchResults);
const params = sections['package-search'].state.params = res.params.substr(1); const params = SinglePageHelper.sections['package-search'].state.params = res.params.substr(1);
updateHashPreventingSectionInitializer('#package-search-section?' + encodeURIComponent(params)); SinglePageHelper.updateHashPreventingSectionInitializer('#package-search-section?' + encodeURIComponent(params));
return res.ajaxRequest; return res.ajaxRequest;
} }
function showPackageSearchResults(ajaxRequest) function showPackageSearchResults(ajaxRequest)
{ {
const packageSearchResults = getAndEmptyElement('package-search-results'); const packageSearchResults = Utils.getAndEmptyElement('package-search-results');
if (ajaxRequest.status !== 200) { if (ajaxRequest.status !== 200) {
packageSearchResults.appendChild(document.createTextNode('unable search for packages: ' + ajaxRequest.responseText)); packageSearchResults.appendChild(document.createTextNode('unable search for packages: ' + ajaxRequest.responseText));
return; return;
} }
const responseJson = JSON.parse(ajaxRequest.responseText); const responseJson = JSON.parse(ajaxRequest.responseText);
const table = renderTableFromJsonArray({ const table = GenericRendering.renderTableFromJsonArray({
rows: responseJson, rows: responseJson,
columnHeaders: ['', 'Arch', 'Repo', 'Name', 'Version', 'Description', 'Build date'], columnHeaders: ['', 'Arch', 'Repo', 'Name', 'Version', 'Description', 'Build date'],
columnAccessors: ['checkbox', 'arch', 'db', 'name', 'version', 'description', 'buildDate'], columnAccessors: ['checkbox', 'arch', 'db', 'name', 'version', 'description', 'buildDate'],
rowsPerPage: 40, rowsPerPage: 40,
customRenderer: { customRenderer: {
name: function (value, row) { name: function (value, row) {
return renderLink(value, row, queryPackageDetails, 'Show package details', undefined, return GenericRendering.renderLink(value, row, PackageDetailsPage.queryPackageDetails, 'Show package details', undefined,
'#package-details-section?' + encodeURIComponent(row.db + (row.dbArch ? '@' + row.dbArch : '') + '/' + value)); '#package-details-section?' + encodeURIComponent(row.db + (row.dbArch ? '@' + row.dbArch : '') + '/' + value));
}, },
checkbox: function(value, row) { checkbox: function(value, row) {
return renderCheckBoxForTableRow(value, row, function(row) { return GenericRendering.renderCheckBoxForTableRow(value, row, function(row) {
return [row.db, row.name].join('/'); return [row.db, row.name].join('/');
}); });
}, },

View File

@ -1,18 +1,34 @@
import * as GlobalStatusPage from './globalstatuspage.js';
import * as Utils from './utils.js';
export let sections = {};
export let sectionNames = [];
/// \brief 'main()' function which initializes the single page app. /// \brief 'main()' function which initializes the single page app.
function initPage() export function initPage(pageSections)
{ {
sections = pageSections;
sectionNames = Object.keys(sections);
handleHashChange(); handleHashChange();
queryGlobalStatus(); document.body.onhashchange = handleHashChange;
document.getElementById('logo-link').onclick = function () {
document.getElementById('about-dialog').style.display = 'block';
return false;
};
GlobalStatusPage.queryGlobalStatus();
} }
let preventHandlingHashChange = false;
let preventSectionInitializer = false;
/// \brief Shows the current section and hides other sections. /// \brief Shows the current section and hides other sections.
function handleHashChange() function handleHashChange()
{ {
if (window.preventHandlingHashChange) { if (preventHandlingHashChange) {
return; return;
} }
const hashParts = splitHashParts(); const hashParts = Utils.splitHashParts();
const currentSectionName = hashParts.shift() || 'global-section'; const currentSectionName = hashParts.shift() || 'global-section';
if (!currentSectionName.endsWith('-section')) { if (!currentSectionName.endsWith('-section')) {
return; return;
@ -23,7 +39,7 @@ function handleHashChange()
const sectionElement = document.getElementById(sectionName + '-section'); const sectionElement = document.getElementById(sectionName + '-section');
if (sectionElement.id === currentSectionName) { if (sectionElement.id === currentSectionName) {
const sectionInitializer = sectionData.initializer; const sectionInitializer = sectionData.initializer;
if (sectionInitializer === undefined || window.preventSectionInitializer || sectionInitializer(sectionElement, sectionData, hashParts)) { if (sectionInitializer === undefined || preventSectionInitializer || sectionInitializer(sectionElement, sectionData, hashParts)) {
sectionElement.style.display = 'block'; sectionElement.style.display = 'block';
} }
} else { } else {
@ -42,39 +58,17 @@ function handleHashChange()
} }
/// \brief Updates the #hash without triggering the handler. /// \brief Updates the #hash without triggering the handler.
function updateHashPreventingChangeHandler(newHash) export function updateHashPreventingChangeHandler(newHash)
{ {
window.preventHandlingHashChange = true; preventHandlingHashChange = true;
window.location.hash = newHash; window.location.hash = newHash;
window.preventHandlingHashChange = false; preventHandlingHashChange = false;
} }
/// \brief Updates the #hash without triggering the section initializer. /// \brief Updates the #hash without triggering the section initializer.
function updateHashPreventingSectionInitializer(newHash) export function updateHashPreventingSectionInitializer(newHash)
{ {
window.preventSectionInitializer = true; preventSectionInitializer = true;
window.location.hash = newHash; window.location.hash = newHash;
window.preventSectionInitializer = false; preventSectionInitializer = false;
} }
const sections = {
'global': {
},
'package-search': {
initializer: initPackageSearch,
state: {params: undefined},
},
'package-details': {
initializer: initPackageDetails,
state: {package: undefined},
},
'build-action': {
initializer: initBuildActionsForm,
},
'build-action-details': {
initializer: initBuildActionDetails,
state: {id: undefined},
},
};
const sectionNames = Object.keys(sections);
const status = {repoNames: undefined};

View File

@ -1,5 +1,7 @@
import * as AjaxHelper from './ajaxhelper.js';
/// \brief Returns a new terminal created via xterm.js. /// \brief Returns a new terminal created via xterm.js.
function makeTerminal() export function makeTerminal()
{ {
const terminal = new Terminal({ const terminal = new Terminal({
disableStdin: true, disableStdin: true,
@ -11,7 +13,7 @@ function makeTerminal()
} }
/// \brief Adds a search for the specified \a terminal to the specified \a targetElement. /// \brief Adds a search for the specified \a terminal to the specified \a targetElement.
function addSearchToTerminal(terminal, targetElement) export function addSearchToTerminal(terminal, targetElement)
{ {
const searchAddon = new SearchAddon(); const searchAddon = new SearchAddon();
// FIXME: import the search addon correctly // FIXME: import the search addon correctly
@ -41,7 +43,7 @@ function addSearchToTerminal(terminal, targetElement)
/// to the terminal. /// to the terminal.
/// \remarks This initialization only works if \a targetElement is already part of the rendered HTML page. Hence this function /// \remarks This initialization only works if \a targetElement is already part of the rendered HTML page. Hence this function
/// uses window.setTimeout to ensure \a targetElement is rendered. /// uses window.setTimeout to ensure \a targetElement is rendered.
function setupTerminalLater(terminal, targetElement, value) export function setupTerminalLater(terminal, targetElement, value)
{ {
window.setTimeout(function() { window.setTimeout(function() {
terminal.open(targetElement); terminal.open(targetElement);
@ -56,7 +58,7 @@ function setupTerminalLater(terminal, targetElement, value)
/// \brief Makes an AJAX query and writes the received data to the specified \a terminal. /// \brief Makes an AJAX query and writes the received data to the specified \a terminal.
/// \remarks If the server responds in chunks, each chunk is written as soon as it arrives. /// \remarks If the server responds in chunks, each chunk is written as soon as it arrives.
function streamRouteIntoTerminal(method, path, terminal) export function streamRouteIntoTerminal(method, path, terminal)
{ {
const ajaxRequest = new XMLHttpRequest(); const ajaxRequest = new XMLHttpRequest();
let responseWritten = 0; let responseWritten = 0;
@ -72,7 +74,7 @@ function streamRouteIntoTerminal(method, path, terminal)
} }
} }
}; };
ajaxRequest.open(method, apiPrefix + path, true); ajaxRequest.open(method, AjaxHelper.apiPrefix + path, true);
ajaxRequest.send(); ajaxRequest.send();
return ajaxRequest; return ajaxRequest;
} }

View File

@ -1,4 +1,5 @@
function splitHashParts()
export function splitHashParts()
{ {
const currentHash = location.hash.substr(1); const currentHash = location.hash.substr(1);
const hashParts = currentHash.split('?'); const hashParts = currentHash.split('?');
@ -8,10 +9,10 @@ function splitHashParts()
return hashParts; return hashParts;
} }
function hashAsObject() export function hashAsObject()
{ {
const hashObject = {}; const hashObject = {};
location.hash.substr(1).split('?').forEach(function(hashPart) { location.hash.substr(1).split('&').forEach(function(hashPart) {
const parts = hashPart.split('=', 2); const parts = hashPart.split('=', 2);
if (parts.length < 1) { if (parts.length < 1) {
return; return;
@ -21,12 +22,12 @@ function hashAsObject()
return hashObject; return hashObject;
} }
function getAndEmptyElement(elementId, specialActionsById) export function getAndEmptyElement(elementId, specialActionsById)
{ {
return emptyDomElement(document.getElementById(elementId), specialActionsById); return emptyDomElement(document.getElementById(elementId), specialActionsById);
} }
function emptyDomElement(domElement, specialActionsById) export function emptyDomElement(domElement, specialActionsById)
{ {
let child = domElement.firstChild; let child = domElement.firstChild;
while (child) { while (child) {
@ -40,7 +41,7 @@ function emptyDomElement(domElement, specialActionsById)
return domElement; return domElement;
} }
function alterFormSelection(form, command) export function alterFormSelection(form, command)
{ {
// modify form elements // modify form elements
const elements = form.elements; const elements = form.elements;
@ -78,7 +79,7 @@ function alterFormSelection(form, command)
} }
} }
function getProperty(object, property, fallback) export function getProperty(object, property, fallback)
{ {
if (typeof object !== 'object') { if (typeof object !== 'object') {
return fallback; return fallback;
@ -87,13 +88,13 @@ function getProperty(object, property, fallback)
return value !== undefined ? value : fallback; return value !== undefined ? value : fallback;
} }
function makeRepoName(dbName, dbArch) export function makeRepoName(dbName, dbArch)
{ {
return dbArch && dbArch !== 'x86_64' ? dbName + '@' + dbArch : dbName; return dbArch && dbArch !== 'x86_64' ? dbName + '@' + dbArch : dbName;
} }
/// \brief Returns the table row data for the table within the element with the specified ID. /// \brief Returns the table row data for the table within the element with the specified ID.
function getFormTableData(formId) export function getFormTableData(formId)
{ {
const formElement = document.getElementById(formId); const formElement = document.getElementById(formId);
const tableElement = formElement.getElementsByTagName('table')[0]; const tableElement = formElement.getElementsByTagName('table')[0];
@ -106,7 +107,7 @@ function getFormTableData(formId)
/// \brief Returns the cell values of selected rows. /// \brief Returns the cell values of selected rows.
/// \remarks The row data needs to be passed. The cell is determined by the specified \a propertyName. /// \remarks The row data needs to be passed. The cell is determined by the specified \a propertyName.
function getSelectedRowProperties(data, propertyName) export function getSelectedRowProperties(data, propertyName)
{ {
const propertyValues = []; const propertyValues = [];
data.forEach(function (row) { data.forEach(function (row) {

View File

@ -8,17 +8,13 @@
<meta name="author" content="Martchus" /> <meta name="author" content="Martchus" />
<meta name="keywords" content="pacman, Arch Linux, build, service" /> <meta name="keywords" content="pacman, Arch Linux, build, service" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<link rel="stylesheet" type="text/css" href="css/style.css" /> <link rel="stylesheet" type="text/css" href="css/basics.css" />
<link rel="stylesheet" type="text/css" href="css/log.css" /> <link rel="stylesheet" type="text/css" href="css/log.css" />
<script type="application/javascript" src="js/ajaxhelper.js"></script> <script type="module" src="js/log.js"></script>
<script type="application/javascript" src="js/utils.js"></script>
<script type="application/javascript" src="js/log.js"></script>
<!-- 3rd party libraries --> <!-- 3rd party libraries -->
<link rel="stylesheet" type="text/css" href="node_modules/xterm/css/xterm.css" /> <link rel="stylesheet" type="text/css" href="node_modules/xterm/css/xterm.css" />
<script type="application/javascript" src="node_modules/xterm/lib/xterm.js"></script> <script type="application/javascript" src="node_modules/xterm/lib/xterm.js"></script>
<script type="application/javascript" src="node_modules/xterm-addon-search/out/SearchAddon.js"></script> <script type="application/javascript" src="node_modules/xterm-addon-search/out/SearchAddon.js"></script>
</head> </head>
<body onload="initLog();" onhashchange="initLog();"> <body><main id="log-container"></main></body>
<main id="log-container"></main> </html>
</body>
</html>