Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pre-releases for test devices only.

## 0.2.0-beta.17

- **Power schedule: you see when the box comes on again.** The schedule is a short overview first (the days with their times, next switch-on/-off, *aktiv*/*aus*) with *Einrichten*/*Bearbeiten*; the table only opens on that button. The page shows the next switch-on and switch-off with the full date; saving says "the box switches on on Sat., 07.11.2026, 10:15" (or that none is planned); the power-off dialog says when the schedule switches it on again. Times entered while the switch *Zeitplan einschalten* is off get a clear warning (easy to miss before). The switch-on must now be only **one minute** ahead (was three) when you save or switch off – the alarm counts whole minutes and the MCU cuts the power 3 s after the shutdown; before, a test like "on at 10:15, switch off at 10:13" silently set the alarm for the next week. `tests/test-power.sh` updated.
- **Header stays on one line.** The status chips (messages, update, VPN, apps …) use the free room left of *ONLINE*; the ones that do not fit go behind a small **+N** button (amber when a warning is among them), which opens them as a list under the row – the header keeps its height and the clock stays on the right. The *MELDUNG* chip no longer counts the “update available” message while the *UPDATE* chip is shown (it counted the same thing twice; the message stays under System → Meldungen and still goes out by mail/push).
- **Interface:** *Mein ShareOS* has its own tab **APPS** with all apps of the user (star = favourite); the start page keeps only the favourites at the top (*FAV APPS*). System → Allgemein: device name and access (root password) moved down, between *App & Widgets* and *Über* (section *GERÄT & ZUGANG*).
- **Scheduled jobs did not run at all** ([#82](https://github.com/UniqueDroid/ShareOS/issues/82)). BusyBox `crond` reads `<dir>/crontabs`, and buildroot's busybox config sets the dir to `/etc/cron` – while `shareos-cron` writes root's crontab to `/etc/crontabs/root`. So `crond` saw an empty `/etc/cron/crontabs`, and none of the ShareOS jobs ran: the scheduled switch-off (reported: the box switched on by schedule but not off), the hourly wake-up alarm, the message checks every 10 minutes, automatic updates, scheduled SMART tests, cloud jobs, Paperless inboxes, app tasks, the Zabbix collector. Running a job by hand always worked, which hid it. `CONFIG_FEATURE_CROND_DIR="/etc"` now, and `post-build.sh` stops the build if crond would read another directory. On a box before beta.17: `rm -rf /etc/cron/crontabs; ln -s /etc/crontabs /etc/cron/crontabs; /etc/init.d/S50crond restart`.
Expand Down
10 changes: 6 additions & 4 deletions src/shareos-core/usr/sbin/shareos-power
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const ROOT = getenv('SHAREOS_ROOT') ?? '';
const DAYS = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ]; // localtime().wday % 7
const DAEMON = getenv('SHAREOS_POWER_DAEMON') ?? '/usr/sbin/dns320l-daemon';
const TIME_RE = /^([01][0-9]|2[0-3]):([0-5][0-9])$/;
// the alarm counts whole minutes and the MCU cuts the power 3 s after the
// shutdown: a switch-on at least one minute ahead is still reached
const MARGIN = 60;

function out(o) { print(sprintf('%J\n', o)); }
function shq(s) { return "'" + replace('' + s, "'", "'\\''") + "'"; }
Expand Down Expand Up @@ -84,14 +87,13 @@ function status() {
let cf = conf(), t = now(), sup = supported();
let alarm = sup ? capture(DAEMON + ' -x ReadAlarm') : null;
return { supported: sup, enabled: cf.enabled, days: cf.days,
next_on: sup ? fmt(next_time(cf, 'on', t)) : null, next_off: fmt(next_time(cf, 'off', t)), alarm };
next_on: sup ? fmt(next_time(cf, 'on', t + MARGIN)) : null, next_off: fmt(next_time(cf, 'off', t)), alarm };
}

// the next switch-on into the MCU; at least 3 minutes ahead, so it is not
// set for the minute the box goes off in
// the next switch-on into the MCU
function arm() {
if (!supported()) return { ok: true, armed: null };
let cf = conf(), at = next_time(cf, 'on', now() + 180);
let cf = conf(), at = next_time(cf, 'on', now() + MARGIN);
let r;
if (at == null) r = capture(DAEMON + ' -x WriteAlarm disable');
else {
Expand Down
37 changes: 32 additions & 5 deletions src/shareos-core/www/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2855,7 +2855,12 @@ const VIEWS_AFTER = {
e.preventDefault();
const f = e.target, days = {};
for (const [d] of PWR_DAYS) days[d] = { on: f['on_' + d]?.value || '', off: f['off_' + d].value || '' };
try { S.pwr = await rpc('shareos.system', 'power_schedule_set', { enabled: f.enabled.checked, days }); toast('Zeitplan gespeichert.'); renderMain(); }
try {
S.pwr = await rpc('shareos.system', 'power_schedule_set', { enabled: f.enabled.checked, days });
S.pwrEdit = false;
toast(!S.pwr.enabled ? 'Zeitplan gespeichert (ausgeschaltet).' : S.pwr.next_on ? `Zeitplan gespeichert – die Box schaltet sich am ${fmtPwr(S.pwr.next_on)} ein.` : 'Zeitplan gespeichert – kein Einschalten geplant.');
renderMain();
}
catch (err) { toast('Zeitplan: ' + err.message, true); }
});
document.querySelectorAll('#f-fan input[name=preset]').forEach((r) => r.addEventListener('change', () => { $('#fan-custom').hidden = r.value !== 'custom'; }));
Expand Down Expand Up @@ -3507,11 +3512,26 @@ async function webSet(v) {
const bootlogTime = (n) => { const m = n.match(/^(\d{4})(\d\d)(\d\d)-(\d\d)(\d\d)/); return m ? new Date(+m[1], m[2] - 1, +m[3], +m[4], +m[5]).toLocaleString(LOC, { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : n; };
// power schedule (shareos-power): switch-off by cron, switch-on by the MCU's wake-up alarm
const PWR_DAYS = [['mon', 'Montag'], ['tue', 'Dienstag'], ['wed', 'Mittwoch'], ['thu', 'Donnerstag'], ['fri', 'Freitag'], ['sat', 'Samstag'], ['sun', 'Sonntag']];
const fmtPwr = (t) => (t ? new Date(t.replace(' ', 'T')).toLocaleString(LOC, { weekday: 'short', day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }) : '–');
const fmtPwr = (t) => (t ? new Date(t.replace(' ', 'T')).toLocaleString(LOC, { weekday: 'short', day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }) : '–');
// any time entered in the schedule?
const pwrHasTimes = (p) => Object.values(p?.days || {}).some((d) => d.on || d.off);
function powerSchedule() {
const p = S.pwr;
if (!p) return html`<div class="panel"><div class="empty"><div class="spin" style="margin:0"></div><span>Lade …</span></div></div>`;
if (p.error) return html`<div class="panel"><p class="mut">${p.error}</p></div>`;
const head = html`<div class="ph"><span class="pt">AUS- UND EINSCHALTEN NACH ZEITPLAN</span>
${p.enabled ? html`<span class="tag ok ml">aktiv</span>` : html`<span class="tag ml">aus</span>`}</div>`;
// first a short overview; the table only after "Einrichten"/"Bearbeiten"
if (!S.pwrEdit) {
const set = PWR_DAYS.filter(([d]) => p.days?.[d]?.on || p.days?.[d]?.off);
return html`<div class="panel">${head}
${!set.length ? html`<p class="mut" style="margin:0">Keine Zeiten eingerichtet.</p>`
: html`<dl class="kv">${set.map(([d, n]) => html`<dt>${n}</dt><dd>${p.days[d].on ? html`an ${p.days[d].on}` : ''}${p.days[d].on && p.days[d].off ? ' · ' : ''}${p.days[d].off ? html`aus ${p.days[d].off}` : ''}</dd>`)}</dl>`}
${p.enabled && set.length ? html`<p style="margin:10px 0 0">${p.supported ? html`Nächstes Einschalten: <b>${fmtPwr(p.next_on)}</b> · ` : ''}Nächstes Ausschalten: <b>${fmtPwr(p.next_off)}</b></p>`
: !p.enabled && set.length ? html`<div class="banner" style="margin-top:10px">${svg(I.warn)}<span>Der Zeitplan ist aus – die eingetragenen Zeiten gelten erst mit dem Haken bei „Zeitplan einschalten“.</span></div>` : ''}
<div class="acts" style="margin-top:12px"><button class="btn" type="button" data-act="pwr-edit">${set.length ? 'Bearbeiten' : 'Einrichten'}</button></div>
</div>`;
}
return html`<form class="panel" id="f-power">
<div class="ph"><span class="pt">AUS- UND EINSCHALTEN NACH ZEITPLAN</span>
${p.enabled ? html`<span class="tag ok ml">aktiv</span>` : html`<span class="tag ml">aus</span>`}</div>
Expand All @@ -3520,8 +3540,9 @@ function powerSchedule() {
<tbody>${PWR_DAYS.map(([d, n]) => html`<tr><td>${n}</td>
${p.supported ? html`<td><input type="time" name="on_${d}" value="${p.days?.[d]?.on || ''}" aria-label="${n} einschalten"></td>` : ''}
<td><input type="time" name="off_${d}" value="${p.days?.[d]?.off || ''}" aria-label="${n} ausschalten"></td></tr>`)}</tbody></table></div>
${p.enabled ? html`<p class="mut" style="margin:10px 0 0">${p.supported ? html`Nächstes Einschalten: <b>${fmtPwr(p.next_on)}</b> · ` : ''}Nächstes Ausschalten: <b>${fmtPwr(p.next_off)}</b></p>` : ''}
<div class="acts" style="margin-top:12px"><button class="btn primary" type="submit">Speichern</button></div>
${p.enabled ? html`<p style="margin:10px 0 0">${p.supported ? html`Nächstes Einschalten: <b>${fmtPwr(p.next_on)}</b> · ` : ''}Nächstes Ausschalten: <b>${fmtPwr(p.next_off)}</b></p>`
: pwrHasTimes(p) ? html`<div class="banner" style="margin-top:10px">${svg(I.warn)}<span>Der Zeitplan ist aus – die eingetragenen Zeiten gelten erst mit dem Haken bei „Zeitplan einschalten“.</span></div>` : ''}
<div class="acts" style="margin-top:12px"><button class="btn primary" type="submit">Speichern</button><button class="btn ghost" type="button" data-act="pwr-cancel">Abbrechen</button></div>
<p class="note">${p.supported ? 'Leere Felder: an diesem Tag nichts. Ausgeschaltet wird sauber (wie mit dem Knopf); läuft gerade ein Speicher-Auftrag (z. B. RAID-Aufbau), bleibt die Box an. Zum Einschalten stellt ShareOS die Weckzeit der Gerätesteuerung – den Stecker nicht ziehen.'
: 'Diese Box kann sich nicht selbst einschalten (keine Weckfunktion) – nur das Ausschalten nach Zeitplan geht. Einschalten über den Power-Taster.'}</p>
</form>`;
Expand Down Expand Up @@ -4007,8 +4028,14 @@ const ACTIONS = {
waitForBox('Neustart läuft', 'ShareOS startet neu');
},

'pwr-edit'() { S.pwrEdit = true; renderMain(); },
'pwr-cancel'() { S.pwrEdit = false; renderMain(); },
async poweroff() {
if (!await confirmDlg('Ausschalten?', 'ShareOS fährt herunter und die Box schaltet sich aus. Einschalten danach nur am Gerät.', 'Ausschalten', true)) return;
// with a power schedule: when the box will come on again
let p = null;
try { p = await rpc('shareos.system', 'power_schedule'); } catch (e) { p = null; }
const back = p?.supported && p.enabled && p.next_on ? `Nach Zeitplan schaltet sie sich am ${fmtPwr(p.next_on)} wieder ein.` : 'Einschalten danach nur am Gerät.';
if (!await confirmDlg('Ausschalten?', `ShareOS fährt herunter und die Box schaltet sich aus. ${back}`, 'Ausschalten', true)) return;
try { await rpc('shareos.system', 'poweroff', { confirm: true }); } catch (e) { toast(e.message, true); return; }
stopPolling();
cover('Box wird ausgeschaltet', 'Du kannst dieses Fenster schließen.');
Expand Down
29 changes: 29 additions & 0 deletions src/shareos-core/www/i18n/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,11 @@
"Schneller mit Windows 11, das jede Verbindung signiert: Die Signatur von SMB 2.1 kostet diese Box etwa halb so viel Rechenzeit wie die von SMB 3 (gemessen etwa 9–14 statt 4–10 MB/s beim Schreiben). Ohne Signatur sind etwa 35 MB/s möglich. Dafür gibt es kein SMB 3: keine Verschlüsselung, kein Multichannel. Bestehende Verbindungen werden dabei getrennt.": "أسرع مع Windows 11 الذي يوقّع كل اتصال: توقيع SMB 2.1 يكلّف هذا الجهاز نحو نصف وقت المعالجة الذي يكلّفه توقيع SMB 3 (قيس نحو 9–14 بدلاً من 4–10 ميغابايت/ث عند الكتابة). بدون توقيع يمكن بلوغ نحو 35 ميغابايت/ث. في المقابل لا يوجد SMB 3: لا تشفير ولا تعدد قنوات. تُقطع الاتصالات المفتوحة عند التغيير.",
"Der Assistent löscht die ausgewählten Platten und richtet sie neu ein. Einzelne Platten lassen sich auch direkt an der Platte unter „Laufwerke“ formatieren. Platten mit Daten (z. B. von Alt-F oder der D-Link-Firmware) bindet „… übernehmen“ an der Platte ohne Löschen ein.": "يمسح المعالج الأقراص المختارة ويعيد إعدادها. يمكن أيضاً تهيئة قرص منفرد مباشرة تحت «محركات الأقراص». الأقراص التي تحوي بيانات (مثلاً من Alt-F أو برنامج D-Link الثابت) تُضاف دون مسح عبر «… استلام» عند القرص.",
"Nur für Geräte, die mit SMB 3 nicht zurechtkommen. Die Signatur, die Windows 11 bei jeder Verbindung verlangt, rechnet bei SMB 3 die Krypto-Einheit der Box (gemessen etwa 19–23 MB/s beim Schreiben, ohne Signatur etwa 35); SMB 2.1 rechnet sie auf dem Prozessor (etwa 9–14 MB/s) und hat keine Verschlüsselung und kein Multichannel. Bestehende Verbindungen werden beim Umschalten getrennt.": "فقط للأجهزة التي تواجه مشاكل مع SMB 3. التوقيع الذي يطلبه Windows 11 في كل اتصال يحسبه في SMB 3 محرك التشفير في الجهاز (قيس نحو 19–23 ميغابايت/ث عند الكتابة، ونحو 35 بدون توقيع)؛ أما SMB 2.1 فيحسبه على المعالج (نحو 9–14 ميغابايت/ث) ولا يدعم التشفير ولا تعدد القنوات. تُقطع الاتصالات المفتوحة عند التبديل.",
"Der Zeitplan ist aus – die eingetragenen Zeiten gelten erst mit dem Haken bei „Zeitplan einschalten“.": "الجدول متوقف – لا تسري الأوقات المدخلة إلا بتحديد «تفعيل الجدول».",
"Zeitplan gespeichert (ausgeschaltet).": "تم حفظ الجدول (متوقف).",
"Zeitplan gespeichert – kein Einschalten geplant.": "تم حفظ الجدول – لا يوجد تشغيل مخطط.",
"Einschalten danach nur am Gerät.": "التشغيل بعد ذلك من الجهاز فقط.",
"Keine Zeiten eingerichtet.": "لم تُضبط أوقات.",
"GERÄT & ZUGANG": "الجهاز والوصول",
"Mit dem Stern wird eine App zum Favoriten – Favoriten stehen oben auf „Mein ShareOS“.": "النجمة تجعل التطبيق مفضلاً – تظهر المفضلة أعلى «ShareOS الخاص بي».",
"Alle Hinweise anzeigen": "عرض كل التنبيهات"
Expand Down Expand Up @@ -2543,6 +2548,30 @@
[
"^(\\S+) ist jetzt das Laufwerk „(.+)“ – die Daten sind erhalten\\. Freigaben für vorhandene Ordner unter „Freigaben“ anlegen\\.$",
"أصبح $1 الآن المحرك «$2» – البيانات محفوظة. أنشئ مشاركات للمجلدات الموجودة تحت «المشاركات»."
],
[
"^Zeitplan gespeichert – die Box schaltet sich am (.+) ein\\.$",
"تم حفظ الجدول – يعمل الجهاز في $1."
],
[
"^ShareOS fährt herunter und die Box schaltet sich aus\\. Nach Zeitplan schaltet sie sich am (.+) wieder ein\\.$",
"يتوقف ShareOS وينطفئ الجهاز. وفق الجدول يعمل مجدداً في $1."
],
[
"^Nach Zeitplan schaltet sie sich am (.+) wieder ein\\.$",
"وفق الجدول يعمل مجدداً في $1."
],
[
"^ShareOS fährt herunter und die Box schaltet sich aus\\. (.+)$",
"يتوقف ShareOS وينطفئ الجهاز. $1"
],
[
"^an ([0-9:]+)$",
"تشغيل $1"
],
[
"^aus ([0-9:]+)$",
"إيقاف $1"
]
],
"words": [
Expand Down
Loading
Loading