Rename to Remora
This commit is contained in:
1 parent
ad165ef6a5
commit
69933f37e5
7 files changed
+17
-17
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
# Sandbar mobile
|
# Remora
|
||||||
|
|
||||||
An installable companion for [Sandbar](https://git.veillax.com/Veillax/sandbar): push notifications, on-call actions
|
An installable companion for [Sandbar](https://git.veillax.com/Veillax/sandbar): push notifications, on-call actions
|
||||||
(acknowledge, snooze, escalate, resolve), topics, monitors and Hammerhead tunnel status.
|
(acknowledge, snooze, escalate, resolve), topics, monitors and Hammerhead tunnel status.
|
||||||
@@ -17,7 +17,7 @@ Start Sandbar and let it serve this folder:
|
|||||||
```
|
```
|
||||||
cd ../sandbar
|
cd ../sandbar
|
||||||
SANDBAR_ADMIN_EMAIL=dev@example.com SANDBAR_ADMIN_PASSWORD=choose-a-password \
|
SANDBAR_ADMIN_EMAIL=dev@example.com SANDBAR_ADMIN_PASSWORD=choose-a-password \
|
||||||
SANDBAR_APP_DIR=../sandbar-mobile/public SANDBAR_PUSH_DEV=1 \
|
SANDBAR_APP_DIR=../remora/public SANDBAR_PUSH_DEV=1 \
|
||||||
go run ./cmd/sandbar -listen 127.0.0.1:8090 -url http://127.0.0.1:8090
|
go run ./cmd/sandbar -listen 127.0.0.1:8090 -url http://127.0.0.1:8090
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "sandbar-mobile",
|
"name": "remora",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Installable companion app for Sandbar: push notifications, on-call actions, monitors and tunnel status.",
|
"description": "Remora: installable companion app for Sandbar: push notifications, on-call actions, monitors and tunnel status.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+5
-5
@@ -76,7 +76,7 @@ function onWorkerMessage(m) {
|
|||||||
if (!m) return;
|
if (!m) return;
|
||||||
if (m.type === 'push') {
|
if (m.type === 'push') {
|
||||||
const p = m.payload || {};
|
const p = m.payload || {};
|
||||||
if (document.visibilityState === 'visible') toast((p.title || 'Sandbar') + (p.message ? ': ' + p.message.slice(0, 80) : ''));
|
if (document.visibilityState === 'visible') toast((p.title || 'Remora') + (p.message ? ': ' + p.message.slice(0, 80) : ''));
|
||||||
load();
|
load();
|
||||||
} else if (m.type === 'open') {
|
} else if (m.type === 'open') {
|
||||||
location.hash = m.alert ? 'alerts' : 'topics';
|
location.hash = m.alert ? 'alerts' : 'topics';
|
||||||
@@ -172,7 +172,7 @@ async function signOut(expired) {
|
|||||||
|
|
||||||
// Turns push on for this device: asks permission, subscribes, and tells the server which topics to send.
|
// Turns push on for this device: asks permission, subscribes, and tells the server which topics to send.
|
||||||
async function enablePush() {
|
async function enablePush() {
|
||||||
if (!('serviceWorker' in navigator) || !('PushManager' in window)) throw new Error('This browser cannot receive push notifications. On iPhone, add Sandbar to the Home Screen first.');
|
if (!('serviceWorker' in navigator) || !('PushManager' in window)) throw new Error('This browser cannot receive push notifications. On iPhone, add Remora to the Home Screen first.');
|
||||||
const perm = await Notification.requestPermission();
|
const perm = await Notification.requestPermission();
|
||||||
if (perm !== 'granted') throw new Error('Notifications are blocked. Allow them for this site in your browser settings, then try again.');
|
if (perm !== 'granted') throw new Error('Notifications are blocked. Allow them for this site in your browser settings, then try again.');
|
||||||
const reg = await navigator.serviceWorker.ready;
|
const reg = await navigator.serviceWorker.ready;
|
||||||
@@ -209,7 +209,7 @@ function loginView() {
|
|||||||
const pw = h('input', { id: 'pw', type: 'password', autocomplete: 'current-password', required: true });
|
const pw = h('input', { id: 'pw', type: 'password', autocomplete: 'current-password', required: true });
|
||||||
const btn = h('button', { class: 'primary', type: 'submit' }, 'Sign in');
|
const btn = h('button', { class: 'primary', type: 'submit' }, 'Sign in');
|
||||||
const form = h('form', { class: 'login' },
|
const form = h('form', { class: 'login' },
|
||||||
h('h1', {}, 'Sandbar'),
|
h('h1', {}, 'Remora'),
|
||||||
h('p', { class: 'hint' }, 'Alerts, on-call and monitors from your own server.'),
|
h('p', { class: 'hint' }, 'Alerts, on-call and monitors from your own server.'),
|
||||||
err,
|
err,
|
||||||
h('label', { for: 'server' }, 'Server address'), server,
|
h('label', { for: 'server' }, 'Server address'), server,
|
||||||
@@ -349,7 +349,7 @@ function statusView() {
|
|||||||
function settingsView() {
|
function settingsView() {
|
||||||
const box = h('div', { id: 'pushbox' }, h('p', { class: 'hint' }, 'Checking'));
|
const box = h('div', { id: 'pushbox' }, h('p', { class: 'hint' }, 'Checking'));
|
||||||
pushState().then(async (st) => {
|
pushState().then(async (st) => {
|
||||||
const msg = { unsupported: 'This browser cannot receive push notifications. On iPhone and iPad, add Sandbar to the Home Screen (Share, then Add to Home Screen) and open it from there.',
|
const msg = { unsupported: 'This browser cannot receive push notifications. On iPhone and iPad, add Remora to the Home Screen (Share, then Add to Home Screen) and open it from there.',
|
||||||
blocked: 'Notifications are blocked for this site. Allow them in your browser settings, then come back.',
|
blocked: 'Notifications are blocked for this site. Allow them in your browser settings, then come back.',
|
||||||
on: 'Push notifications are on for this device.', off: 'Push notifications are off for this device.' }[st];
|
on: 'Push notifications are on for this device.', off: 'Push notifications are off for this device.' }[st];
|
||||||
box.replaceChildren(...[h('p', {}, msg),
|
box.replaceChildren(...[h('p', {}, msg),
|
||||||
@@ -377,7 +377,7 @@ function render() {
|
|||||||
const focus = document.activeElement && document.activeElement.id;
|
const focus = document.activeElement && document.activeElement.id;
|
||||||
const keep = focus && ['newtopic', 'pub'].includes(focus) ? document.activeElement.value : null;
|
const keep = focus && ['newtopic', 'pub'].includes(focus) ? document.activeElement.value : null;
|
||||||
app.replaceChildren(
|
app.replaceChildren(
|
||||||
h('header', { class: 'top' }, h('h1', {}, 'Sandbar'), h('span', { class: 'sub' }, session.server.replace(/^https?:\/\//, ''))),
|
h('header', { class: 'top' }, h('h1', {}, 'Remora'), h('span', { class: 'sub' }, session.server.replace(/^https?:\/\//, ''))),
|
||||||
h('main', {}, data.error ? h('div', { class: 'error', role: 'alert' }, data.error) : null, view()),
|
h('main', {}, data.error ? h('div', { class: 'error', role: 'alert' }, data.error) : null, view()),
|
||||||
h('nav', { class: 'tabs', 'aria-label': 'Sections' }, TABS.map(([id, label, d]) =>
|
h('nav', { class: 'tabs', 'aria-label': 'Sections' }, TABS.map(([id, label, d]) =>
|
||||||
h('a', { href: '#' + id, 'aria-current': tab === id ? 'page' : null }, svg(d), h('span', {}, label, id === 'alerts' && firing ? h('span', { class: 'badge-count' }, String(firing)) : null)))));
|
h('a', { href: '#' + id, 'aria-current': tab === id ? 'page' : null }, svg(d), h('span', {}, label, id === 'alerts' && firing ? h('span', { class: 'badge-count' }, String(firing)) : null)))));
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
<meta name="theme-color" content="#1f5c5c">
|
<meta name="theme-color" content="#1f5c5c">
|
||||||
<meta name="color-scheme" content="light dark">
|
<meta name="color-scheme" content="light dark">
|
||||||
<title>Sandbar</title>
|
<title>Remora</title>
|
||||||
<link rel="manifest" href="manifest.webmanifest">
|
<link rel="manifest" href="manifest.webmanifest">
|
||||||
<link rel="icon" href="icon.svg" type="image/svg+xml">
|
<link rel="icon" href="icon.svg" type="image/svg+xml">
|
||||||
<link rel="apple-touch-icon" href="icon-192.png">
|
<link rel="apple-touch-icon" href="icon-192.png">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Sandbar",
|
"name": "Remora",
|
||||||
"short_name": "Sandbar",
|
"short_name": "Remora",
|
||||||
"description": "Alerts, on-call and monitors from your Sandbar server.",
|
"description": "Alerts, on-call and monitors from your Sandbar server.",
|
||||||
"start_url": "./",
|
"start_url": "./",
|
||||||
"scope": "./",
|
"scope": "./",
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
// localStorage, so the sign-in lives in IndexedDB; the page keeps a copy in localStorage
|
// localStorage, so the sign-in lives in IndexedDB; the page keeps a copy in localStorage
|
||||||
// only for quick startup and falls back to IndexedDB.
|
// only for quick startup and falls back to IndexedDB.
|
||||||
|
|
||||||
const DB = 'sandbar-mobile';
|
const DB = 'remora';
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
+4
-4
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
function kvGet(key) {
|
function kvGet(key) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const r = indexedDB.open('sandbar-mobile', 1);
|
const r = indexedDB.open('remora', 1);
|
||||||
r.onupgradeneeded = () => r.result.createObjectStore('kv');
|
r.onupgradeneeded = () => r.result.createObjectStore('kv');
|
||||||
r.onerror = () => reject(r.error);
|
r.onerror = () => reject(r.error);
|
||||||
r.onsuccess = () => {
|
r.onsuccess = () => {
|
||||||
@@ -60,7 +60,7 @@ self.addEventListener('fetch', (e) => {
|
|||||||
|
|
||||||
self.addEventListener('push', (e) => {
|
self.addEventListener('push', (e) => {
|
||||||
let p;
|
let p;
|
||||||
try { p = e.data.json(); } catch { p = { title: 'Sandbar', message: e.data ? e.data.text() : '' }; }
|
try { p = e.data.json(); } catch { p = { title: 'Remora', message: e.data ? e.data.text() : '' }; }
|
||||||
e.waitUntil(show(p));
|
e.waitUntil(show(p));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ async function show(p) {
|
|||||||
timestamp: (p.time || Date.now() / 1000) * 1000,
|
timestamp: (p.time || Date.now() / 1000) * 1000,
|
||||||
actions: firing ? [{ action: 'ack', title: 'Acknowledge' }, { action: 'snooze', title: 'Snooze 30 min' }] : [],
|
actions: firing ? [{ action: 'ack', title: 'Acknowledge' }, { action: 'snooze', title: 'Snooze 30 min' }] : [],
|
||||||
};
|
};
|
||||||
await self.registration.showNotification(p.title || 'Sandbar', options);
|
await self.registration.showNotification(p.title || 'Remora', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.addEventListener('notificationclick', (e) => {
|
self.addEventListener('notificationclick', (e) => {
|
||||||
@@ -103,7 +103,7 @@ self.addEventListener('notificationclick', (e) => {
|
|||||||
return;
|
return;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
await self.registration.showNotification('Could not ' + (e.action === 'ack' ? 'acknowledge' : 'snooze') + ' the alert', {
|
await self.registration.showNotification('Could not ' + (e.action === 'ack' ? 'acknowledge' : 'snooze') + ' the alert', {
|
||||||
body: err.message + ' Open Sandbar to try again.', tag: 'err-' + alert, data: { alert },
|
body: err.message + ' Open Remora to try again.', tag: 'err-' + alert, data: { alert },
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user