feat: implement Windows service for parental control application
This commit is contained in:
+27
-10
@@ -5,6 +5,11 @@ async function api(path, body) {
|
||||
? { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }
|
||||
: {};
|
||||
const res = await fetch(path, init);
|
||||
// сессия истекла — иначе открытая вкладка молча перестала бы обновляться
|
||||
if (res.status === 401) {
|
||||
location.href = '/login';
|
||||
throw new Error('нужен вход');
|
||||
}
|
||||
if (!res.ok) throw new Error(`${path}: ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
@@ -89,25 +94,32 @@ const UpdateBar = {
|
||||
props: { info: { type: Object, required: true } },
|
||||
emits: ['check'],
|
||||
setup(props) {
|
||||
const busy = computed(() => ['checking', 'downloading', 'installing'].includes(props.info.phase));
|
||||
const status = computed(() => {
|
||||
const i = props.info;
|
||||
const phases = { checking: 'проверка…', downloading: 'загрузка…', installing: 'установка…' };
|
||||
if (phases[i.phase]) return phases[i.phase];
|
||||
if (i.error) return 'не удалось проверить обновления';
|
||||
if (i.available && i.latest && i.latest !== i.current) return 'доступна версия ' + i.latest;
|
||||
if (i.phase === 'checking') return 'проверка…';
|
||||
if (i.phase === 'installing') return `устанавливается ${i.latest}, панель перезапустится`;
|
||||
if (i.error) return i.error;
|
||||
if (i.available) return `доступна версия ${i.latest}`;
|
||||
return 'установлена последняя версия';
|
||||
});
|
||||
return { busy, status };
|
||||
return {
|
||||
status,
|
||||
checking: computed(() => props.info.phase === 'checking'),
|
||||
bad: computed(() => Boolean(props.info.error)),
|
||||
};
|
||||
},
|
||||
template: `
|
||||
<section class="card">
|
||||
<div class="row" style="justify-content:space-between">
|
||||
<div>
|
||||
<h2 style="margin:0">Версия {{ info.current || '—' }}</h2>
|
||||
<span :class="info.error ? 'warn' : 'muted'">{{ status }}</span>
|
||||
<span :class="bad ? 'warn' : 'muted'">{{ status }}</span>
|
||||
</div>
|
||||
<button :disabled="busy" @click="$emit('check')">Проверить обновления</button>
|
||||
<button :disabled="checking" @click="$emit('check')">Проверить обновления</button>
|
||||
</div>
|
||||
<div v-if="info.installer" class="muted" style="margin-top:8px">
|
||||
Установщик, {{ info.installer.time }}:
|
||||
<span :class="{ warn: !info.installer.ok }">{{ info.installer.message }}</span>
|
||||
</div>
|
||||
</section>`,
|
||||
};
|
||||
@@ -243,7 +255,9 @@ const App = {
|
||||
const { info: update, load: loadUpdate, check: checkUpdate } = useUpdate();
|
||||
|
||||
onMounted(loadRules);
|
||||
usePolling(() => { loadProcesses(); loadLog(); loadUpdate(); }, 3000);
|
||||
usePolling(() => { loadProcesses(); loadLog(); }, 3000);
|
||||
// версия меняется раз в сутки в лучшем случае — частить незачем
|
||||
usePolling(loadUpdate, 30000);
|
||||
|
||||
// в whitelist «заблокирован» означает отсутствие в allowed — действия зеркальные
|
||||
const toggle = async (g) => {
|
||||
@@ -265,7 +279,10 @@ const App = {
|
||||
return { rules, groups, lines, update, whitelist, save, remove, toggle, kill, scheduleRandom, checkUpdate };
|
||||
},
|
||||
template: `
|
||||
<h1>Родительский контроль</h1>
|
||||
<div class="row" style="justify-content:space-between">
|
||||
<h1>Родительский контроль</h1>
|
||||
<form method="post" action="/logout"><button>Выйти</button></form>
|
||||
</div>
|
||||
<update-bar :info="update" @check="checkUpdate" />
|
||||
<rules-panel :rules="rules" @update="save" @remove="remove" />
|
||||
<random-kill @schedule="scheduleRandom" />
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
button { background:#2a2f3a; color:#e6e8eb; border:1px solid #3a4150;
|
||||
border-radius:6px; padding:4px 10px; cursor:pointer; font-size:13px; }
|
||||
button:hover { background:#343a47; }
|
||||
button:disabled { opacity:.5; cursor:default; background:#2a2f3a; }
|
||||
button.danger { border-color:#6b2b2b; color:#ff9b9b; }
|
||||
button.danger:hover { background:#3a1f1f; }
|
||||
input, select { background:#14161a; color:#e6e8eb; border:1px solid #3a4150;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Вход — Родительский контроль</title>
|
||||
<style>
|
||||
:root { color-scheme: dark; }
|
||||
* { box-sizing: border-box; }
|
||||
body { margin:0; min-height:100vh; display:grid; place-items:center;
|
||||
background:#14161a; color:#e6e8eb;
|
||||
font:14px/1.5 system-ui, "Segoe UI", sans-serif; }
|
||||
form { background:#1c1f26; border:1px solid #2a2f3a; border-radius:10px;
|
||||
padding:22px; width:min(320px, 90vw); }
|
||||
h1 { font-size:16px; margin:0 0 14px; }
|
||||
label { display:block; color:#9aa4b2; font-size:13px; margin-bottom:6px; }
|
||||
input { width:100%; background:#14161a; color:#e6e8eb; border:1px solid #3a4150;
|
||||
border-radius:6px; padding:8px; font-size:14px; }
|
||||
button { width:100%; margin-top:14px; background:#2a2f3a; color:#e6e8eb;
|
||||
border:1px solid #3a4150; border-radius:6px; padding:8px;
|
||||
cursor:pointer; font-size:14px; }
|
||||
button:hover { background:#343a47; }
|
||||
p.msg { margin:12px 0 0; color:#e0b872; font-size:13px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<form method="post" action="/login">
|
||||
<h1>Родительский контроль</h1>
|
||||
<label for="password">Пароль</label>
|
||||
<input id="password" name="password" type="password" autofocus required>
|
||||
<button type="submit">Войти</button>
|
||||
<p class="msg"><!--MESSAGE--></p>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user