Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove Overlays #112

Merged
merged 13 commits into from
Mar 6, 2024
3 changes: 2 additions & 1 deletion api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,10 +211,11 @@ export default async function server(config: Config) {
throw new Error('Unauthorized');
}
} catch (err) {
console.error('Error: WebSocket: ', err);
ws.send(JSON.stringify({
type: 'Error',
properties: {
message: err instanceof Error ? String(err.message) : String(err)
message: err instanceof Error ? String(err.message) : String(err)
}
}));
await sleep(500);
Expand Down
2 changes: 1 addition & 1 deletion api/lib/api/mission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default class {
}

#headers(opts?: Static<typeof MissionOptions>): object {
if (opts.token) {
if (opts && opts.token) {
return {
MissionAuthorization: `Bearer ${opts.token}`
}
Expand Down
2 changes: 1 addition & 1 deletion api/lib/aws/batch-logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default class LogGroup {
})
}
} catch (err) {
if (err instanceof Error && err.message.includes('The specified log stream does not exist')) {
if (String(err).startsWith('ResourceNotFoundException')) {
return { logs: [] }
} else {
throw new Error(err instanceof Error ? err.message : String(err));
Expand Down
4 changes: 1 addition & 3 deletions api/lib/aws/s3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,7 @@ export default class S3 {
}));
return true;
} catch (err) {
//@ts-ignore
if (err.code === 'NotFound') return false;

if (String(err).startsWith('NotFound')) return false;
throw new Err(500, new Error(err instanceof Error ? err.message : String(err)), 'Failed to determine existance');
}
}
Expand Down
4 changes: 4 additions & 0 deletions api/routes/data-asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,10 @@ export default async function router(schema: Schema, config: Config) {

const data = await config.models.Data.from(req.params.dataid);

if (!await S3.exists(`data/${req.params.dataid}/${req.params.asset}.pmtiles`)) {
throw new Err(404, null, 'Asset does not exist');
}

const token = jwt.sign({ access: 'user' }, config.SigningSecret)
const url = new URL(`${config.PMTILES_URL}/tiles/data/${data.id}/${req.params.asset}`);
url.searchParams.append('token', token);
Expand Down
14 changes: 13 additions & 1 deletion api/routes/profile-asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default async function router(schema: Schema, config: Config) {
total: Type.Integer(),
tiles: Type.Object({
url: Type.String()
}),
}),
assets: Type.Array(ProfileAssetResponse)
})
}, async (req, res) => {
Expand Down Expand Up @@ -128,6 +128,14 @@ export default async function router(schema: Schema, config: Config) {

await S3.del(`profile/${user.email}/${req.params.asset}.${req.params.ext}`);

if (await S3.exists(`profile/${user.email}/${req.params.asset}.geojsonld`)) {
await S3.del(`profile/${user.email}/${req.params.asset}.geojsonld`);
}

if (await S3.exists(`profile/${user.email}/${req.params.asset}.pmtiles`)) {
await S3.del(`profile/${user.email}/${req.params.asset}.pmtiles`);
}

return res.json({
status: 200,
message: 'Asset Deleted'
Expand Down Expand Up @@ -174,6 +182,10 @@ export default async function router(schema: Schema, config: Config) {
try {
const user = await Auth.as_user(config, req, { token: true });

if (!await S3.exists(`profile/${user.email}/${req.params.asset}.pmtiles`)) {
throw new Err(404, null, 'Asset does not exist');
}

const token = jwt.sign({ access: 'profile', email: user.email }, config.SigningSecret)
const url = new URL(`${config.PMTILES_URL}/tiles/profile/${user.email}/${req.params.asset}`);
url.searchParams.append('token', token);
Expand Down
31 changes: 29 additions & 2 deletions api/routes/profile-overlays.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Type } from '@sinclair/typebox'
import path from 'node:path';
import Config from '../lib/config.js';
import Schema from '@openaddresses/batch-schema';
import S3 from '../lib/aws/s3.js';
import Err from '@openaddresses/batch-error';
import Auth, { AuthResource } from '../lib/auth.js';
import { StandardResponse, ProfileOverlayResponse } from '../lib/types.js'
Expand All @@ -16,12 +18,18 @@ export default async function router(schema: Schema, config: Config) {
await schema.get('/profile/overlay', {
name: 'Get Overlays',
group: 'ProfileOverlays',
description: 'Get User\'s Profile Overlays',
description: `
Return a list of Profile Overlay's that are curently active.

Each item is checked to ensure it is still present and if not the overlay is removed from the list
before being returned.
`,
query: Type.Object({
limit: Type.Optional(Type.Integer())
}),
res: Type.Object({
total: Type.Integer(),
removed: Type.Array(ProfileOverlayResponse),
items: Type.Array(ProfileOverlayResponse)
})

Expand All @@ -33,7 +41,26 @@ export default async function router(schema: Schema, config: Config) {
limit: req.query.limit
});

return res.json(overlays);
const removed = [];

for (let i = 0; i < overlays.items.length; i++) {
const item = overlays.items[i];

// TODO someday surface these to the user that the underlying resources don't exist
if (
(item.mode === 'profile' && !(await S3.exists(`profile/${item.username}/${path.parse(item.url.replace(/\/tile$/, '')).name}.pmtiles`)))
|| (item.mode === 'data' && !(await S3.exists(`data/${item.mode_id}/${path.parse(item.url.replace(/\/tile$/, '')).name}.pmtiles`)))
) {
await config.models.ProfileOverlay.delete(item.id);
removed.push(...overlays.items.splice(i, 1));
overlays.total--;
}
}

return res.json({
removed,
...overlays
});
} catch (err) {
return Err.respond(err, res);
}
Expand Down
2 changes: 1 addition & 1 deletion api/web/src/components/CloudTAK/CoTView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
<CoTStyle v-model='feat'/>
</template>

<div class='col-12 d-flex align-items-center'>
<div v-if='false' class='col-12 d-flex align-items-center'>
<div class='d-flex'>
<button class='btn bg-gray-500'><IconShare2/></button>
<button class='btn bg-gray-500'><IconPencil/></button>
Expand Down
38 changes: 29 additions & 9 deletions api/web/src/components/CloudTAK/Menu/Datas.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,24 @@

<TablerLoading v-if='loading'/>
<template v-else-if='mode === "user"'>
<div :key='a.id' v-for='a in assetList.assets' class="col-lg-12">
<div class='col-12 py-2 px-2 d-flex align-items-center'>
<IconEyeX v-if='!a.visualized' v-tooltip='"No Viz Layer"'/>
<IconEye v-else-if='a.visible' @click='flipVisible(a)' class='cursor-pointer'/>
<IconEyeOff v-else @click='flipVisible(a)' class='cursor-pointer'/>
<span class="mx-2 cursor-pointer" v-text='a.name'></span>
<TablerNone
v-if='!assetList.assets.length'
label='User Uploads'
@create='$router.push("/profile/files")'
/>
<template v-else>
<div :key='a.id' v-for='a in assetList.assets' class="cursor-pointer col-12 py-2 px-3 hover-dark">
<div class='col-12 py-2 px-2 d-flex align-items-center'>
<IconEyeX v-if='!a.visualized' v-tooltip='"No Viz Layer"'/>
<IconEye v-else-if='a.visible' @click='flipVisible(a)' class='cursor-pointer'/>
<IconEyeOff v-else @click='flipVisible(a)' class='cursor-pointer'/>
<span class="mx-2 cursor-pointer" v-text='a.name'></span>
<div class='ms-auto btn-list'>
<TablerDelete displaytype='icon' @delete='deleteProfileAsset(a)'/>
</div>
</div>
</div>
</div>
</template>
</template>
<template v-else-if='mode === "data"'>
<template v-if='data'>
Expand All @@ -40,7 +50,7 @@
</div>

<div class='modal-body my-2'>
<div :key='a.id' v-for='a in assetList.assets' class='cursor-pointer col-12 py-2 px-3 rounded hover-dark'>
<div :key='a.id' v-for='a in assetList.assets' class='cursor-pointer col-12 py-2 px-3 hover-dark'>
<div class='col-12 py-2 px-2 d-flex align-items-center'>
<IconEyeX v-if='!a.visualized' v-tooltip='"No Viz Layer"'/>
<IconEye v-else-if='a.visible' @click='flipVisible(a)' class='cursor-pointer'/>
Expand All @@ -58,7 +68,7 @@
/>
<template v-else>
<div class='modal-body my-2'>
<div @click='data = d' :key='d.id' v-for='d in list.items' class='cursor-pointer col-12 py-2 px-3 rounded hover-dark'>
<div @click='data = d' :key='d.id' v-for='d in list.items' class='cursor-pointer col-12 py-2 px-3 hover-dark'>
<div class='col-12 py-2 px-2 d-flex align-items-center'>
<IconFolder/><span class="mx-2" v-text='d.name'></span>
</div>
Expand All @@ -80,6 +90,7 @@ const mapStore = useMapStore();
import {
TablerNone,
TablerPager,
TablerDelete,
TablerLoading
} from '@tak-ps/vue-tabler';
import {
Expand Down Expand Up @@ -177,6 +188,14 @@ export default {
}
}
},
deleteProfileAsset: async function(a) {
this.loading = true;
const url = window.stdurl(`/api/profile/asset/${a.name}`);
await window.std(url, {
method: 'DELETE'
});
this.fetchUserAssetList();
},
createOverlay: async function(id, url, a) {
this.loading = true;
const res = await window.std(url);
Expand Down Expand Up @@ -274,6 +293,7 @@ export default {
IconSettings,
IconSearch,
TablerLoading,
TablerDelete,
IconCircleArrowLeft
}
}
Expand Down
2 changes: 1 addition & 1 deletion api/web/src/components/CloudTAK/Menu/Overlays.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</div>
<TablerLoading v-if='loading'/>
<template v-else>
<div :key='layer.url' v-for='layer in layers' class="col-lg py-2 px2 hover-dark">
<div :key='layer.url' v-for='layer in layers' class="col-lg py-2 px-3 hover-dark">
<div class='py-2 px-2 hover-dark'>
<div class='col-12 d-flex align-items-center'>
<IconEye v-if='layer.visible === "visible"' @click='flipVisible(layer)' class='cursor-pointer' v-tooltip='"Hide Layer"'/>
Expand Down
17 changes: 17 additions & 0 deletions api/web/src/components/CloudTAK/Menu/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@
</template>
</div>
</template>
<template v-if='mode === "display"'>
<div class='col-12 border-bottom border-light'>
<div class='modal-header px-0 mx-2'>
<IconCircleArrowLeft @click='mode = "settings"' class='cursor-pointer'/>
<div class='modal-title'>Display Preferences</div>
<div/>
</div>
</div>
<div class='col-12 px-2 py-2'>
</div>
</template>
<template v-else-if='mode === "settings"'>
<div class='col-12 border-bottom border-light'>
<div class='modal-header px-0 mx-2'>
Expand All @@ -41,6 +52,10 @@
<IconUserCog size='32'/>
<span class='mx-2' style='font-size: 18px;'>Callsign &amp; Device Preferences</span>
</div>
<div @click='mode = "display"' class='cursor-pointer col-12 py-2 px-3 d-flex align-items-center hover-dark'>
<IconAdjustments size='32'/>
<span class='mx-2' style='font-size: 18px;'>Display Preferences</span>
</div>
</div>
</template>
</div>
Expand All @@ -49,6 +64,7 @@
<script>
import {
IconUserCog,
IconAdjustments,
IconCircleArrowLeft
} from '@tabler/icons-vue';
import {
Expand Down Expand Up @@ -95,6 +111,7 @@ export default {
},
components: {
IconUserCog,
IconAdjustments,
TablerInput,
TablerEnum,
TablerLoading,
Expand Down
2 changes: 1 addition & 1 deletion api/web/src/components/CloudTAK/Mission/MissionList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class='modal-header px-0 mx-2'>
<IconCircleArrowLeft @click='$emit("close")' class='cursor-pointer'/>
<div class='modal-title'>Missions</div>
<div class='btn-list' v-if='!loading'>
<div class='btn-list'>
<IconPlus @click='$emit("create")' class='cursor-pointer' v-tooltip='"Create Mission"'/>
<IconRefresh @click='fetchMissions' class='cursor-pointer' v-tooltip='"Refresh"'/>
</div>
Expand Down
29 changes: 12 additions & 17 deletions api/web/src/stores/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,26 +66,21 @@ export const useMapStore = defineStore('cloudtak', {
this.map.on('click', click.id, (e) => {
if (this.draw && this.draw.getMode() !== 'static') return;

if (e.point.x < 150 || e.point.y < 150) {
const flyTo = { speed: Infinity };
if (e.features[0].geometry.type === 'Point') {
flyTo.center = e.features[0].geometry.coordinates;
} else {
flyTo.center = pointOnFeature(e.features[0].geometry).geometry.coordinates;
}

// This is required to ensure the map has nowhere to flyTo - ie the whole world is shown
// and then the radial menu won't actually be on the CoT when the CoT is clicked
if (this.map.getZoom() < 3) flyTo.zoom = 4;
this.map.flyTo(flyTo)

this.radial.x = this.container.clientWidth / 2;
this.radial.y = this.container.clientHeight / 2;
const flyTo = { speed: Infinity };
if (e.features[0].geometry.type === 'Point') {
flyTo.center = e.features[0].geometry.coordinates;
} else {
this.radial.x = e.point.x;
this.radial.y = e.point.y;
flyTo.center = pointOnFeature(e.features[0].geometry).geometry.coordinates;
}

// This is required to ensure the map has nowhere to flyTo - ie the whole world is shown
// and then the radial menu won't actually be on the CoT when the CoT is clicked
if (this.map.getZoom() < 3) flyTo.zoom = 4;
this.map.flyTo(flyTo)

this.radial.x = this.container.clientWidth / 2;
this.radial.y = this.container.clientHeight / 2;

this.radial.cot = e.features[0];
this.radial.mode = click.type;
});
Expand Down
6 changes: 3 additions & 3 deletions api/web/src/stores/overlays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ export const useOverlayStore = defineStore('overlays', {
this.overlays = (await std(`/api/profile/overlay`)).items;

this.subscriptions.clear();
for (const sub of this.overlays) {
if (sub.mode === 'mission') {
for (const overlay of this.overlays) {
if (overlay.mode === 'mission') {
// mode_id is GUID for mission type
this.subscriptions.set(sub.mode_id, sub);
this.subscriptions.set(overlay.mode_id, overlay);
}
}

Expand Down
Loading