get rid of ambiguous unicode chars
All checks were successful
ci/woodpecker/pr/pr Pipeline was successful

This commit is contained in:
Madison Grubb
2026-02-17 11:28:47 -05:00
parent c696f38f4d
commit a4996e7c91
12 changed files with 41 additions and 41 deletions

View File

@@ -36,7 +36,7 @@ Camera and geolocation in the browser require a **secure context** (HTTPS) when
npm run dev
```
3. On your phone, open **https://192.168.1.123:3000** (same IP you passed above). Accept the browser's untrusted certificate warning once (e.g. Advanced → Proceed). Then log in and use Share live; camera and location will work.
3. On your phone, open **https://192.168.1.123:3000** (same IP you passed above). Accept the browser's "untrusted certificate" warning once (e.g. Advanced → Proceed). Then log in and use Share live; camera and location will work.
Without the certs, `npm run dev` still runs over HTTP as before.
@@ -50,7 +50,7 @@ The **Share live** feature uses WebRTC for real-time video streaming from mobile
- **Mediasoup** server (runs automatically in the Nuxt process)
- **mediasoup-client** (browser library, included automatically)
**Streaming from a phone on your LAN:** The server auto-detects your machine's LAN IP (from network interfaces) and uses it for WebRTC. Open **https://<your-LAN-IP>:3000** on both phone and laptop (same IP as for your dev cert). To override (e.g. Docker or multiple NICs), set `MEDIASOUP_ANNOUNCED_IP`. Ensure firewall allows UDP/TCP ports 4000049999 on the server.
**Streaming from a phone on your LAN:** The server auto-detects your machine's LAN IP (from network interfaces) and uses it for WebRTC. Open **https://<your-LAN-IP>:3000** on both phone and laptop (same IP as for your dev cert). To override (e.g. Docker or multiple NICs), set `MEDIASOUP_ANNOUNCED_IP`. Ensure firewall allows UDP/TCP ports 40000-49999 on the server.
See [docs/live-streaming.md](docs/live-streaming.md) for setup and usage.
@@ -60,12 +60,12 @@ KestrelOS can act as a **TAK Server** so ATAK and iTAK devices connect and share
## Scripts
- `npm run dev` development server
- `npm run build` production build
- `npm run test` run tests
- `npm run test:coverage` run tests with coverage (85% threshold)
- `npm run test:e2e` Playwright E2E tests
- `npm run lint` ESLint (zero warnings)
- `npm run dev` - development server
- `npm run build` - production build
- `npm run test` - run tests
- `npm run test:coverage` - run tests with coverage (85% threshold)
- `npm run test:e2e` - Playwright E2E tests
- `npm run lint` - ESLint (zero warnings)
## Documentation
@@ -76,7 +76,7 @@ Full docs are in the **[docs/](docs/README.md)** directory: [installation](docs/
- **Devices**: Manage cameras/devices via the API (`/api/devices`); see [Map and cameras](docs/map-and-cameras.md). Each device needs `name`, `device_type`, `lat`, `lng`, `stream_url`, and `source_type` (`mjpeg` or `hls`).
- **Environment**: No required env vars for basic run. For production, set `HOST=0.0.0.0` and expose ports 3000 (web/API) and 8089 (CoT). Set `COT_TTL_MS=90000`, `COT_REQUIRE_AUTH=true`. For TLS use `.dev-certs/` or set `COT_SSL_CERT` and `COT_SSL_KEY`.
- **Authentication**: The login page always offers password sign-in (local). Optionally set `BOOTSTRAP_EMAIL` and `BOOTSTRAP_PASSWORD` before the first run to create the first admin; otherwise a default admin is created and its credentials are printed in the terminal. To also show an OIDC sign-in button, configure `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, and optionally `OIDC_LABEL`, `OIDC_REDIRECT_URI`. See [docs/auth.md](docs/auth.md) for local login, OIDC config, and sign up.
- **Bootstrap admin** (when using local auth): The server initializes the database and runs bootstrap at startup. On first run (no users in the database), it creates the first admin. If you set `BOOTSTRAP_EMAIL` and `BOOTSTRAP_PASSWORD` before starting, that account is created. If you don't set them, a default admin is created (identifier: `admin`) with a random password and the credentials are printed in the terminalcopy them and sign in at `/login`, then change the password or add users via Members. Use **Members** to change roles (admin, leader, member). Only admins can change roles; admins and leaders can edit POIs.
- **Bootstrap admin** (when using local auth): The server initializes the database and runs bootstrap at startup. On first run (no users in the database), it creates the first admin. If you set `BOOTSTRAP_EMAIL` and `BOOTSTRAP_PASSWORD` before starting, that account is created. If you don't set them, a default admin is created (identifier: `admin`) with a random password and the credentials are printed in the terminal-copy them and sign in at `/login`, then change the password or add users via Members. Use **Members** to change roles (admin, leader, member). Only admins can change roles; admins and leaders can edit POIs.
- **Database**: SQLite file at `data/kestrelos.db` (created automatically). Contains users, sessions, and POIs.
## Docker
@@ -106,9 +106,9 @@ Health: `GET /health` (overview), `GET /health/live` (liveness), `GET /health/re
Merges to `main` trigger a semver release. Use one of these prefixes in your PR title to set the version bump:
- `major:` breaking changes
- `minor:` new features
- `patch:` bug fixes, docs (default if no prefix)
- `major:` - breaking changes
- `minor:` - new features
- `patch:` - bug fixes, docs (default if no prefix)
Example: `minor: Add map layer toggle`

View File

@@ -130,7 +130,7 @@ function getPoiIcon(L, poi) {
})
}
const LIVE_ICON_COLOR = '#22c9c9' /* kestrel-accent JS string for Leaflet SVG */
const LIVE_ICON_COLOR = '#22c9c9' /* kestrel-accent - JS string for Leaflet SVG */
function getLiveSessionIcon(L) {
const html = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="${LIVE_ICON_COLOR}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="2" fill="${LIVE_ICON_COLOR}"/></svg>`
return L.divIcon({
@@ -141,7 +141,7 @@ function getLiveSessionIcon(L) {
})
}
const COT_ICON_COLOR = '#f59e0b' /* amber ATAK/CoT devices */
const COT_ICON_COLOR = '#f59e0b' /* amber - ATAK/CoT devices */
function getCotEntityIcon(L) {
const html = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="${COT_ICON_COLOR}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="8" r="2.5" fill="${COT_ICON_COLOR}"/></svg>`
return L.divIcon({

View File

@@ -47,7 +47,7 @@
Wrong host: server sees <strong>{{ failureReason.wrongHost.serverHostname }}</strong> but you opened this page at <strong>{{ failureReason.wrongHost.clientHostname }}</strong>. Use the same URL or set MEDIASOUP_ANNOUNCED_IP.
</p>
<ul class="normal-case list-inside list-disc text-left text-kestrel-muted">
<li><strong>Firewall:</strong> Open UDP/TCP 4000049999 on the server.</li>
<li><strong>Firewall:</strong> Open UDP/TCP 40000-49999 on the server.</li>
<li><strong>Wrong host:</strong> Server must see the same address you use.</li>
<li><strong>Restrictive NAT / cellular:</strong> TURN may be required.</li>
</ul>
@@ -66,7 +66,7 @@
Wrong host: server sees <strong>{{ failureReason.wrongHost.serverHostname }}</strong> but you opened at <strong>{{ failureReason.wrongHost.clientHostname }}</strong>.
</p>
<ul class="normal-case list-inside list-disc text-left text-kestrel-muted">
<li>Firewall: open ports 4000049999.</li>
<li>Firewall: open ports 40000-49999.</li>
<li>Wrong host: use same URL or set MEDIASOUP_ANNOUNCED_IP.</li>
<li>Restrictive NAT: TURN may be required.</li>
</ul>

View File

@@ -112,7 +112,7 @@
class="border-b border-kestrel-border"
>
<td class="px-4 py-2 text-kestrel-text">
{{ p.label || '' }}
{{ p.label || '-' }}
</td>
<td class="px-4 py-2 text-kestrel-muted">
{{ p.lat }}

View File

@@ -42,7 +42,7 @@
</h3>
<div class="kestrel-card p-4">
<p class="mb-3 text-sm text-kestrel-text">
Scan this QR code with iTAK (or ATAK) to add this KestrelOS server. Youll be prompted for your KestrelOS username and password after scanning.
Scan this QR code with iTAK (or ATAK) to add this KestrelOS server. You'll be prompted for your KestrelOS username and password after scanning.
</p>
<div
v-if="takQrDataUrl"

View File

@@ -39,7 +39,7 @@
Wrong host: server sees <strong>{{ webrtcFailureReason.wrongHost.serverHostname }}</strong> but you opened this page at <strong>{{ webrtcFailureReason.wrongHost.clientHostname }}</strong>. Use the same URL on phone and server, or set MEDIASOUP_ANNOUNCED_IP.
</p>
<ul class="mt-2 list-inside list-disc space-y-0.5 text-kestrel-muted">
<li><strong>Firewall:</strong> Open UDP/TCP ports 4000049999 on the server.</li>
<li><strong>Firewall:</strong> Open UDP/TCP ports 40000-49999 on the server.</li>
<li><strong>Wrong host:</strong> Server must see the same address you use (see above or open /api/live/debug-request-host).</li>
<li><strong>Restrictive NAT / cellular:</strong> A TURN server may be required (future enhancement).</li>
</ul>
@@ -68,7 +68,7 @@
v-if="sharing"
class="absolute bottom-2 left-2 rounded bg-black/70 px-2 py-1 text-xs text-green-400"
>
● Live you appear on the map
● Live - you appear on the map
</div>
</div>
@@ -273,7 +273,7 @@ async function startSharing() {
return
}
// 5. Get location (continuous) also requires HTTPS on mobile Safari
// 5. Get location (continuous) - also requires HTTPS on mobile Safari
if (!navigator.geolocation) {
setError('Geolocation not supported in this browser.')
cleanup()

View File

@@ -4,8 +4,8 @@ Tactical Operations Center (TOC) for OSINT feeds: map view, cameras/devices, liv
## Quick Start
1. [Installation](installation.md) npm, Docker, or Helm
2. [Authentication](auth.md) First login (bootstrap admin or OIDC)
3. [Map and cameras](map-and-cameras.md) Add devices and view streams
4. [ATAK and iTAK](atak-itak.md) Connect TAK clients (port 8089)
5. [Share live](live-streaming.md) Stream from mobile device (HTTPS required)
1. [Installation](installation.md) - npm, Docker, or Helm
2. [Authentication](auth.md) - First login (bootstrap admin or OIDC)
3. [Map and cameras](map-and-cameras.md) - Add devices and view streams
4. [ATAK and iTAK](atak-itak.md) - Connect TAK clients (port 8089)
5. [Share live](live-streaming.md) - Stream from mobile device (HTTPS required)

View File

@@ -21,11 +21,11 @@ KestrelOS acts as a **TAK Server**. ATAK (Android) and iTAK (iOS) connect on **p
## iTAK (iOS)
**Option A QR code (easiest):**
**Option A - QR code (easiest):**
1. KestrelOS **Settings****TAK Server** → Scan QR with iTAK
2. Enter username/password when prompted
**Option B Manual:**
**Option B - Manual:**
1. **Settings****Network** → Add **TAK Server**
2. Set **Host**, **Port** (`8089`), enable SSL if needed
3. Enable **Use Authentication**, enter username/password

View File

@@ -32,8 +32,8 @@ OIDC users don't have a KestrelOS password. To use ATAK/iTAK:
## Roles
- **Admin** Manage users, edit POIs, add/edit devices (API)
- **Leader** Edit POIs, add/edit devices (API)
- **Member** View map/cameras/POIs, use Share live
- **Admin** - Manage users, edit POIs, add/edit devices (API)
- **Leader** - Edit POIs, add/edit devices (API)
- **Member** - View map/cameras/POIs, use Share live
Only admins can change roles (Members page).

View File

@@ -15,7 +15,7 @@ Stream your phone's camera and location to KestrelOS. Appears as a **live sessio
- **HTTPS** (browsers require secure context for camera/geolocation)
- **Camera and location permissions**
- **WebRTC ports:** UDP/TCP `4000049999` open on server
- **WebRTC ports:** UDP/TCP `40000-49999` open on server
## Local Development
@@ -40,5 +40,5 @@ npm run dev
|-------|-----|
| "HTTPS required" | Use `https://` (not `http://`) |
| "Media devices not available" | Ensure HTTPS and browser permissions |
| "WebRTC: failed" / "Wrong host" | Set `MEDIASOUP_ANNOUNCED_IP`, open firewall ports `4000049999` |
| "WebRTC: failed" / "Wrong host" | Set `MEDIASOUP_ANNOUNCED_IP`, open firewall ports `40000-49999` |
| Stream not visible | Check server reachability and firewall |

View File

@@ -4,16 +4,16 @@ KestrelOS shows a **map** with devices, POIs, live sessions (Share live), and AT
## Map Layers
- **Devices** Fixed feeds (IPTV, ALPR, CCTV, NVR, etc.) added via API
- **POIs** Points of interest (admin/leader can edit)
- **Live sessions** Mobile devices streaming via Share live
- **CoT (ATAK/iTAK)** Amber markers for connected TAK devices (position only)
- **Devices** - Fixed feeds (IPTV, ALPR, CCTV, NVR, etc.) added via API
- **POIs** - Points of interest (admin/leader can edit)
- **Live sessions** - Mobile devices streaming via Share live
- **CoT (ATAK/iTAK)** - Amber markers for connected TAK devices (position only)
## Cameras
A **camera** is either:
1. A **device** Fixed feed with stream URL
2. A **live session** Mobile device streaming via Share live
1. A **device** - Fixed feed with stream URL
2. A **live session** - Mobile device streaming via Share live
View via map markers or **Cameras** page (sidebar).

View File

@@ -202,14 +202,14 @@ function startCotServers() {
serverState.tlsServer = createTlsServer(tlsOpts, socket => setupSocket(socket, true))
serverState.tlsServer.on('error', err => console.error('[cot] TLS server error:', err?.message))
serverState.tlsServer.listen(port, '0.0.0.0', () => {
console.log('[cot] CoT server listening on 0.0.0.0:' + port + ' (TLS) use this port in ATAK/iTAK and enable SSL')
console.log('[cot] CoT server listening on 0.0.0.0:' + port + ' (TLS) - use this port in ATAK/iTAK and enable SSL')
})
}
else {
serverState.tcpServer = createTcpServer(socket => setupSocket(socket, false))
serverState.tcpServer.on('error', err => console.error('[cot] TCP server error:', err?.message))
serverState.tcpServer.listen(port, '0.0.0.0', () => {
console.log('[cot] CoT server listening on 0.0.0.0:' + port + ' (plain TCP) use this port in ATAK/iTAK with SSL disabled')
console.log('[cot] CoT server listening on 0.0.0.0:' + port + ' (plain TCP) - use this port in ATAK/iTAK with SSL disabled')
})
}
}