369 lines
13 KiB
HTML
369 lines
13 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="text/javascript">
|
|
const $ = (selectors, fn) => {
|
|
let e = document.querySelector(selectors);
|
|
return fn && e ? fn(e) : e;
|
|
};
|
|
|
|
const $$ = (selectors, fn) => {
|
|
let e = document.querySelectorAll(selectors);
|
|
return fn && e ? fn(e) : e;
|
|
};
|
|
|
|
const $$$ = (selectors, event, listener) => {
|
|
$(selectors).addEventListener(event, listener);
|
|
};
|
|
|
|
let Profiles = new Array();
|
|
let Latest = null;
|
|
let Status = null;
|
|
|
|
function main(profiles, args) {
|
|
$$$('#begin', 'click', async () => {
|
|
switch (Status) {
|
|
case 'READY':
|
|
let name = $('#name').value;
|
|
let options = new Map();
|
|
|
|
for (let element of $$("input[type='checkbox']:not(.local-only)")) {
|
|
options[element.id] = element.checked;
|
|
}
|
|
|
|
for (let element of $$("input[type='date']")) {
|
|
options[element.id] = element.value;
|
|
}
|
|
|
|
for (let element of $$("input[type='number']")) {
|
|
args[element.id] = element.valueAsNumber;
|
|
}
|
|
|
|
options['profile'] = name;
|
|
await Rpc2.invoke('begin', options, args);
|
|
break;
|
|
case 'RUNNING':
|
|
await Rpc2.invoke('pause');
|
|
break;
|
|
case 'STANDBY':
|
|
await Rpc2.invoke('resume');
|
|
break;
|
|
}
|
|
});
|
|
|
|
$$$('#begin', 'click', (e) => {
|
|
$('#begin > span.icon').removeAttribute('hidden');
|
|
$('#begin > span.text').innerText = '';
|
|
$('#begin').classList.remove('pulse');
|
|
$('#begin').disabled = true;
|
|
});
|
|
|
|
$$$('#cancel', 'click', async (e) => {
|
|
$('#cancel').disabled = true;
|
|
await Rpc2.invoke('cancel');
|
|
});
|
|
|
|
$$$('#skip', 'click', async (e) => {
|
|
$('#skip').disabled = true;
|
|
await Rpcs.invoke('skip');
|
|
});
|
|
|
|
$$$('#logs', 'change', (e) => {
|
|
if (e.target.checked) $('#messages').removeAttribute('hidden');
|
|
else $('#messages').setAttribute('hidden', '');
|
|
});
|
|
|
|
$$$('#name', 'change', (e) => {
|
|
let p = Profiles.find(o => o.name === e.target.value);
|
|
for (let k of Object.keys(p)) $(`#${k}`, e => e.value = p[k] ?? '');
|
|
});
|
|
|
|
$$$('#all', 'change', (e) => {
|
|
$('#name').disabled = e.target.checked;
|
|
});
|
|
|
|
for (let item of JSON.parse(profiles)) {
|
|
Profiles.push(item);
|
|
$('#name').add(new Option(item.name, item.name));
|
|
$('#name').dispatchEvent(new Event('change'));
|
|
}
|
|
|
|
for (let item of $$("input[type='number']")) {
|
|
item.value = args[item.id];
|
|
}
|
|
|
|
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'));
|
|
|
|
let account = new String(args['account']);
|
|
let name = account.split('@', 1).pop() ?? 'unknown';
|
|
name = name.charAt(0).toLocaleUpperCase() + name.slice(1);
|
|
document.title += ` (${name})`;
|
|
|
|
setInterval(async () => {
|
|
let history = await Rpc2.invoke('history');
|
|
let logs = Array.from(history);
|
|
|
|
for (let record of logs) {
|
|
if (record.levelno >= 40) Latest = 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');
|
|
$('#statusLabel').innerText = Status.charAt(0).toUpperCase() + Status.slice(1).toLowerCase();
|
|
|
|
switch (Status) {
|
|
case 'IDLE':
|
|
return;
|
|
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');
|
|
let { task, number, index, limit } = progress;
|
|
$('#numberLabel').innerText = number ?? '';
|
|
$('#progressLabel').innerText = limit ? `${task}, ${parseFloat((index / limit * 100).toFixed(2))}% (${index}/${limit})` : task;
|
|
|
|
let uptime = await Rpc2.invoke('uptime');
|
|
$('#uptimeLabel').innerText = Temporal.Duration.from({ seconds: uptime }).round({ largestUnit: 'hours' }).toLocaleString('en', { style: 'digital' });
|
|
|
|
if (index && limit && uptime) {
|
|
let rate = index / uptime;
|
|
let remaining = Math.floor((limit - index) / rate);
|
|
$('#remainingLabel').innerHTML = Temporal.Duration.from({ seconds: remaining }).round({ largestUnit: 'hours' }).toLocaleString('en');
|
|
}
|
|
break;
|
|
case 'STANDBY':
|
|
if (Latest !== null) {
|
|
alert(`(${Latest.levelname}) ${Latest.msg}\n${Latest.exc_text ?? ''}`);
|
|
Latest = 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');
|
|
$('#cancel').disabled = !actions['Cancel'];
|
|
$('#skip').disabled = !actions['Skip'];
|
|
}, 1000);
|
|
}
|
|
</script>
|
|
|
|
<style type="text/css">
|
|
body {
|
|
width: 100%;
|
|
margin: 2em 0 2em 0;
|
|
display: inline-flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
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> |