Files
imporders/index.html
2026-05-27 17:48:54 +08:00

359 lines
12 KiB
HTML

<link rel="icon" href="https://s3.dualstack.us-east-2.amazonaws.com/pythondotorg-assets/media/files/python-logo-only.svg" type="image/svg+xml">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/pure-min.css" integrity="sha384-X38yfunGUhNzHpBaEBsWLO+A0HDYOQi8ufWDkZ0k9e0eXz/tH3II7uKZ9msv++Ls" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/purecss@3.0.0/build/grids-responsive-min.css">
<title>Order Import</title>
<form class="pure-form pure-form-stacked pure-u-11-12 pure-u-lg-3-4 pure-u-xl-2-3">
<fieldset>
<legend>Basic Information</legend>
<div class="gaps pure-g">
<div class="inline-flex pure-u-1 pure-u-sm-1-2">
<span class="ellipsis pure-u-1-4">Number</span>
<span class="less ellipsis pure-u-3-4" id="numberLabel"></span>
</div>
<div class="inline-flex pure-u-1 pure-u-sm-1-2">
<span class="ellipsis pure-u-1-4">Progress</span>
<span class="less ellipsis pure-u-3-4" id="progressLabel"></span>
</div>
<div class="inline-flex pure-u-1 pure-u-sm-1-2">
<span class="ellipsis pure-u-1-4">Uptime</span>
<span class="less ellipsis pure-u-3-4" id="uptimeLabel"></span>
</div>
<div class="inline-flex pure-u-1 pure-u-sm-1-2">
<span class="ellipsis pure-u-1-4">Remaining</span>
<span class="less ellipsis pure-u-3-4" id="remainingLabel"></span>
</div>
<div class="inline-flex pure-u-1 pure-u-sm-1-2">
<span class="ellipsis pure-u-1-4">Status</span>
<span class="less ellipsis pure-u-3-4" id="statusLabel"></span>
</div>
</div>
<br>
<div id="actions" class="inline-flex">
<button type="button" class="inline-flex pure-button pure-button-primary" id="begin" disabled>
<span class="text"></span>
<span class="icon spin">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-loader">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M12 6l0 -3" />
<path d="M16.25 7.75l2.15 -2.15" />
<path d="M18 12l3 0" />
<path d="M16.25 16.25l2.15 2.15" />
<path d="M12 18l0 3" />
<path d="M7.75 16.25l-2.15 2.15" />
<path d="M6 12l-3 0" />
<path d="M7.75 7.75l-2.15 -2.15" />
</svg>
</span>
</button>
<button type="button" class="pure-button" id="skip" disabled>
Skip
</button>
<button type="button" class="pure-button" id="cancel" disabled>
Cancel
</button>
</div>
</fieldset>
<br>
<fieldset>
<legend>Profile Relevant</legend>
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-3">
<label for="prefix">Prefix</label>
<input id="prefix" class="pure-u-23-24" type="text" readonly/>
</div>
<div class="pure-u-1 pure-u-md-1-3">
<label for="suffix">Suffix</label>
<input id="suffix" class="pure-u-23-24" type="text" readonly/>
</div>
<div class="pure-u-1 pure-u-md-1-3">
<label for="subdomain">Subdomain</label>
<input id="subdomain" class="pure-u-23-24" type="text" readonly/>
</div>
<div class="pure-u-1 pure-u-md-1-3">
<label for="remise">Remise Code</label>
<input id="remise" class="pure-u-23-24" type="text" readonly/>
</div>
<div class="pure-u-1 pure-u-md-1-3">
<label for="person">Individual Code</label>
<input id="person" class="pure-u-23-24" type="text" readonly/>
</div>
<div class="pure-u-1 pure-u-md-1-3">
<label for="name">Name</label>
<select id="name" class="pure-input-1-2" required>
</select>
</div>
</div>
</fieldset>
<br>
<fieldset>
<legend>Parameters</legend>
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-3">
<label for="timeout">Timeout</label>
<input id="timeout" class="pure-u-23-24" type="number" min="0"/>
</div>
<div class="pure-u-1 pure-u-md-1-3">
<label for="interval">Interval</label>
<input id="interval" class="pure-u-23-24" type="number" min="0"/>
</div>
<div class="pure-u-1 pure-u-md-1-3">
<label for="attempts">Attempts</label>
<input id="attempts" class="pure-u-23-24" type="number" min="1"/>
</div>
<div class="pure-u-1 pure-u-md-1-3">
<label for="datefrom">Date From</label>
<input id="datefrom" class="pure-u-23-24" type="date"/>
</div>
<div class="pure-u-1 pure-u-md-1-3">
<label for="dateto">Date To</label>
<input id="dateto" class="pure-u-23-24" type="date"/>
</div>
</div>
<br>
<label for="avoir" class="pure-checkbox">
<input id="avoir" type="checkbox" checked/> Include Facture d'Avoirs
</label>
<label for="all" class="pure-checkbox">
<input id="all" type="checkbox"/> Once for all profiles
</label>
<label for="draft" class="pure-checkbox">
<input id="draft" type="checkbox"/> Save as drafts
</label>
<label for="logs" class="pure-checkbox local-only">
<input id="logs" type="checkbox"/> Show logs
</label>
<br>
<textarea id="messages" name="messages" rows="30" hidden readonly></textarea>
</fieldset>
</form>
<script type="module">
import { default as $, Rpc2, LogRecord } from '/';
if (performance.getEntriesByType('navigation')[0].type === 'reload') await Rpc2.invoke('exit');
let error = null;
let status = null;
let { profiles, parameters } = await Rpc2.invoke('context');
let date = new Date();
let day = date.getDay() || 7;
$('#datefrom').valueAsNumber = date.setHours(-24 * (day - 1)) - date.getTimezoneOffset() * 60 * 1000;
$('#dateto').valueAsNumber = date.setHours(24 * 7) + date.getTimezoneOffset() * 60 * 1000;
$('#all').dispatchEvent(new Event('change'));
$.set('#begin', 'click', async () => {
switch (status) {
case 'READY':
let name = $('#name').value;
let options = { profile: name };
for (let element of $.all("input[type='checkbox']:not(.local-only)")) {
options[element.id] = element.checked;
}
for (let element of $.all("input[type='date']")) {
options[element.id] = element.value;
}
for (let element of $.all("input[type='number']")) {
parameters[element.id] = element.valueAsNumber;
}
await Rpc2.invoke('begin', options, parameters);
break;
case 'RUNNING':
await Rpc2.invoke('pause');
break;
case 'STANDBY':
await Rpc2.invoke('resume');
break;
}
});
$.set('#begin', 'click', () => {
$('#begin > span.icon').removeAttribute('hidden');
$('#begin > span.text').innerText = '';
$('#begin').classList.remove('pulse');
$('#begin').disabled = true;
});
$.set('#cancel', 'click', async () => {
$('#cancel').disabled = true;
await Rpc2.invoke('cancel');
});
$.set('#skip', 'click', async () => {
$('#skip').disabled = true;
await Rpcs.invoke('skip');
});
$.set('#logs', 'change', (e) => {
if (e.target.checked) $('#messages').removeAttribute('hidden');
else $('#messages').setAttribute('hidden', '');
});
$.set('#name', 'change', (e) => {
let p = profiles.find(o => o.name === e.target.value);
for (let k of Object.keys(p)) $(`#${k}`)?.setAttribute('value', p[k] ?? '');
});
$.set('#all', 'change', (e) => {
$('#name').disabled = e.target.checked;
});
if (parameters['account'] && parameters['password']) {
let account = new String(parameters['account']);
let name = account.split('@', 1).pop();
name = name.charAt(0).toLocaleUpperCase() + name.slice(1);
document.title += ` (${name})`;
}
for (let item of profiles) {
$('#name').add(new Option(item.name, item.name));
$('#name').dispatchEvent(new Event('change'));
}
for (let item of $.all("input[type='number']")) {
item.value = parameters[item.id];
}
while (await new Promise(o => setTimeout(o, 1000, true))) {
let history = await Rpc2.invoke('history').catch(() => []);
let logs = Array.from(history);
for (let record of logs) {
if ($('#messages').childNodes.length >= 500) $('#messages').childNodes.item(0)?.remove();
if (record.levelno >= 40) error = record;
let message = LogRecord.format(record);
let node = document.createTextNode(new String(message).concat('\n'));
$('#messages').appendChild(node);
$('#messages').scrollTop = $('#messages').scrollHeight;
}
status = await Rpc2.invoke('status').catch(() => null);
$('#statusLabel').innerText = status ? status.charAt(0).toUpperCase() + status.slice(1).toLowerCase() : '';
switch (status) {
case 'IDLE':
continue;
case 'READY':
$('#begin > span.text').innerText = 'Begin';
$('#begin').classList.remove('pulse');
$('#progressLabel').innerText = '';
$('#remainingLabel').innerText = '';
break;
case 'RUNNING':
$('#begin > span.text').innerText = 'Pause';
$('#begin').classList.add('pulse');
let progress = await Rpc2.invoke('progress').catch(() => new Object());
let { task, number, index, limit } = progress;
$('#numberLabel').innerText = number ?? '';
$('#progressLabel').innerText = limit ? `${task}, ${parseFloat((index / limit * 100).toFixed(2))}% (${index}/${limit})` : task;
let [t1, t2] = await Rpc2.invoke('uptime').catch(() => []);
$('#uptimeLabel').innerText = Temporal.Duration.from({ seconds: t1 ?? 0 }).round({ largestUnit: 'hours' }).toLocaleString('en', { style: 'digital' });
let remaining = index && limit && t2 ? Math.floor((limit - index) / (index / t2)) : 0;
$('#remainingLabel').innerHTML = remaining ? Temporal.Duration.from({ seconds: remaining }).round({ largestUnit: 'hours' }).toLocaleString('en') : '';
break;
case 'STANDBY':
if (error !== null) {
alert(`(${error.levelname}) ${error.msg}\n${error.exc_text ?? ''}`);
error = null;
}
$('#begin > span.text').innerText = 'Resume';
$('#begin').classList.remove('pulse');
break;
}
$('#begin > span.icon').setAttribute('hidden', '');
$('#begin').disabled = false;
let actions = await Rpc2.invoke('actions').catch(() => new Object());
$('#cancel').disabled = !actions['Cancel'];
$('#skip').disabled = !actions['Skip'];
}
</script>
<style type="text/css">
body {
width: 100%;
margin: 2em 0 2em 0;
display: inline-flex;
justify-content: center;
}
select, option, input[type='checkbox'], input[type='date'] {
cursor: pointer;
}
label {
user-select: none;
width: fit-content;
}
#actions {
max-height: 2.5em;
column-gap: 0.4em;
}
#actions > button {
height: stretch;
}
#messages {
resize: none;
white-space: pre-wrap;
width: 100%;
}
#mesages:focus {
outline: none;
}
.inline-flex {
display: inline-flex;
align-items: center;
}
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.gaps {
row-gap: 0.4em;
}
.less {
color: #666;
max-width: 72%;
}
.icon {
height: 1em;
}
.spin {
animation: spin 1s linear infinite;
}
.pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@keyframes pulse {
50% {
opacity: 0.6;
}
}
</style>