4 * A bunch of shorthand to put together common parts of an HTML page.
8 * Some fields may have values outside normal dates, handle them here.
10 * @param {String} otherwise
12 const dateOrNot
= (date
, otherwise
) => {
16 if (typeof date
=== 'number') {
17 date
= new Date(date
);
19 const dateMs
= date
.getTime();
20 if (!Number
.isFinite(dateMs
)
24 return date
.toString();
30 * @param {Number} seconds
33 const secondsToPeriod
= (seconds
) => {
37 const nextResult
= (factor
, label
) => {
38 const r
= factor
? value
% factor : value
;
40 result
.push(`${r} ${label}${r != 1 ? 's' : ''}`);
42 value
= factor
? Math
.floor(value
/ factor
) : value
;
45 nextResult(60, 'second');
46 nextResult(60, 'minute');
47 nextResult(24, 'hour');
48 nextResult(30, 'day');
49 nextResult(undefined, 'month');
52 return result
.join(' ');
57 * Render a topic as a row of details.
58 * @param {Object} topic
59 * @param {Object[]} subscribers
60 * @param {Boolean} detailsLink
63 function renderTopicRow(topic
, subscribers
, detailsLink
= true) {
66 <th colspan="15">(topic not found)</th>
70 <th scope="row">${detailsLink ? '<a href="topic/' + topic.id + '">' : ''}${topic.url}${detailsLink ? '</a>' : ''}</th>
71 <td>${subscribers.length}</td>
72 <td>${dateOrNot(topic.created, 'Unknown')}</td>
73 <td>${secondsToPeriod(topic.leaseSecondsPreferred)}</td>
74 <td>${secondsToPeriod(topic.leaseSecondsMin)}</td>
75 <td>${secondsToPeriod(topic.leaseSecondsMax)}</td>
76 <td>${topic.publisherValidationUrl ? topic.publisherValidationUrl : 'None'}</td>
77 <td>${topic.isActive}</td>
78 <td>${topic.isDeleted}</td>
79 <td>${dateOrNot(topic.lastPublish, 'Never')}</td>
80 <td>${dateOrNot(topic.contentFetchNextAttempt, 'Next Publish')}</td>
81 <td>${topic.contentFetchAttemptsSinceSuccess}</td>
82 <td>${dateOrNot(topic.contentUpdated, 'Never')}</td>
83 <td>${topic.contentType}</td>
90 * Render the header row for topic details.
93 function renderTopicRowHeader() {
95 <th scope="col">Topic URL</th>
96 <th scope="col">Subscribers</th>
97 <th scope="col">Created</th>
98 <th scope="col">Lease Time Preferred</th>
99 <th scope="col">Lease Time Minimum</th>
100 <th scope="col">Lease Time Maximum</th>
101 <th scope="col">Publisher Validation URL</th>
102 <th scope="col">Active</th>
103 <th scope="col">Deleted</th>
104 <th scope="col">Last Publish Notification</th>
105 <th scope="col">Next Content Fetch</th>
106 <th scope="col">Content Fetch Failures</th>
107 <th scope="col">Content Updated</th>
108 <th scope="col">Content Type</th>
109 <th scope="col">ID</th>
115 * Render a subscription as a row of details.
116 * @param {Object} subscription
119 function renderSubscriptionRow(subscription
) {
122 <th colspan="12">(topic not found)</th>
126 <td scope="row">${subscription.callback}</td>
127 <td>${dateOrNot(subscription.created, 'Unknown')}</td>
128 <td>${dateOrNot(subscription.verified, 'Never')}</td>
129 <td>${dateOrNot(subscription.expires, 'Never')}</td>
130 <td>${!!subscription.secret}</td>
131 <td>${subscription.signatureAlgorithm}</td>
132 <td>${subscription.httpRemoteAddr}</td>
133 <td>${subscription.httpFrom}</td>
134 <td>${dateOrNot(subscription.contentDelivered, 'Never')}</td>
135 <td>${subscription.deliveryAttemptsSinceSuccess}</td>
136 <td>${dateOrNot(subscription.deliveryNextAttempt, 'Next Publish')}</td>
137 <td>${subscription.id}</td>
143 * Render a row of headers for subscription details.
146 function renderSubscriptionRowHeader() {
148 <th scope="col">Callback URL</th>
149 <th scope="col">Created</th>
150 <th scope="col">Verified</th>
151 <th scope="col">Expires</th>
152 <th scope="col">Using Secret</th>
153 <th scope="col">Signature Type</th>
154 <th scope="col">Remote Address</th>
155 <th scope="col">From</th>
156 <th scope="col">Content Delivered</th>
157 <th scope="col">Content Delivery Failures</th>
158 <th scope="col">Next Delivery</th>
159 <th scope="col">ID</th>
166 * Render the preamble for an HTML page, up through body.
167 * @param {Number} pagePathLevel number of paths below root this page is
168 * @param {String} pageTitle
169 * @param {String[]} headElements
172 function htmlHead(pagePathLevel
, pageTitle
, headElements
= []) {
173 const rootPathPfx
= '../'.repeat(pagePathLevel
);
174 return `<!DOCTYPE html>
177 <meta charset="utf-8">` +
178 headElements
.map((e
) => `${' '.repeat(2)}${e}`).join('\n') + `
179 <title>${pageTitle}</title>
180 <link rel="stylesheet" href="${rootPathPfx}static/theme.css">
187 * Closes remainder of HTML page body.
190 function htmlTail() {
197 * Render a navigation link for the header section.
198 * @param {Object} nav
199 * @param {String} nav.href
200 * @param {String} nav.class
201 * @param {String} nav.text
204 function renderNavLink(nav
) {
206 <a href="${nav.href}"${nav.class ? (' class="' + nav.class + '"') : ''}>${nav.text}</a>
212 * Render the navigation header, and open the main section.
213 * @param {String} pageTitle
214 * @param {Object[]} navLinks
217 function htmlHeader(pageTitle
, navLinks
= []) {
219 <h1>${pageTitle}</h1>
223 ${navLinks.map((l) => renderNavLink(l)).join('\n')}
233 * Close the main section and finish off with boilerplate.
234 * @param {String[]} footerEntries
237 function htmlFooter(footerEntries
= []) {
240 (footerEntries
.length
? `
241 <ol>` + footerEntries
.map((f
) => ` <li>${f}</li>`).join('\n') + `
249 * Render all parts of an HTML page. Adds user logout nav link automatically.
250 * @param {Object} ctx
251 * @param {Number} pagePathLevel
252 * @param {String} pageTitle
253 * @param {String[]} headElements
254 * @param {Object[]} navLinks
255 * @param {String[]} main
256 * @param {String[]} footerEntries
259 function htmlTemplate(ctx
, pagePathLevel
, pageTitle
, headElements
= [], navLinks
= [], main
= [], footerEntries
= []) {
260 const user
= (ctx
&& ctx
.session
&& ctx
.session
.authenticatedProfile
) || (ctx
&& ctx
.session
&& ctx
.session
.authenticatedIdentifier
);
263 if (pagePathLevel
> 0) {
264 logoutPath
= `${'../'.repeat(pagePathLevel - 1)}`;
266 logoutPath
= 'admin/';
269 text: `Logout (${user})`,
270 href: `${logoutPath}logout`,
274 htmlHead(pagePathLevel
, pageTitle
, headElements
),
275 htmlHeader(pageTitle
, navLinks
),
277 htmlFooter(footerEntries
),
291 renderTopicRowHeader
,
293 renderSubscriptionRowHeader
,
294 renderSubscriptionRow
,