358 lines
11 KiB
HTML
358 lines
11 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>About Printio</title>
|
||
<link rel="icon" type="image/x-icon" href="/static/images/favicon.ico" />
|
||
<link rel="stylesheet" href="/static/css/styles.css" />
|
||
<link rel="stylesheet" href="/static/fontawesome/css/all.min.css" />
|
||
<style>
|
||
:root{
|
||
--brand: #1e40af; /* blue-800 */
|
||
--brand-hover: #15327f;
|
||
--ok: #16a34a; /* green-600 */
|
||
--ok-hover: #118039;
|
||
--border: #d1d5db; /* gray-300 */
|
||
--text: #111827; /* gray-900 */
|
||
--muted: #4b5563; /* gray-600 */
|
||
--bg: #ffffff;
|
||
--bg-alt: #f9fafb; /* gray-50 */
|
||
--focus: #2563eb; /* blue-600 */
|
||
}
|
||
@media (prefers-color-scheme: dark){
|
||
:root{
|
||
--brand: #60a5fa;
|
||
--brand-hover: #3b82f6;
|
||
--ok: #34d399;
|
||
--ok-hover: #10b981;
|
||
--border: #374151;
|
||
--text: #e5e7eb;
|
||
--muted: #9ca3af;
|
||
--bg: #0b0f14;
|
||
--bg-alt: #111827;
|
||
--focus: #60a5fa;
|
||
}
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
html, body { height: 100%; }
|
||
body{
|
||
margin: 0;
|
||
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
|
||
color: var(--text);
|
||
background: var(--bg);
|
||
line-height: 1.55;
|
||
}
|
||
|
||
#navbar { position: sticky; top: 0; z-index: 20; }
|
||
|
||
h1 { margin: 0 0 16px; font-size: clamp(1.5rem, 2vw, 2rem); }
|
||
h3 { margin: 0 0 12px; font-size: 1.125rem; }
|
||
|
||
p { margin: 0 0 12px; }
|
||
|
||
a { color: var(--brand); text-decoration: underline; text-underline-offset: 2px; }
|
||
a:hover { color: var(--brand-hover); }
|
||
|
||
.content-wrapper{
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: 20px;
|
||
max-width: 1100px;
|
||
margin: 0 auto;
|
||
padding: 16px;
|
||
}
|
||
|
||
@media (min-width: 840px){
|
||
.content-wrapper{
|
||
grid-template-columns: 300px 1fr;
|
||
gap: 24px;
|
||
padding: 24px;
|
||
}
|
||
.left-column{
|
||
position: sticky;
|
||
top: 72px; /* keep below navbar */
|
||
align-self: start;
|
||
}
|
||
}
|
||
|
||
.left-column, .right-column { width: 100%; }
|
||
|
||
.card{
|
||
padding: 16px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
background: var(--bg-alt);
|
||
}
|
||
.info-box{ margin-bottom: 16px; }
|
||
|
||
.kv p{
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
border-bottom: 1px dashed var(--border);
|
||
padding: 6px 0;
|
||
margin: 0;
|
||
font-size: 0.95rem;
|
||
}
|
||
.kv p span:first-child{ color: var(--muted); }
|
||
.kv p span:last-child{ font-variant-numeric: tabular-nums; }
|
||
|
||
.license-section{
|
||
margin-top: 20px;
|
||
text-align: left;
|
||
}
|
||
.license-status{ font-size: 1rem; margin: 0 0 10px; }
|
||
|
||
.field{
|
||
display: grid;
|
||
grid-template-columns: 140px 1fr auto;
|
||
gap: 10px;
|
||
align-items: center;
|
||
margin: 10px 0;
|
||
}
|
||
@media (max-width: 520px){
|
||
.field{ grid-template-columns: 1fr; }
|
||
.field label{ margin-bottom: -4px; }
|
||
}
|
||
|
||
input[type="text"], input[type="password"]{
|
||
padding: 10px 12px;
|
||
font-size: 1rem;
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
}
|
||
input[readonly]{
|
||
background: rgba(0,0,0,0.03);
|
||
}
|
||
|
||
button{
|
||
appearance: none;
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 10px 16px;
|
||
font-size: 1rem;
|
||
cursor: pointer;
|
||
color: #fff;
|
||
background: var(--brand);
|
||
}
|
||
button:hover{ background: var(--brand-hover); }
|
||
button:focus-visible{
|
||
outline: 3px solid var(--focus);
|
||
outline-offset: 2px;
|
||
}
|
||
.btn-ok{
|
||
background: var(--ok);
|
||
}
|
||
.btn-ok:hover{
|
||
background: var(--ok-hover);
|
||
}
|
||
|
||
.actions{
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.qr{
|
||
width: 140px;
|
||
max-width: 40vw;
|
||
height: auto;
|
||
border-radius: 8px;
|
||
border: 1px solid var(--border);
|
||
background: #fff;
|
||
}
|
||
|
||
.muted{ color: var(--muted); font-size: 0.95rem; }
|
||
</style>
|
||
<script src="/static/js/crypto-js.min.js" defer></script>
|
||
</head>
|
||
<body>
|
||
<div id="navbar" role="navigation" aria-label="Primary"></div>
|
||
|
||
<script>
|
||
// Load navbar with basic error handling
|
||
fetch('/static/html/nav.html')
|
||
.then(r => r.ok ? r.text() : Promise.reject(r.status))
|
||
.then(html => { document.getElementById('navbar').innerHTML = html; })
|
||
.catch(() => { document.getElementById('navbar').innerHTML = '<div class="card" role="alert">Navigation failed to load.</div>'; });
|
||
</script>
|
||
|
||
<main class="content-wrapper">
|
||
<!-- Sidebar -->
|
||
<aside class="left-column" aria-label="System and License Information">
|
||
<section class="card info-box" aria-labelledby="sysinfo-heading">
|
||
<h3 id="sysinfo-heading">System Info</h3>
|
||
<div class="kv">
|
||
<p><span>Version</span><span>{{info.software_version}}</span></p>
|
||
<p><span>CPU %</span><span>{{info.cpu}}</span></p>
|
||
<p><span>CPU Temp</span><span>{{info.cpu_t}}</span></p>
|
||
<p><span>Disk Size</span><span>{{info.disk_size}}</span></p>
|
||
<p><span>Disk Used</span><span>{{info.disk_used}}</span></p>
|
||
<p><span>RAM Size</span><span>{{info.ram_size}}</span></p>
|
||
<p><span>RAM Used</span><span>{{info.ram_used}}</span></p>
|
||
<p><span>Up Time</span><span>{{info.uptime}}</span></p>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="card" aria-labelledby="licenseinfo-heading">
|
||
<h3 id="licenseinfo-heading">License Info</h3>
|
||
<p class="muted">Status: <strong>{{info.license}}</strong></p>
|
||
<!-- Optional fields kept commented
|
||
<p>ImageMagick: {{info.image_magic}}</p>
|
||
<p>Drop Folder: {{info.drop_folder}}</p>
|
||
-->
|
||
</section>
|
||
</aside>
|
||
|
||
<!-- Main -->
|
||
<section class="right-column">
|
||
<h1>About Printio</h1>
|
||
<p>
|
||
Printio is a professional printing solution designed for seamless printing from iPads. It’s widely used in the photobooth industry and supports a range of dye-sublimation printers. Printio works with well-known brands such as DNP, Sinfonia, HiTi, and Mitsubishi, as well as many other printers, including Zebra label printers and most HP and Epson models—making it a versatile choice for diverse printing needs.
|
||
</p>
|
||
<p class="muted">
|
||
Warning: Unauthorized reproduction or distribution of this product is strictly prohibited and may result in civil and criminal penalties.
|
||
</p>
|
||
|
||
<p>
|
||
<a href="https://www.ataphotobooths.com/knowledge-base/category/printio-setup/" target="_blank" rel="noopener noreferrer">
|
||
Printio Help: Click me!
|
||
</a>
|
||
</p>
|
||
|
||
<figure aria-label="Printio Help QR Code" style="margin: 16px 0;">
|
||
<img class="qr" src="/static/images/printio_help_qr.jpg" alt="Scan for Printio setup help" />
|
||
</figure>
|
||
|
||
<!-- License Section (conditionally hidden) -->
|
||
<section class="license-section card" {{info.hidden}} aria-labelledby="license-activation-heading">
|
||
<h3 id="license-activation-heading">License Activation</h3>
|
||
<p class="license-status muted" id="license-status" aria-live="polite"></p>
|
||
|
||
<div class="field">
|
||
<label for="idcode">ID Code</label>
|
||
<input type="text" id="idcode" value="{{info.idcode}}" readonly aria-readonly="true" />
|
||
<div class="actions">
|
||
<button type="button" id="copy-btn" class="btn-ok" aria-describedby="copy-hint">
|
||
<i class="fa fa-copy" aria-hidden="true"></i> Copy
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<p class="muted" id="copy-hint">Copies your ID Code to the clipboard.</p>
|
||
|
||
<div class="field">
|
||
<label for="license-password">Activation Key</label>
|
||
<input type="password" id="license-password" placeholder="Enter Activation Key" autocomplete="one-time-code" />
|
||
<div class="actions">
|
||
<button type="button" id="activate-btn">
|
||
<i class="fa fa-bolt" aria-hidden="true"></i> Activate
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
</main>
|
||
|
||
<script>
|
||
// Optional: simple (placeholder) hashing before sending; replace with real logic if needed.
|
||
function hashPass(plain) {
|
||
try {
|
||
// Example using CryptoJS if you choose to enable hashing later:
|
||
// return CryptoJS.SHA256(plain).toString();
|
||
return plain; // currently pass-through; replace when backend expects a hash
|
||
} catch {
|
||
return plain;
|
||
}
|
||
}
|
||
|
||
function setStatus(msg, ok = true){
|
||
const el = document.getElementById('license-status');
|
||
if (!el) return;
|
||
el.textContent = msg || '';
|
||
el.style.color = ok ? 'inherit' : '#dc2626'; // red-600 in light; acceptable in dark
|
||
}
|
||
|
||
async function activateLicense() {
|
||
const input = document.getElementById('license-password');
|
||
const pass = (input?.value || '').trim();
|
||
if (!pass){
|
||
setStatus('Please enter your activation key.', false);
|
||
input?.focus();
|
||
return;
|
||
}
|
||
setStatus('Activating…');
|
||
|
||
try {
|
||
const res = await fetch('/activate', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ pass: hashPass(pass) })
|
||
});
|
||
|
||
if (!res.ok) throw new Error('Network error');
|
||
const data = await res.json();
|
||
|
||
if (data.reply === true) {
|
||
alert('Activation successful!');
|
||
window.location.href = '/about';
|
||
} else {
|
||
setStatus('Activation failed. Please verify your key and try again.', false);
|
||
alert('Activation failed. Try again.');
|
||
}
|
||
} catch (err) {
|
||
console.error('Activation error:', err);
|
||
setStatus('Error during activation attempt.', false);
|
||
alert('Error during activation attempt.');
|
||
}
|
||
}
|
||
|
||
async function copyToClipboard() {
|
||
const el = document.getElementById('idcode');
|
||
const btn = document.getElementById('copy-btn');
|
||
if (!el) return alert('No ID Code field found.');
|
||
const text = el.value || el.textContent || '';
|
||
if (!text) return alert('Nothing to copy.');
|
||
|
||
try {
|
||
if (navigator.clipboard?.writeText) {
|
||
await navigator.clipboard.writeText(text);
|
||
} else {
|
||
// Fallback
|
||
const ta = document.createElement('textarea');
|
||
ta.value = text;
|
||
ta.style.position = 'fixed';
|
||
ta.style.opacity = '0';
|
||
document.body.appendChild(ta);
|
||
ta.select();
|
||
document.execCommand('copy');
|
||
document.body.removeChild(ta);
|
||
}
|
||
btn?.classList.add('copied');
|
||
btn?.setAttribute('aria-label', 'Copied!');
|
||
setStatus('ID Code copied to clipboard.');
|
||
setTimeout(() => {
|
||
btn?.removeAttribute('aria-label');
|
||
setStatus('');
|
||
}, 1500);
|
||
} catch (e) {
|
||
console.error('Clipboard error:', e);
|
||
alert('Failed to copy to clipboard.');
|
||
}
|
||
}
|
||
|
||
// Wire up buttons
|
||
document.getElementById('copy-btn')?.addEventListener('click', copyToClipboard);
|
||
document.getElementById('activate-btn')?.addEventListener('click', activateLicense);
|
||
|
||
// Allow Enter key to activate from password field
|
||
document.getElementById('license-password')?.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') activateLicense();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|