retro css
This commit is contained in:
+6
-2
@@ -168,8 +168,12 @@ def create_app():
|
|||||||
if type_:
|
if type_:
|
||||||
query = query.filter(SensorReadout.type == type_)
|
query = query.filter(SensorReadout.type == type_)
|
||||||
rows = query.order_by(SensorReadout.timestamp).all()
|
rows = query.order_by(SensorReadout.timestamp).all()
|
||||||
# Zakresy today i 24h: wszystkie pomiary jako punkty, bez uśredniania.
|
# Zakresy today i 24h domyślnie zwracają wszystkie pomiary bez
|
||||||
if range_ in ("today", "24h"):
|
# uśredniania; checkbox "hourly average" wymusza średnie godzinowe.
|
||||||
|
averaged = range_ not in ("today", "24h") or request.args.get(
|
||||||
|
"avg", "0"
|
||||||
|
).lower() in ("1", "true", "yes")
|
||||||
|
if not averaged:
|
||||||
return jsonify(
|
return jsonify(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -63,6 +63,27 @@ h1 {
|
|||||||
|
|
||||||
h2 { color: var(--mc-bright); letter-spacing: 2px; }
|
h2 { color: var(--mc-bright); letter-spacing: 2px; }
|
||||||
|
|
||||||
|
/* Yggdrasil logo — pixel art, so keep it crisp when scaled down.
|
||||||
|
* On the home page it spans the full width of the blue band. */
|
||||||
|
.logo {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin: 1rem auto;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
border: 3px double var(--mc-bright);
|
||||||
|
box-shadow: 8px 8px 0 rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Small logo pinned to the right side of the black navbar. */
|
||||||
|
nav { display: flex; align-items: center; }
|
||||||
|
.nav-logo {
|
||||||
|
height: 68px;
|
||||||
|
width: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes mcblink { 50% { opacity: 0; } }
|
@keyframes mcblink { 50% { opacity: 0; } }
|
||||||
|
|
||||||
/* --- Menu bar (navigation) --- */
|
/* --- Menu bar (navigation) --- */
|
||||||
@@ -166,6 +187,12 @@ button:disabled { opacity: 0.5; cursor: default; }
|
|||||||
|
|
||||||
#refresh-button { margin-top: 0.6rem; }
|
#refresh-button { margin-top: 0.6rem; }
|
||||||
|
|
||||||
|
/* Sensors page: the "hourly average" checkbox must stay a small inline
|
||||||
|
* box next to its text — the global `input { width: 100% }` would
|
||||||
|
* otherwise stretch it full-width and break the row layout. */
|
||||||
|
#avg-label { display: flex; align-items: center; gap: 0.5em; margin-top: 0.6rem; }
|
||||||
|
#avg-label input { width: auto; margin: 0; }
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
background: var(--mc-red);
|
background: var(--mc-red);
|
||||||
color: var(--mc-bright);
|
color: var(--mc-bright);
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
@@ -13,6 +13,7 @@
|
|||||||
<a href="{{ url_for('home') }}">Home</a>
|
<a href="{{ url_for('home') }}">Home</a>
|
||||||
<a href="{{ url_for('sensors_page') }}">Sensors</a>
|
<a href="{{ url_for('sensors_page') }}">Sensors</a>
|
||||||
<a href="{{ url_for('logout') }}">Log out</a>
|
<a href="{{ url_for('logout') }}">Log out</a>
|
||||||
|
<img class="nav-logo" src="{{ url_for('static', filename='yggdrasil-logo.png') }}" alt="Yggdrasil logo">
|
||||||
</nav>
|
</nav>
|
||||||
<h1>Sensor Readouts — API Documentation</h1>
|
<h1>Sensor Readouts — API Documentation</h1>
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<a href="{{ url_for('sensors_page') }}">Sensors</a>
|
<a href="{{ url_for('sensors_page') }}">Sensors</a>
|
||||||
<a href="{{ url_for('api_docs') }}">API</a>
|
<a href="{{ url_for('api_docs') }}">API</a>
|
||||||
<a href="{{ url_for('logout') }}">Log out</a>
|
<a href="{{ url_for('logout') }}">Log out</a>
|
||||||
|
<img class="nav-logo" src="{{ url_for('static', filename='yggdrasil-logo.png') }}" alt="Yggdrasil logo">
|
||||||
</nav>
|
</nav>
|
||||||
<h1>Yggdrasil</h1>
|
<h1>Yggdrasil</h1>
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
@@ -26,6 +27,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<p class="hint">Tip: press <b>1</b>, <b>2</b> or <b>0</b> to navigate.</p>
|
<p class="hint">Tip: press <b>1</b>, <b>2</b> or <b>0</b> to navigate.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<img class="logo" src="{{ url_for('static', filename='yggdrasil-logo.png') }}" alt="Yggdrasil logo">
|
||||||
<footer class="statusbar">
|
<footer class="statusbar">
|
||||||
<span>{{ user.username }}@yggdrasil:~$</span>
|
<span>{{ user.username }}@yggdrasil:~$</span>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<a href="{{ url_for('home') }}">Home</a>
|
<a href="{{ url_for('home') }}">Home</a>
|
||||||
<a href="{{ url_for('api_docs') }}">API</a>
|
<a href="{{ url_for('api_docs') }}">API</a>
|
||||||
<a href="{{ url_for('logout') }}">Log out</a>
|
<a href="{{ url_for('logout') }}">Log out</a>
|
||||||
|
<img class="nav-logo" src="{{ url_for('static', filename='yggdrasil-logo.png') }}" alt="Yggdrasil logo">
|
||||||
</nav>
|
</nav>
|
||||||
<h1>Sensor Readouts</h1>
|
<h1>Sensor Readouts</h1>
|
||||||
|
|
||||||
@@ -40,6 +41,9 @@
|
|||||||
<option value="month">Month</option>
|
<option value="month">Month</option>
|
||||||
<option value="all" selected>All</option>
|
<option value="all" selected>All</option>
|
||||||
</select>
|
</select>
|
||||||
|
<label id="avg-label" class="hidden">
|
||||||
|
<input type="checkbox" id="avg-checkbox"> hourly average
|
||||||
|
</label>
|
||||||
<button id="refresh-button" type="button">[ Refresh ]</button>
|
<button id="refresh-button" type="button">[ Refresh ]</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -77,6 +81,8 @@
|
|||||||
const readoutsSection = document.getElementById('readouts-section');
|
const readoutsSection = document.getElementById('readouts-section');
|
||||||
const readoutsBody = document.getElementById('readouts-body');
|
const readoutsBody = document.getElementById('readouts-body');
|
||||||
const chartInfo = document.getElementById('chart-info');
|
const chartInfo = document.getElementById('chart-info');
|
||||||
|
const avgCheckbox = document.getElementById('avg-checkbox');
|
||||||
|
const avgLabel = document.getElementById('avg-label');
|
||||||
let chart = null;
|
let chart = null;
|
||||||
|
|
||||||
// Strefa czasowa przeglądarki — backend liczy "today" od północy
|
// Strefa czasowa przeglądarki — backend liczy "today" od północy
|
||||||
@@ -97,20 +103,35 @@
|
|||||||
return d.toLocaleString('pl-PL', { timeZone: userTz });
|
return d.toLocaleString('pl-PL', { timeZone: userTz });
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatChartLabel(iso) {
|
function formatChartLabel(iso, timeOnly) {
|
||||||
const d = new Date(iso + 'Z');
|
const d = new Date(iso + 'Z');
|
||||||
return d.toLocaleString('pl-PL', { timeZone: userTz, hour: '2-digit', minute: '2-digit', day: '2-digit', month: '2-digit', year: 'numeric' });
|
if (timeOnly) {
|
||||||
|
return d.toLocaleString('pl-PL', { timeZone: userTz, hour: '2-digit', minute: '2-digit' });
|
||||||
|
}
|
||||||
|
return d.toLocaleString('pl-PL', { timeZone: userTz, day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
|
||||||
}
|
}
|
||||||
|
|
||||||
function chartInfoText(range) {
|
function chartInfoText(range, averaged) {
|
||||||
|
if (!averaged) {
|
||||||
if (range === 'today') return `Raw measurements since local midnight (${userTz}) — no averaging.`;
|
if (range === 'today') return `Raw measurements since local midnight (${userTz}) — no averaging.`;
|
||||||
if (range === '24h') return 'Raw measurements from the last 24 hours — no averaging.';
|
return 'Raw measurements from the last 24 hours — no averaging.';
|
||||||
const period = range === 'week' ? 'the last 7 days'
|
}
|
||||||
|
const period = range === 'today' ? 'today (since local midnight)'
|
||||||
|
: range === '24h' ? 'the last 24 hours'
|
||||||
|
: range === 'week' ? 'the last 7 days'
|
||||||
: range === 'month' ? 'the last 30 days'
|
: range === 'month' ? 'the last 30 days'
|
||||||
: 'all stored data';
|
: 'all stored data';
|
||||||
return `Hourly averages over ${period} — each point is the mean of all measurements in that local hour (${userTz}).`;
|
return `Hourly averages over ${period} — each point is the mean of all measurements in that local hour (${userTz}).`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isRawRange(range) {
|
||||||
|
return range === 'today' || range === '24h';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAvgVisibility() {
|
||||||
|
avgLabel.classList.toggle('hidden', !isRawRange(rangeSelect.value));
|
||||||
|
}
|
||||||
|
|
||||||
// Ciśnienie jest składowane w bazie w paskalach (tak wysyła firmware),
|
// Ciśnienie jest składowane w bazie w paskalach (tak wysyła firmware),
|
||||||
// a wyświetlane w hektopaskalach z jednym miejscem po przecinku.
|
// a wyświetlane w hektopaskalach z jednym miejscem po przecinku.
|
||||||
function displayValue(type, value) {
|
function displayValue(type, value) {
|
||||||
@@ -176,14 +197,16 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// fetch hourly data for chart
|
// fetch hourly data for chart
|
||||||
const hourly = await fetchJSON(`/api/sensors/${sensorId}/readouts/hourly?type=${encodeURIComponent(type)}&range=${encodeURIComponent(range)}&tz=${encodeURIComponent(userTz)}`);
|
const averaged = !isRawRange(range) || avgCheckbox.checked;
|
||||||
|
const hourly = await fetchJSON(`/api/sensors/${sensorId}/readouts/hourly?type=${encodeURIComponent(type)}&range=${encodeURIComponent(range)}&tz=${encodeURIComponent(userTz)}&avg=${averaged ? '1' : '0'}`);
|
||||||
if (!hourly.length) throw new Error('no data for this range');
|
if (!hourly.length) throw new Error('no data for this range');
|
||||||
|
|
||||||
const labels = hourly.map(r => formatChartLabel(r.timestamp));
|
// Surowe punkty today/24h (~1/min) — mniejsze markery,
|
||||||
|
// żeby wykres był czytelny. W obrębie doby na osi X
|
||||||
|
// wystarczy sama godzina (też po uśrednieniu).
|
||||||
|
const dense = !averaged;
|
||||||
|
const labels = hourly.map(r => formatChartLabel(r.timestamp, isRawRange(range)));
|
||||||
const values = hourly.map(r => displayValue(type, r.value));
|
const values = hourly.map(r => displayValue(type, r.value));
|
||||||
// Przy zakresach today/24h punktów jest dużo (~1/min),
|
|
||||||
// więc mniejsze markery żeby wykres był czytelny.
|
|
||||||
const dense = range === 'today' || range === '24h';
|
|
||||||
|
|
||||||
if (chart) chart.destroy();
|
if (chart) chart.destroy();
|
||||||
const ctx = document.getElementById('chart').getContext('2d');
|
const ctx = document.getElementById('chart').getContext('2d');
|
||||||
@@ -216,7 +239,7 @@
|
|||||||
// The chart was created while the container was hidden,
|
// The chart was created while the container was hidden,
|
||||||
// so force a re-measure now that it is visible.
|
// so force a re-measure now that it is visible.
|
||||||
chart.resize();
|
chart.resize();
|
||||||
chartInfo.textContent = '> ' + chartInfoText(range);
|
chartInfo.textContent = '> ' + chartInfoText(range, averaged);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
chartError.textContent = '! Chart error: ' + err.message;
|
chartError.textContent = '! Chart error: ' + err.message;
|
||||||
chartError.classList.remove('hidden');
|
chartError.classList.remove('hidden');
|
||||||
@@ -265,8 +288,14 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
rangeSelect.addEventListener('change', loadChart);
|
rangeSelect.addEventListener('change', () => {
|
||||||
|
updateAvgVisibility();
|
||||||
|
loadChart();
|
||||||
|
});
|
||||||
|
|
||||||
|
avgCheckbox.addEventListener('change', loadChart);
|
||||||
|
|
||||||
|
updateAvgVisibility();
|
||||||
loadSensors();
|
loadSensors();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user