All checks were successful
ci/woodpecker/push/push Pipeline was successful
## Added - CoT (Cursor on Target) server on port 8089 enabling ATAK/iTAK device connectivity - Support for TAK stream protocol and traditional XML CoT messages - TLS/SSL support with automatic fallback to plain TCP - Username/password authentication for CoT connections - Real-time device position tracking with TTL-based expiration (90s default) - API endpoints: `/api/cot/config`, `/api/cot/server-package`, `/api/cot/truststore`, `/api/me/cot-password` - TAK Server section in Settings with QR code for iTAK setup - ATAK password management in Account page for OIDC users - CoT device markers on map showing real-time positions - Comprehensive documentation in `docs/` directory - Environment variables: `COT_PORT`, `COT_TTL_MS`, `COT_REQUIRE_AUTH`, `COT_SSL_CERT`, `COT_SSL_KEY`, `COT_DEBUG` - Dependencies: `fast-xml-parser`, `jszip`, `qrcode` ## Changed - Authentication system supports CoT password management for OIDC users - Database schema includes `cot_password_hash` field - Test suite refactored to follow functional design principles ## Removed - Consolidated utility modules: `authConfig.js`, `authSkipPaths.js`, `bootstrap.js`, `poiConstants.js`, `session.js` ## Security - XML entity expansion protection in CoT parser - Enhanced input validation and SQL injection prevention - Authentication timeout to prevent hanging connections ## Breaking Changes - Port 8089 must be exposed for CoT server. Update firewall rules and Docker/Kubernetes configurations. ## Migration Notes - OIDC users must set ATAK password via Account settings before connecting - Docker: expose port 8089 (`-p 8089:8089`) - Kubernetes: update Helm values to expose port 8089 Co-authored-by: Madison Grubb <madison@elastiflow.com> Reviewed-on: #6
171 lines
4.4 KiB
Vue
171 lines
4.4 KiB
Vue
<template>
|
|
<div class="p-6">
|
|
<h2 class="kestrel-page-heading mb-2">
|
|
POI placement
|
|
</h2>
|
|
<p
|
|
v-if="!canEditPois"
|
|
class="mb-4 text-sm text-kestrel-muted"
|
|
>
|
|
View-only. Sign in as admin or leader to add or edit POIs.
|
|
</p>
|
|
<template v-else>
|
|
<form
|
|
class="mb-6 flex flex-wrap items-end gap-3 rounded border border-kestrel-border bg-kestrel-surface p-4"
|
|
@submit.prevent="onAdd"
|
|
>
|
|
<div>
|
|
<label
|
|
for="poi-lat"
|
|
class="kestrel-label"
|
|
>Lat</label>
|
|
<input
|
|
id="poi-lat"
|
|
v-model.number="form.lat"
|
|
type="number"
|
|
step="any"
|
|
required
|
|
class="kestrel-input w-28"
|
|
>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="poi-lng"
|
|
class="kestrel-label"
|
|
>Lng</label>
|
|
<input
|
|
id="poi-lng"
|
|
v-model.number="form.lng"
|
|
type="number"
|
|
step="any"
|
|
required
|
|
class="kestrel-input w-28"
|
|
>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="poi-label"
|
|
class="kestrel-label"
|
|
>Label</label>
|
|
<input
|
|
id="poi-label"
|
|
v-model="form.label"
|
|
type="text"
|
|
class="kestrel-input w-40"
|
|
>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="poi-icon"
|
|
class="kestrel-label"
|
|
>Icon</label>
|
|
<select
|
|
id="poi-icon"
|
|
v-model="form.iconType"
|
|
class="kestrel-input w-28"
|
|
>
|
|
<option
|
|
v-for="opt in POI_ICON_TYPES"
|
|
:key="opt"
|
|
:value="opt"
|
|
>
|
|
{{ opt }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
<button
|
|
type="submit"
|
|
class="rounded bg-kestrel-accent px-3 py-1.5 text-sm font-medium text-kestrel-bg hover:opacity-90"
|
|
>
|
|
Add POI
|
|
</button>
|
|
</form>
|
|
</template>
|
|
<div class="overflow-x-auto rounded border border-kestrel-border">
|
|
<table class="w-full text-left text-sm">
|
|
<thead>
|
|
<tr class="border-b border-kestrel-border bg-kestrel-surface-hover">
|
|
<th class="px-4 py-2 font-medium text-kestrel-text">
|
|
Label
|
|
</th>
|
|
<th class="px-4 py-2 font-medium text-kestrel-text">
|
|
Lat
|
|
</th>
|
|
<th class="px-4 py-2 font-medium text-kestrel-text">
|
|
Lng
|
|
</th>
|
|
<th class="px-4 py-2 font-medium text-kestrel-text">
|
|
Icon
|
|
</th>
|
|
<th
|
|
v-if="canEditPois"
|
|
class="px-4 py-2 font-medium text-kestrel-text"
|
|
>
|
|
Actions
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr
|
|
v-for="p in poisList"
|
|
:key="p.id"
|
|
class="border-b border-kestrel-border"
|
|
>
|
|
<td class="px-4 py-2 text-kestrel-text">
|
|
{{ p.label || '-' }}
|
|
</td>
|
|
<td class="px-4 py-2 text-kestrel-muted">
|
|
{{ p.lat }}
|
|
</td>
|
|
<td class="px-4 py-2 text-kestrel-muted">
|
|
{{ p.lng }}
|
|
</td>
|
|
<td class="px-4 py-2 text-kestrel-muted">
|
|
{{ p.icon_type }}
|
|
</td>
|
|
<td
|
|
v-if="canEditPois"
|
|
class="px-4 py-2"
|
|
>
|
|
<button
|
|
type="button"
|
|
class="text-xs text-red-400 hover:underline"
|
|
@click="remove(p.id)"
|
|
>
|
|
Delete
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
const POI_ICON_TYPES = Object.freeze(['pin', 'flag', 'waypoint'])
|
|
|
|
const { data: poisData, refresh } = usePois()
|
|
const { canEditPois } = useUser()
|
|
const poisList = computed(() => poisData.value ?? [])
|
|
|
|
const form = ref({ lat: 37.77, lng: -122.42, label: '', iconType: 'pin' })
|
|
|
|
async function onAdd() {
|
|
const { lat, lng, label, iconType } = form.value
|
|
try {
|
|
await $fetch('/api/pois', { method: 'POST', body: { lat, lng, label, iconType } })
|
|
await refresh()
|
|
}
|
|
catch { /* ignore */ }
|
|
}
|
|
|
|
async function remove(id) {
|
|
try {
|
|
await $fetch(`/api/pois/${id}`, { method: 'DELETE' })
|
|
await refresh()
|
|
}
|
|
catch { /* ignore */ }
|
|
}
|
|
</script>
|