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/genericrendering.css" />
<link rel="stylesheet" type="text/css" href="css/specifics.css" />
<script src="js/ajaxhelper.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>
<script type="module" src="js/main.js"></script>
<!-- include xterm.js -->
<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-addon-search/out/SearchAddon.js"></script>
</head>
<body onload="initPage();" onhashchange="handleHashChange();">
<body>
<header>
<nav>
<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" />
</a>
Repository Manager for Arch Linux<br />
@ -65,7 +56,7 @@
</section>
<section id="package-search-section">
<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">
<tr>
<th>Package name:</th>
@ -109,13 +100,13 @@
<fieldset>
<legend>Actions</legend>
<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
</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
</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
</button>
</div>
@ -131,15 +122,13 @@
<section id="build-action-section">
<h2>
Build actions
<span class="heading-actions">
<span class="heading-actions" id="build-action-toolbar">
<a href="#" title="Save state manually"
class="icon-link"
onclick="return triggerToolbarAction(this);"
data-action="/dump/cache-file"
data-method="POST"><img src="img/icon/content-save.svg" alt="Save state manually" class="icon" /></a>
<a href="#" title="Stop service"
class="icon-link"
onclick="return triggerToolbarAction(this);"
data-action="/quit"
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>
@ -152,22 +141,22 @@
<fieldset>
<legend>Modify selected actions</legend>
<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
</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
</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
</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
</button>
</div>
</fieldset>
</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>
<legend>Start new build action</legend>
<div>
@ -175,7 +164,7 @@
<div class="form-split-50">
<label for="build-action-task">Predefined task:</label>
<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>
</select>
</div>
@ -186,7 +175,7 @@
<div class="form-split-50">
<label for="build-action-type">Action:</label>
<br />
<select name="type" id="build-action-type" onchange="handleBuildActionTypeChange();"></select>
<select name="type" id="build-action-type"></select>
</div>
<div class="form-split-50">
<label for="build-action-directory">Directory:</label>
@ -262,7 +251,7 @@
</section>
</main>
<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">
<h1>Repository Manager for Arch Linux</h1>
<p><img src="img/logo.svg" alt="Logo" /></p>
@ -271,4 +260,4 @@
</div>
</div>
</body>
</html>
</html>

View File

@ -1,8 +1,8 @@
const apiPrefix = 'api/v0';
export const apiPrefix = 'api/v0';
let authError = false;
/// \brief Makes an AJAX query with basic error handling.
function queryRoute(method, path, callback)
export function queryRoute(method, path, callback)
{
const ajaxRequest = new XMLHttpRequest();
ajaxRequest.onreadystatechange = function() {
@ -34,13 +34,13 @@ function queryRoute(method, path, callback)
}
/// \brief Makes an AJAX query for the specified form.
function startFormQuery(formId, handler)
export function startFormQuery(formId, handler)
{
return startFormQueryEx(formId, handler).ajaxRequest;
}
/// \brief Makes an AJAX query for the specified form.
function startFormQueryEx(formId, handler)
export function startFormQueryEx(formId, handler)
{
const form = document.getElementById(formId);
const params = makeFormQueryParameter(form);
@ -105,7 +105,7 @@ function makeFormQueryParameter(form)
return "?" + params.join("&");
}
function makeIdParams(ids)
export function makeIdParams(ids)
{
if (!Array.isArray(ids)) {
ids = [ids];
@ -114,7 +114,7 @@ function makeIdParams(ids)
}
/// \brief Shows an alert for the specified AJAX request.
function showAjaxError(xhr, action)
export function showAjaxError(xhr, action)
{
let errorMessage;
try {
@ -129,7 +129,7 @@ function showAjaxError(xhr, action)
}
/// \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) {
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');
if (buildActionsForm.dataset.initialized) {
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();
handleBuildActionTypeChange();
buildActionsForm.dataset.initialized = true;
@ -12,21 +34,21 @@ function initBuildActionsForm()
function queryBuildActions()
{
queryRoute('GET', '/build-action', showBuildActions);
AjaxHelper.queryRoute('GET', '/build-action', showBuildActions);
return true;
}
function queryBuildActionDetails(ids)
{
queryRoute('GET', '/build-action/details?' + makeIdParams(ids), showBuildActionDetails);
AjaxHelper.queryRoute('GET', '/build-action/details?' + AjaxHelper.makeIdParams(ids), showBuildActionDetails);
return true;
}
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) {
return showAjaxError(xhr, 'clone build action');
return AjaxHelper.showAjaxError(xhr, 'clone build action');
}
const cloneIDs = JSON.parse(xhr.responseText);
if (cloneIDs.length === 1 && typeof cloneIDs[0] === 'number') {
@ -42,24 +64,24 @@ function cloneBuildAction(ids)
function deleteBuildAction(ids)
{
queryRoute('DELETE', '/build-action?' + makeIdParams(ids), function (xhr, success) {
success ? window.alert('Build action has been deleted.') : showAjaxError(xhr, 'delete build action');
AjaxHelper.queryRoute('DELETE', '/build-action?' + AjaxHelper.makeIdParams(ids), function (xhr, success) {
success ? window.alert('Build action has been deleted.') : AjaxHelper.showAjaxError(xhr, 'delete build action');
});
return true;
}
function stopBuildAction(ids)
{
queryRoute('POST', '/build-action/stop?' + makeIdParams(ids), function (xhr, success) {
success ? window.alert('Build action has been stopped.') : showAjaxError(xhr, 'stop build action');
AjaxHelper.queryRoute('POST', '/build-action/stop?' + AjaxHelper.makeIdParams(ids), function (xhr, success) {
success ? window.alert('Build action has been stopped.') : AjaxHelper.showAjaxError(xhr, 'stop build action');
});
return true;
}
function startBuildAction(ids)
{
queryRoute('POST', '/build-action/start?' + makeIdParams(ids), function (xhr, success) {
success ? window.alert('Build action has been started.') : showAjaxError(xhr, 'start build action');
AjaxHelper.queryRoute('POST', '/build-action/start?' + AjaxHelper.makeIdParams(ids), function (xhr, success) {
success ? window.alert('Build action has been started.') : AjaxHelper.showAjaxError(xhr, 'start build action');
});
return true;
}
@ -98,7 +120,7 @@ function prepareBuildActionBasedOnExistingOne(existingBuildAction)
});
}
function handleBuildActionTypeChange()
export function handleBuildActionTypeChange()
{
if (!globalInfo) {
return;
@ -129,14 +151,14 @@ function handleBuildActionTypeChange()
});
}
function handleBuildActionPresetChange()
export function handleBuildActionPresetChange()
{
if (!globalInfo) {
return;
}
const task = document.getElementById('build-action-task').value;
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');
if (!taskInfo) {
actionSelect.disabled = false;
@ -170,33 +192,33 @@ function renderBuildActionActions(actionValue, buildAction, refresh)
container.className = 'table-row-actions';
}
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);
return false;
}, 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 + '?')) {
cloneBuildAction(id);
}
}, 'Clone ' + id));
container.appendChild(renderIconLink('plus', buildAction, function() {
container.appendChild(CustomRendering.renderIconLink('plus', buildAction, function() {
prepareBuildActionBasedOnExistingOne(buildAction);
switchToBuildActions();
}, '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 + '?')) {
deleteBuildAction(id);
}
}, 'Delete ' + id));
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 + '?')) {
stopBuildAction(id);
}
}, 'Stop/decline ' + id));
}
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 + '?')) {
startBuildAction(id);
}
@ -211,16 +233,16 @@ function showBuildActions(ajaxRequest)
window.functionsPostponedUntilGlobalInfo.push(showBuildActions.bind(this, ...arguments));
return;
}
const buildActionsList = getAndEmptyElement('build-actions-list');
const buildActionsList = Utils.getAndEmptyElement('build-actions-list');
if (ajaxRequest.status !== 200) {
buildActionsList.appendChild(document.createTextNode('Unable to load build actions: ' + ajaxRequest.responseText));
buildActionsList.appendChild(renderReloadButton(queryBuildActions));
buildActionsList.appendChild(CustomRendering.renderReloadButton(queryBuildActions));
return;
}
const responseJson = JSON.parse(ajaxRequest.responseText);
if (!Array.isArray(responseJson)) {
buildActionsList.appendChild(document.createTextNode('Unable to load build actions: response is no array'));
buildActionsList.appendChild(renderReloadButton(queryBuildActions));
buildActionsList.appendChild(CustomRendering.renderReloadButton(queryBuildActions));
return;
}
@ -285,7 +307,7 @@ function showBuildActions(ajaxRequest)
};
// render the table
const container = renderTableFromJsonArray({
const container = GenericRendering.renderTableFromJsonArray({
rows: responseJson,
rowsPerPage: 10,
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'],
customRenderer: {
checkbox: function(value, row) {
return renderCheckBoxForTableRow(value, row, function(row) {
return GenericRendering.renderCheckBoxForTableRow(value, row, function(row) {
return row.name;
});
},
actions: renderBuildActionActions,
id: function(value, row) {
return renderLink(value, row, function() {
return GenericRendering.renderLink(value, row, function() {
queryBuildActionDetails(row.id);
return false;
}, 'Show details', undefined, '#build-action-details-section?' + row.id);
},
taskName: function (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) {
return document.createTextNode(getProperty(globalInfo.buildActionStates[value], 'name', 'Invalid/unknown'));
return document.createTextNode(Utils.getProperty(globalInfo.buildActionStates[value], 'name', 'Invalid/unknown'));
},
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) {
return document.createTextNode(getProperty(globalInfo.buildActionTypes[value], 'name', 'Invalid/debugging'));
return document.createTextNode(Utils.getProperty(globalInfo.buildActionTypes[value], 'name', 'Invalid/debugging'));
},
created: renderShortTimeStamp,
started: renderShortTimeStamp,
created: GenericRendering.renderShortTimeStamp,
started: GenericRendering.renderShortTimeStamp,
finished: function (value, row) {
return renderTimeSpan(row.started, value);
return GenericRendering.renderTimeSpan(row.started, value);
},
sourceDbs: renderArrayElidedAsCommaSeparatedString,
destinationDbs: renderArrayElidedAsCommaSeparatedString,
sourceDbs: GenericRendering.renderArrayElidedAsCommaSeparatedString,
destinationDbs: GenericRendering.renderArrayElidedAsCommaSeparatedString,
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) {
const note = document.createElement('p');
note.appendChild(document.createTextNode(rows.length + ' build actions present '));
note.appendChild(renderReloadButton(queryBuildActions));
note.appendChild(CustomRendering.renderReloadButton(queryBuildActions));
return note;
},
},
@ -396,15 +418,15 @@ function showBuildActions(ajaxRequest)
function switchToBuildActionDetails(buildActionIds)
{
sections['build-action-details'].state.ids = buildActionIds;
updateHashPreventingSectionInitializer(!Array.isArray(buildActionIds) || buildActionIds.length === 0
SinglePageHelper.sections['build-action-details'].state.ids = buildActionIds;
SinglePageHelper.updateHashPreventingSectionInitializer(!Array.isArray(buildActionIds) || buildActionIds.length === 0
? '#build-action-details-section'
: '#build-action-details-section?' + encodeURIComponent(buildActionIds.join(',')));
}
function switchToBuildActions()
{
updateHashPreventingSectionInitializer('#build-action-section');
SinglePageHelper.updateHashPreventingSectionInitializer('#build-action-section');
}
function showBuildActionDetails(ajaxRequest)
@ -413,7 +435,7 @@ function showBuildActionDetails(ajaxRequest)
window.functionsPostponedUntilGlobalInfo.push(showBuildActionDetails.bind(this, ...arguments));
return;
}
if (checkForAjaxError(ajaxRequest, 'show build action')) {
if (AjaxHelper.checkForAjaxError(ajaxRequest, 'show build action')) {
return;
}
const responseJSON = JSON.parse(ajaxRequest.responseText);
@ -422,8 +444,8 @@ function showBuildActionDetails(ajaxRequest)
function showBuildActionDetails2(buildActions)
{
const buildActionResults = getAndEmptyElement('build-action-results');
let buildActionActions = getAndEmptyElement('build-action-details-actions');
const buildActionResults = Utils.getAndEmptyElement('build-action-results');
let buildActionActions = Utils.getAndEmptyElement('build-action-details-actions');
buildActions.forEach(function (buildActionDetails) {
if (!buildActionActions) {
buildActionActions = document.createElement('span');
@ -439,33 +461,33 @@ function showBuildActionDetails2(buildActions)
function renderBuildActionDetailsTable(buildActionDetails)
{
return renderTableFromJsonObject({
return GenericRendering.renderTableFromJsonObject({
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'],
fieldAccessors: ['id', 'taskName', 'type', 'status', 'result', 'resultData', 'created', 'started', 'finished', 'startAfter', 'directory', 'sourceDbs', 'destinationDbs', 'packageNames', 'flags', 'settings', 'logfiles', 'artefacts', 'output'],
customRenderer: {
taskName: function (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) {
return document.createTextNode(getProperty(globalInfo.buildActionStates[value], 'name', 'Invalid/unknown'));
return document.createTextNode(Utils.getProperty(globalInfo.buildActionStates[value], 'name', 'Invalid/unknown'));
},
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) {
return document.createTextNode(getProperty(globalInfo.buildActionTypes[value], 'name', 'Invalid/debugging'));
return document.createTextNode(Utils.getProperty(globalInfo.buildActionTypes[value], 'name', 'Invalid/debugging'));
},
created: renderTimeStamp,
started: renderTimeStamp,
finished: renderTimeStamp,
startAfter: renderArrayAsCommaSeparatedString,
sourceDbs: renderArrayAsCommaSeparatedString,
destinationDbs: renderArrayAsCommaSeparatedString,
packageNames: renderArrayAsCommaSeparatedString,
created: GenericRendering.renderTimeStamp,
started: GenericRendering.renderTimeStamp,
finished: GenericRendering.renderTimeStamp,
startAfter: GenericRendering.renderArrayAsCommaSeparatedString,
sourceDbs: GenericRendering.renderArrayAsCommaSeparatedString,
destinationDbs: GenericRendering.renderArrayAsCommaSeparatedString,
packageNames: GenericRendering.renderArrayAsCommaSeparatedString,
flags: function(value, row) {
const flagNames = [];
const typeInfo = globalInfo.buildActionTypes[row.type];
@ -474,19 +496,19 @@ function renderBuildActionDetailsTable(buildActionDetails)
flagNames.push(flag.name);
}
});
return renderArrayAsCommaSeparatedString(flagNames);
return GenericRendering.renderArrayAsCommaSeparatedString(flagNames);
},
settings: function(value, row) {
const typeInfo = globalInfo.buildActionTypes[row.type];
if (typeInfo.settingNames.length === 0) {
return renderNoneInGrey();
return GenericRendering.renderNoneInGrey();
}
return renderTableFromJsonObject({
return GenericRendering.renderTableFromJsonObject({
data: value,
displayLabels: typeInfo.settingNames,
fieldAccessors: typeInfo.settingParams,
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
const formElement = document.createElement('form');
formElement.className = 'update-info-form';
formElement.appendChild(renderTableFromJsonObject({
formElement.appendChild(GenericRendering.renderTableFromJsonObject({
data: value.data,
relatedRow: row,
displayLabels: ['Version updates', 'Package updates', 'Downgrades', 'Orphans'],
@ -530,15 +552,15 @@ function renderBuildActionDetailsTable(buildActionDetails)
return formElement;
}
case 4: // build preparation info
return renderTableFromJsonObject({
return GenericRendering.renderTableFromJsonObject({
data: value.data,
displayLabels: ['Error', 'Warnings', 'Database config', 'Batches', 'Cyclic leftovers', 'Build data'],
fieldAccessors: ['error', 'warnings', 'dbConfig', 'batches', 'cyclicLeftovers', 'buildData'],
customRenderer: {
buildData: renderBuildPreparationResultData,
cyclicLeftovers: renderArrayAsCommaSeparatedString,
cyclicLeftovers: GenericRendering.renderArrayAsCommaSeparatedString,
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');
container.className = 'repo-problems';
for (const [database, problems] of Object.entries(value.data)) {
const table = renderTableFromJsonArray({
const table = GenericRendering.renderTableFromJsonArray({
rows: problems,
columnHeaders: ['Related package', 'Problem description'],
columnAccessors: ['pkg', 'desc'],
@ -555,16 +577,16 @@ function renderBuildActionDetailsTable(buildActionDetails)
desc: function(value) {
switch(value.index) {
case 1:
return renderTableFromJsonObject({
return GenericRendering.renderTableFromJsonObject({
data: value.data,
displayLabels: ['Missing dependencies', 'Missing libraries'],
fieldAccessors: ['deps', 'libs'],
customRenderer: {
deps: renderDependency,
deps: CustomRendering.renderDependency,
},
});
default:
return renderStandardTableCell(value.data);
return GenericRendering.renderStandardTableCell(value.data);
}
},
},
@ -575,7 +597,7 @@ function renderBuildActionDetailsTable(buildActionDetails)
return container;
}
default:
return renderStandardTableCell(value.data);
return GenericRendering.renderStandardTableCell(value.data);
}
},
logfiles: renderBuildActionLogFiles,
@ -583,12 +605,12 @@ function renderBuildActionDetailsTable(buildActionDetails)
output: function(value, row) {
const isFinished = row.status === 4;
if (!value && isFinished) {
return renderNoneInGrey();
return GenericRendering.renderNoneInGrey();
}
const targetElement = document.createElement('div');
if (isFinished) {
const terminal = makeTerminal();
setupTerminalLater(terminal, targetElement, value);
const terminal = Terminal.makeTerminal();
Terminal.setupTerminalLater(terminal, targetElement, value);
return targetElement;
}
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 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 ajaxRequest;
return {
elements: [targetElement],
startStreaming: function () {
terminal = makeTerminal();
ajaxRequest = streamRouteIntoTerminal('GET', path, terminal);
setupTerminalLater(terminal, targetElement);
terminal = Terminal.makeTerminal();
ajaxRequest = Terminal.streamRouteIntoTerminal('GET', path, terminal);
Terminal.setupTerminalLater(terminal, targetElement);
},
stopStreaming: function () {
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()
{
startFormQuery('build-action-form', handleBuildActionResponse);
AjaxHelper.startFormQuery('build-action-form', handleBuildActionResponse);
return false;
}
function handleBuildActionResponse(ajaxRequest)
{
const results = getAndEmptyElement('build-action-results');
const results = Utils.getAndEmptyElement('build-action-results');
if (ajaxRequest.status !== 200) {
results.appendChild(document.createTextNode('unable to create build action: ' + ajaxRequest.responseText));
switchToBuildActionDetails();
@ -664,7 +676,7 @@ function handleBuildActionResponse(ajaxRequest)
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 logFilePath = '/build-action/logfile?' + params;
const newWindowPath = 'log.html#' + params;
@ -675,25 +687,25 @@ function renderBuildActionLogFiles(array, obj)
path: logFilePath,
});
const basicElements = streamingSetup.elements;
const openInNewWindowLinkElement = 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 stopStreamingLinkElement = renderIconLink('stop', obj, function() {
const openInNewWindowLinkElement = CustomRendering.renderIconLink('dock-window', obj, function() { window.open(newWindowPath); }, 'Open in new window');
const downloadLinkElement = CustomRendering.renderIconLink('download', obj, function() { window.open(AjaxHelper.apiPrefix + logFilePath); }, 'Download log');
const stopStreamingLinkElement = CustomRendering.renderIconLink('stop', obj, function() {
if (!streamingSetup.isStreaming()) {
return;
}
streamingSetup.stopStreaming();
targetElement.style.display = stopStreamingLinkElement.style.display = 'none';
emptyDomElement(targetElement);
Utils.emptyDomElement(targetElement);
}, 'Close log');
const startStreamingLinkElement = renderLink(arrayElement, obj, function() {
const startStreamingLinkElement = GenericRendering.renderLink(arrayElement, obj, function() {
if (streamingSetup.isStreaming()) {
return;
}
emptyDomElement(targetElement);
Utils.emptyDomElement(targetElement);
streamingSetup.startStreaming();
targetElement.style.display = 'block';
stopStreamingLinkElement.style.display = 'inline-block';
}, 'Show log file', apiPrefix + logFilePath);
}, 'Show log file', AjaxHelper.apiPrefix + logFilePath);
targetElement.style.display = stopStreamingLinkElement.style.display = 'none';
[downloadLinkElement, stopStreamingLinkElement].forEach(function(element) {
element.classList.add('streaming-link');
@ -704,9 +716,9 @@ function renderBuildActionLogFiles(array, obj)
function renderBuildActionArtefacts(array, obj)
{
return renderCustomList(array, function(arrayElement) {
const path = apiPrefix + '/build-action/artefact?id=' + encodeURIComponent(obj.id) + '&name=' + encodeURIComponent(arrayElement);
return renderLink(arrayElement, obj, function() {
return GenericRendering.renderCustomList(array, function(arrayElement) {
const path = AjaxHelper.apiPrefix + '/build-action/artefact?id=' + encodeURIComponent(obj.id) + '&name=' + encodeURIComponent(arrayElement);
return GenericRendering.renderLink(arrayElement, obj, function() {
window.open(path);
}, 'Download artefact', path);
});
@ -751,12 +763,12 @@ function renderUpdateInfoWithCheckbox(id, packageName, newPackageName, versionIn
function renderPackageList(packageList)
{
return renderCustomList(packageList, renderPackage);
return GenericRendering.renderCustomList(packageList, CustomRendering.renderPackage);
}
function renderBuildPreparationBuildData(buildDataForPackage)
{
return renderTableFromJsonObject({
return GenericRendering.renderTableFromJsonObject({
data: buildDataForPackage,
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'],
@ -769,7 +781,7 @@ function renderBuildPreparationBuildData(buildDataForPackage)
function makeVersionsString(packages)
{
const versions = packages.map(package => package.version);
const versions = packages.map(packageObj => packageObj.version);
if (versions.length === 0) {
return '?';
} else if (versions.length === 1) {
@ -786,7 +798,7 @@ function renderBuildPreparationResultData(buildPreparationData)
const heading = document.createElement('h4');
let table;
heading.className = 'compact-heading';
heading.appendChild(renderLink(packageName, undefined, function() {
heading.appendChild(GenericRendering.renderLink(packageName, undefined, function() {
if (table === undefined) {
table = renderBuildPreparationBuildData(buildDataForPackage);
heading.insertAdjacentElement('afterEnd', table);
@ -808,13 +820,13 @@ function renderBuildPreparationResultData(buildPreparationData)
function renderOrphanPackage(value, obj, level, row)
{
return renderCustomList(value, function(package) {
const packageName = package.name;
return GenericRendering.renderCustomList(value, function(packageObj) {
const packageName = packageObj.name;
return renderUpdateInfoWithCheckbox(
'update-info-checkbox-' + packageName + '-' + package.version,
'update-info-checkbox-' + packageName + '-' + packageObj.version,
packageName,
undefined,
package.version,
packageObj.version,
row.sourceDbs,
);
}, function(package1, package2) {
@ -824,7 +836,7 @@ function renderOrphanPackage(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 newVersion = updateInfo.newVersion;
const packageName = oldVersion.name;
@ -843,12 +855,15 @@ function renderUpdateOrDowngrade(value, obj, level, row)
function triggerToolbarAction(toolbarElement)
{
if (!toolbarElement || toolbarElement instanceof PointerEvent) {
toolbarElement = this;
}
const confirmQuestion = toolbarElement.dataset.confirmation;
if (confirmQuestion !== undefined && !window.confirm(confirmQuestion)) {
return false;
}
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);
toolbarElement.disabled = false;
});
@ -857,11 +872,11 @@ function triggerToolbarAction(toolbarElement)
function deleteSelectedActions()
{
const data = getFormTableData('build-actions-list');
const data = Utils.getFormTableData('build-actions-list');
if (data === undefined) {
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(', ') + '?')) {
deleteBuildAction(ids);
}
@ -869,23 +884,23 @@ function deleteSelectedActions()
function showSelectedActions()
{
const data = getFormTableData('build-actions-list');
const data = Utils.getFormTableData('build-actions-list');
if (data === undefined) {
return;
}
const ids = getSelectedRowProperties(data, 'id');
const ids = Utils.getSelectedRowProperties(data, 'id');
if (ids.length) {
queryBuildActionDetails(ids);
}
}
function initBuildActionDetails(sectionElement, sectionData, newHashParts)
export function initBuildActionDetails(sectionElement, sectionData, newHashParts)
{
const currentBuildActionIds = sectionData.state.ids;
const hasCurrentlyBuildActions = Array.isArray(currentBuildActionIds) && currentBuildActionIds.length !== 0;
if (!newHashParts.length) {
if (hasCurrentlyBuildActions) {
updateHashPreventingChangeHandler('#build-action-details-section?' + encodeURIComponent(currentBuildActionIds.join(',')));
SinglePageHelper.updateHashPreventingChangeHandler('#build-action-details-section?' + encodeURIComponent(currentBuildActionIds.join(',')));
}
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.
function renderDependency(value)
export function renderDependency(value)
{
if (value.length < 1) {
return renderArrayAsCommaSeparatedString(value);
return GenericRendering.renderArrayAsCommaSeparatedString(value);
}
const list = document.createElement('ul');
list.className = 'dependency-list';
@ -29,7 +32,7 @@ function renderDependency(value)
}
/// \brief Renders a "Reload" button invoking the specified \a handler when clicked.
function renderReloadButton(handler)
export function renderReloadButton(handler)
{
const reloadButton = document.createElement('button');
reloadButton.className = 'icon-button icon-reload';
@ -40,7 +43,7 @@ function renderReloadButton(handler)
}
/// \brief Renders an icon.
function renderIcon(iconName)
export function renderIcon(iconName)
{
const icon = document.createElement('span');
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.
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';
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.
function renderNoneInGrey(value, row, elementName, noneText)
export function renderNoneInGrey(value, row, elementName, noneText)
{
const noValue = value === undefined || value === null || value === 'none' || value === 'None' ||
value === '' || value === 18446744073709552000;
@ -18,7 +18,7 @@ function renderNoneInGrey(value, row, elementName, noneText)
/// \brief Renders a standard table cell.
/// \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;
if (dataType !== 'object') {
@ -43,7 +43,7 @@ function renderStandardTableCell(data, allData, level)
}
/// \brief Renders a custom list.
function renderCustomList(array, customRenderer, compareFunction)
export function renderCustomList(array, customRenderer, compareFunction)
{
if (!Array.isArray(array) || array.length < 1) {
return renderNoneInGrey();
@ -68,7 +68,7 @@ function renderCustomList(array, customRenderer, compareFunction)
}
/// \brief Renders a list of links.
function renderLinkList(array, obj, handler)
export function renderLinkList(array, obj, handler)
{
return renderCustomList(array, function(arrayElement) {
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.
function formatTimeAgoString(date)
export function formatTimeAgoString(date)
{
const seconds = Math.floor((new Date() - date) / 1000);
let interval = Math.floor(seconds / 31536000);
@ -105,13 +105,13 @@ function formatTimeAgoString(date)
}
/// \brief Returns a Date object from the specified time stamp.
function dateFromTimeStamp(timeStamp)
export function dateFromTimeStamp(timeStamp)
{
return new Date(timeStamp + 'Z');
}
/// \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);
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.
function renderTimeStamp(timeStamp)
export function renderTimeStamp(timeStamp)
{
const date = dateFromTimeStamp(timeStamp);
if (date.getFullYear() === 1) {
@ -134,7 +134,7 @@ function renderTimeStamp(timeStamp)
}
/// \brief Renders a time delta from 2 time stamps.
function renderTimeSpan(startTimeStamp, endTimeStamp)
export function renderTimeSpan(startTimeStamp, endTimeStamp)
{
const startDate = dateFromTimeStamp(startTimeStamp);
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.
function renderLink(value, row, handler, tooltip, href, middleClickHref)
export function renderLink(value, row, handler, tooltip, href, middleClickHref)
{
const linkElement = document.createElement('a');
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.
function renderArrayAsCommaSeparatedString(value)
export function renderArrayAsCommaSeparatedString(value)
{
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.
function renderArrayElidedAsCommaSeparatedString(value)
export function renderArrayElidedAsCommaSeparatedString(value)
{
if (!Array.isArray(value) || value.length <= 0) {
return renderNoneInGrey('none');
@ -203,7 +203,7 @@ function renderArrayElidedAsCommaSeparatedString(value)
}
/// \brief Renders the specified value, possibly eliding the end.
function renderTextPossiblyElidingTheEnd(value)
export function renderTextPossiblyElidingTheEnd(value)
{
const limit = 50;
if (value.length < limit) {
@ -223,7 +223,7 @@ function renderTextPossiblyElidingTheEnd(value)
}
/// \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') {
return renderNoneInGrey('none');
@ -264,7 +264,7 @@ function accessProperty(object, accessor)
}
/// \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');
checkbox.type = 'checkbox';
@ -275,7 +275,7 @@ function renderCheckBoxForTableRow(value, row, computeCheckBoxValue)
}
/// \brief Returns a table for the specified JSON array.
function renderTableFromJsonArray(args)
export function renderTableFromJsonArray(args)
{
// handle arguments
const rows = args.rows;
@ -568,7 +568,7 @@ function renderTableFromJsonArray(args)
}
/// \brief Returns a table for the specified JSON object.
function renderTableFromJsonObject(args)
export function renderTableFromJsonObject(args)
{
// handle arguments
const data = args.data;
@ -619,7 +619,7 @@ function renderTableFromJsonObject(args)
}
/// \brief Returns a heading for each key and values via renderStandardTableCell().
function renderObjectWithHeadings(object, row, level)
export function renderObjectWithHeadings(object, row, level)
{
const elements = [];
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;
}
function handleGlobalStatusUpdate(ajaxRequest)
{
const globalStatus = getAndEmptyElement('global-status');
const globalStatus = Utils.getAndEmptyElement('global-status');
let responseText = ajaxRequest.responseText;
if (ajaxRequest.status === 500) {
responseText = 'internal server error';
@ -18,24 +26,24 @@ function handleGlobalStatusUpdate(ajaxRequest)
const responseJson = JSON.parse(responseText);
const applicationVersion = responseJson.version;
if (applicationVersion) {
getAndEmptyElement('application-version').appendChild(document.createTextNode(applicationVersion));
Utils.getAndEmptyElement('application-version').appendChild(document.createTextNode(applicationVersion));
}
const dbStats = responseJson.config.dbStats;
const table = renderTableFromJsonArray({
const table = GenericRendering.renderTableFromJsonArray({
rows: dbStats,
columnHeaders: ['Arch', 'Database', 'Package count', 'Last update', 'Synced from mirror'],
columnAccessors: ['arch', 'name', 'packageCount', 'lastUpdate', 'syncFromMirror'],
customRenderer: {
name: function (value, row) {
return renderLink(value, row, showRepository);
return GenericRendering.renderLink(value, row, showRepository);
},
lastUpdate: renderShortTimeStamp,
lastUpdate: GenericRendering.renderShortTimeStamp,
note: function(rows) {
const note = document.createElement('div');
const totalPackageCount = rows.reduce((acc, cur) => acc + cur.packageCount, 0);
note.className = 'form-row';
note.appendChild(document.createTextNode(rows.length + ' databases and ' + totalPackageCount + ' packages '));
note.appendChild(renderReloadButton(queryGlobalStatus));
note.appendChild(CustomRendering.renderReloadButton(queryGlobalStatus));
return note;
},
},
@ -44,13 +52,13 @@ function handleGlobalStatusUpdate(ajaxRequest)
// update database selections
const repoSelections = [
getAndEmptyElement('build-action-source-repo', {'build-action-source-repo-none': 'keep'}),
getAndEmptyElement('build-action-destination-repo', {'build-action-destination-repo-none': 'keep'}),
getAndEmptyElement('package-search-db', {'package-search-db-any': 'keep'}),
Utils.getAndEmptyElement('build-action-source-repo', {'build-action-source-repo-none': 'keep'}),
Utils.getAndEmptyElement('build-action-destination-repo', {'build-action-destination-repo-none': 'keep'}),
Utils.getAndEmptyElement('package-search-db', {'package-search-db-any': 'keep'}),
];
status.repoNames = [];
dbStats.forEach(function (dbInfo) {
const repoName = makeRepoName(dbInfo.name, dbInfo.arch);
const repoName = Utils.makeRepoName(dbInfo.name, dbInfo.arch);
status.repoNames.push(repoName);
repoSelections.forEach(function (selection) {
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
const buildActionTypeSelect = getAndEmptyElement('build-action-type');
const buildActionFlagsContainer = getAndEmptyElement('build-action-flags');
const buildActionSettingsTable = getAndEmptyElement('build-action-settings');
const buildActionTypeSelect = Utils.getAndEmptyElement('build-action-type');
const buildActionFlagsContainer = Utils.getAndEmptyElement('build-action-flags');
const buildActionSettingsTable = Utils.getAndEmptyElement('build-action-settings');
let optgroupElements = {};
const makeOrReuseOptgroup = function (label) {
const existingOptgroupElement = optgroupElements[label];
@ -148,7 +156,7 @@ function handleGlobalStatusUpdate(ajaxRequest)
});
// 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;
globalInfo.presets = presets;
optgroupElements = {};
@ -167,8 +175,8 @@ function handleGlobalStatusUpdate(ajaxRequest)
window.functionsPostponedUntilGlobalInfo.forEach(function(f) { f(); });
window.functionsPostponedUntilGlobalInfo = [];
handleBuildActionTypeChange();
handleBuildActionPresetChange();
BuildActionsPage.handleBuildActionTypeChange();
BuildActionsPage.handleBuildActionPresetChange();
}
function showRepository(dbName, dbInfo)

View File

@ -1,21 +1,27 @@
import * as Terminal from './terminal.js';
import * as Utils from './utils.js';
function initLog()
{
const hashParts = hashAsObject();
const hashParts = Utils.hashAsObject();
const id = hashParts.id;
const name = hashParts.name;
const mainElement = getAndEmptyElement('log-container');
const mainElement = Utils.getAndEmptyElement('log-container');
if (id === undefined || id === '' || name === undefined || name === '') {
document.title += ' - logfile';
mainElement.appendChild(document.createTextNode('id or name invalid'));
return;
}
const path = '/build-action/logfile?id=' + encodeURIComponent(id) + '&name=' + encodeURIComponent(name);
const terminal = makeTerminal();
const ajaxRequest = streamRouteIntoTerminal('GET', path, terminal);
const terminal = Terminal.makeTerminal();
const ajaxRequest = Terminal.streamRouteIntoTerminal('GET', path, terminal);
document.title += ' - ' + name;
terminal.resize(180, 500);
window.setTimeout(function() {
addSearchToTerminal(terminal, mainElement);
Terminal.addSearchToTerminal(terminal, 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 hasNewPackages = newPackages.length >= 1;
if (!hasNewPackages) {
if (currentPackage !== undefined) {
updateHashPreventingChangeHandler('#package-details-section?' + encodeURIComponent(currentPackage));
SinglePageHelper.updateHashPreventingChangeHandler('#package-details-section?' + encodeURIComponent(currentPackage));
}
return true;
}
@ -13,12 +19,12 @@ function initPackageDetails(sectionElement, sectionData, newPackages)
return true;
}
const packageParts = packageStr.split('/');
const package = {
const packageObj = {
db: packageParts[0],
name: packageParts[1]
};
queryRoute('GET', '/packages?details=1&name=' + encodeURIComponent(packageStr), function(ajaxRequest) {
showPackageDetails(ajaxRequest, package);
AjaxHelper.queryRoute('GET', '/packages?details=1&name=' + encodeURIComponent(packageStr), function(ajaxRequest) {
showPackageDetails(ajaxRequest, packageObj);
});
return true;
}
@ -30,21 +36,21 @@ function makePackageID(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);
});
}
function switchToPackageDetails(packageID)
{
sections['package-details'].state.package = packageID;
updateHashPreventingSectionInitializer('#package-details-section?' + encodeURIComponent(packageID));
SinglePageHelper.sections['package-details'].state.package = packageID;
SinglePageHelper.updateHashPreventingSectionInitializer('#package-details-section?' + encodeURIComponent(packageID));
}
function showPackageDetails(ajaxRequest, row)
{
const packageID = makePackageID(row);
const packageDetailsContainer = getAndEmptyElement('package-details-container');
const packageDetailsContainer = Utils.getAndEmptyElement('package-details-container');
if (ajaxRequest.status !== 200) {
packageDetailsContainer.appendChild(document.createTextNode('unable query package details: ' + ajaxRequest.responseText));
return;
@ -55,62 +61,14 @@ function showPackageDetails(ajaxRequest, row)
packageDetailsContainer.appendChild(document.createTextNode('unable query package details: package not present'));
return;
}
const package = responseJson[0];
const packageObj = responseJson[0];
const heading = document.createElement('h3');
heading.appendChild(document.createTextNode(package.name));
heading.appendChild(document.createTextNode(' ' + package.version));
heading.appendChild(document.createTextNode(packageObj.name));
heading.appendChild(document.createTextNode(' ' + packageObj.version));
packageDetailsContainer.appendChild(heading);
package.db = row.db;
package.dbArch = row.dbArch;
packageDetailsContainer.appendChild(renderPackage(package, true));
packageObj.db = row.db;
packageObj.dbArch = row.dbArch;
packageDetailsContainer.appendChild(CustomRendering.renderPackage(packageObj, true));
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 hasNewParams = newParams.length >= 1;
if (!hasNewParams) {
if (currentParams !== undefined) {
updateHashPreventingChangeHandler('#package-search-section?' + encodeURIComponent(currentParams));
SinglePageHelper.updateHashPreventingChangeHandler('#package-search-section?' + encodeURIComponent(currentParams));
}
return true;
}
const searchParams = sections['package-search'].state.params = newParams[0];
const searchParams = SinglePageHelper.sections['package-search'].state.params = newParams[0];
if (currentParams === searchParams) {
return true;
}
@ -20,6 +44,17 @@ function initPackageSearch(sectionElement, sectionData, newParams)
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)
{
const params = new URLSearchParams(searchParams);
@ -41,39 +76,39 @@ function searchForPackagesFromParams(searchParams)
formElement.value = value;
}
});
const res = startFormQueryEx('package-search-form', showPackageSearchResults);
sections['package-search'].state.params = res.params;
const res = AjaxHelper.startFormQueryEx('package-search-form', showPackageSearchResults);
SinglePageHelper.sections['package-search'].state.params = res.params;
return res;
}
function searchForPackages()
{
const res = startFormQueryEx('package-search-form', showPackageSearchResults);
const params = sections['package-search'].state.params = res.params.substr(1);
updateHashPreventingSectionInitializer('#package-search-section?' + encodeURIComponent(params));
const res = AjaxHelper.startFormQueryEx('package-search-form', showPackageSearchResults);
const params = SinglePageHelper.sections['package-search'].state.params = res.params.substr(1);
SinglePageHelper.updateHashPreventingSectionInitializer('#package-search-section?' + encodeURIComponent(params));
return res.ajaxRequest;
}
function showPackageSearchResults(ajaxRequest)
{
const packageSearchResults = getAndEmptyElement('package-search-results');
const packageSearchResults = Utils.getAndEmptyElement('package-search-results');
if (ajaxRequest.status !== 200) {
packageSearchResults.appendChild(document.createTextNode('unable search for packages: ' + ajaxRequest.responseText));
return;
}
const responseJson = JSON.parse(ajaxRequest.responseText);
const table = renderTableFromJsonArray({
const table = GenericRendering.renderTableFromJsonArray({
rows: responseJson,
columnHeaders: ['', 'Arch', 'Repo', 'Name', 'Version', 'Description', 'Build date'],
columnAccessors: ['checkbox', 'arch', 'db', 'name', 'version', 'description', 'buildDate'],
rowsPerPage: 40,
customRenderer: {
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));
},
checkbox: function(value, row) {
return renderCheckBoxForTableRow(value, row, function(row) {
return GenericRendering.renderCheckBoxForTableRow(value, row, function(row) {
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.
function initPage()
export function initPage(pageSections)
{
sections = pageSections;
sectionNames = Object.keys(sections);
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.
function handleHashChange()
{
if (window.preventHandlingHashChange) {
if (preventHandlingHashChange) {
return;
}
const hashParts = splitHashParts();
const hashParts = Utils.splitHashParts();
const currentSectionName = hashParts.shift() || 'global-section';
if (!currentSectionName.endsWith('-section')) {
return;
@ -23,7 +39,7 @@ function handleHashChange()
const sectionElement = document.getElementById(sectionName + '-section');
if (sectionElement.id === currentSectionName) {
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';
}
} else {
@ -42,39 +58,17 @@ function handleHashChange()
}
/// \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.preventHandlingHashChange = false;
preventHandlingHashChange = false;
}
/// \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.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.
function makeTerminal()
export function makeTerminal()
{
const terminal = new Terminal({
disableStdin: true,
@ -11,7 +13,7 @@ function makeTerminal()
}
/// \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();
// FIXME: import the search addon correctly
@ -41,7 +43,7 @@ function addSearchToTerminal(terminal, targetElement)
/// to the terminal.
/// \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.
function setupTerminalLater(terminal, targetElement, value)
export function setupTerminalLater(terminal, targetElement, value)
{
window.setTimeout(function() {
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.
/// \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();
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();
return ajaxRequest;
}

View File

@ -1,4 +1,5 @@
function splitHashParts()
export function splitHashParts()
{
const currentHash = location.hash.substr(1);
const hashParts = currentHash.split('?');
@ -8,10 +9,10 @@ function splitHashParts()
return hashParts;
}
function hashAsObject()
export function hashAsObject()
{
const hashObject = {};
location.hash.substr(1).split('?').forEach(function(hashPart) {
location.hash.substr(1).split('&').forEach(function(hashPart) {
const parts = hashPart.split('=', 2);
if (parts.length < 1) {
return;
@ -21,12 +22,12 @@ function hashAsObject()
return hashObject;
}
function getAndEmptyElement(elementId, specialActionsById)
export function getAndEmptyElement(elementId, specialActionsById)
{
return emptyDomElement(document.getElementById(elementId), specialActionsById);
}
function emptyDomElement(domElement, specialActionsById)
export function emptyDomElement(domElement, specialActionsById)
{
let child = domElement.firstChild;
while (child) {
@ -40,7 +41,7 @@ function emptyDomElement(domElement, specialActionsById)
return domElement;
}
function alterFormSelection(form, command)
export function alterFormSelection(form, command)
{
// modify 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') {
return fallback;
@ -87,13 +88,13 @@ function getProperty(object, property, fallback)
return value !== undefined ? value : fallback;
}
function makeRepoName(dbName, dbArch)
export function makeRepoName(dbName, dbArch)
{
return dbArch && dbArch !== 'x86_64' ? dbName + '@' + dbArch : dbName;
}
/// \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 tableElement = formElement.getElementsByTagName('table')[0];
@ -106,7 +107,7 @@ function getFormTableData(formId)
/// \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.
function getSelectedRowProperties(data, propertyName)
export function getSelectedRowProperties(data, propertyName)
{
const propertyValues = [];
data.forEach(function (row) {

View File

@ -8,17 +8,13 @@
<meta name="author" content="Martchus" />
<meta name="keywords" content="pacman, Arch Linux, build, service" />
<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" />
<script type="application/javascript" src="js/ajaxhelper.js"></script>
<script type="application/javascript" src="js/utils.js"></script>
<script type="application/javascript" src="js/log.js"></script>
<script type="module" src="js/log.js"></script>
<!-- 3rd party libraries -->
<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-addon-search/out/SearchAddon.js"></script>
</head>
<body onload="initLog();" onhashchange="initLog();">
<main id="log-container"></main>
</body>
</html>
<body><main id="log-container"></main></body>
</html>