extensive changes

This commit is contained in:
2026-03-27 17:11:13 -04:00
parent 3f3a5136eb
commit 05e63a28f1
14 changed files with 1431 additions and 508 deletions

View File

@@ -106,12 +106,12 @@ export default defineConfig((/* ctx */) => {
proxy: { proxy: {
// proxy all requests starting with /api to jsonplaceholder // proxy all requests starting with /api to jsonplaceholder
'/api': { '/api': {
target: 'http://localhost:8000', target: 'http://localhost:49151',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''), rewrite: (path) => path.replace(/^\/api/, ''),
}, },
'/socket.io': { '/socket.io': {
target: 'http://localhost:8000', // Your backend WebSocket server target: 'http://localhost:49151', // Your backend WebSocket server
secure: false, // Set to true if using wss:// secure: false, // Set to true if using wss://
ws: true, // Enable WebSocket proxying ws: true, // Enable WebSocket proxying
rewriteWsOrigin: true, rewriteWsOrigin: true,
@@ -141,7 +141,7 @@ export default defineConfig((/* ctx */) => {
}, },
// animations: 'all', // --- includes all animations // animations: 'all', // --- includes all animations
// https://v2.quasar.dev/options/animations // https://v2.quasar.dev/options/animations
animations: [], animations: ['slideInLeft', 'slideOutLeft', 'slideOutRight'],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#sourcefiles // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#sourcefiles
// sourceFiles: { // sourceFiles: {

View File

@@ -1,84 +1,73 @@
<template>
<div style="display: none"></div>
</template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, onMounted, onBeforeUnmount } from 'vue'; import {
import 'leaflet-routing-machine/dist/leaflet-routing-machine.css'; ref,
import L from 'leaflet'; markRaw,
inject,
nextTick,
onMounted,
onBeforeUnmount,
useAttrs,
defineEmits,
} from 'vue';
import { routingControlProps, setupRoutingControl } from 'functions/routingControl';
import { Utilities, InjectionKeys } from '@vue-leaflet/vue-leaflet';
import 'leaflet';
import 'leaflet-routing-machine'; import 'leaflet-routing-machine';
import 'leaflet-routing-machine/dist/leaflet-routing-machine.css'; import 'leaflet-routing-machine/dist/leaflet-routing-machine.css';
import type { IRouter, IGeocoder, LineOptions } from 'leaflet-routing-machine';
// Props import type L from 'leaflet';
const props = defineProps<{
mapObject?: any; // ---- Emits ----
visible?: boolean; const emit = defineEmits<{
waypoints: any[]; (e: 'ready', value: L.Routing.Control): void;
router?: IRouter;
plan?: L.Routing.Plan;
geocoder?: IGeocoder;
fitSelectedRoutes?: string | boolean;
lineOptions?: LineOptions;
routeLine?: Function;
autoRoute?: boolean;
routeWhileDragging?: boolean;
routeDragInterval?: number;
waypointMode?: string;
useZoomParameter?: boolean;
showAlternatives?: boolean;
altLineOptions?: LineOptions;
}>(); }>();
// Defaults // ---- Props ----
const visible = props.visible ?? true; const props = defineProps(routingControlProps);
const fitSelectedRoutes = props.fitSelectedRoutes ?? 'smart';
const autoRoute = props.autoRoute ?? true;
const routeWhileDragging = props.routeWhileDragging ?? false;
const routeDragInterval = props.routeDragInterval ?? 500;
const waypointMode = props.waypointMode ?? 'connect';
const useZoomParameter = props.useZoomParameter ?? false;
const showAlternatives = props.showAlternatives ?? false;
// State // ---- Attrs (for events) ----
const ready = ref(false); const attrs = useAttrs();
const layer = ref<any>(null);
// Methods // ---- Injections ----
function add() { const { UseGlobalLeafletInjection, RegisterControlInjection } = InjectionKeys;
if (!props.mapObject) return; const { WINDOW_OR_GLOBAL, assertInject, propsBinder, remapEvents } = Utilities;
const options = { const useGlobalLeaflet = inject(UseGlobalLeafletInjection, false);
waypoints: props.waypoints, const registerControl = assertInject(RegisterControlInjection);
fitSelectedRoutes,
autoRoute,
routeWhileDragging,
routeDragInterval,
waypointMode,
useZoomParameter,
showAlternatives,
};
console.log(L.Routing);
const routingLayer = L.Routing.control(options);
routingLayer.addTo(props.mapObject);
layer.value = routingLayer;
ready.value = true; // ---- State ----
} const leafletObject = ref<L.Routing.Control | null>(null);
// Watchers // ---- Setup logic ----
watch( const { options, methods } = setupRoutingControl(props);
() => props.mapObject,
(val) => {
if (!val) return;
add();
},
);
// Lifecycle onMounted(async () => {
onMounted(() => { const { routing } = useGlobalLeaflet
add(); ? (WINDOW_OR_GLOBAL as any).L
: await import('leaflet/dist/leaflet-src.esm');
const { listeners } = remapEvents(attrs);
leafletObject.value = markRaw(routing.control(options));
leafletObject.value.on(listeners);
propsBinder(methods, leafletObject.value, props);
registerControl({ leafletObject: leafletObject.value });
await nextTick();
emit('ready', leafletObject.value);
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (layer.value) { if (leafletObject.value) {
layer.value.remove(); leafletObject.value.setWaypoints([]);
leafletObject.value.remove();
} }
}); });
</script> </script>

View File

@@ -1,64 +1,168 @@
<template> <template>
<div style="height: 600px; width: 800px; color: #000000"> <div class="q-pa-md">
<q-layout
view="hHh Lpr fFf"
container
style="height: 600px; width: 100vw"
class="rounded-borders"
>
<q-footer :class="$q.dark.isActive ? 'bg-primary' : 'bg-black'" style="height: 48px">
<q-toolbar>
<q-btn
class="q-mr-sm"
dense
flat
icon="menu"
round
size="sm"
@click="qLocDrawer = !qLocDrawer"
/>
<q-btn-dropdown flat label="Zoom To" size="sm" stretch>
<q-list>
<q-item v-close-popup v-ripple clickable @click="zoomTo('fmLoc')">
<q-item-section>
<q-item-label>Find My Location</q-item-label>
</q-item-section>
</q-item>
<q-item v-close-popup v-ripple clickable @click="zoomTo('simLoc')">
<q-item-section>
<q-item-label>Sim Location</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
<q-btn label="Routing" @click="routeLayer = !routeLayer" size="sm" stretch />
</q-toolbar>
</q-footer>
<q-drawer
v-model="qLocDrawer"
show-if-above
mini-to-overlay
overlay
:width="300"
side="left"
:breakpoint="500"
:mini="miniState"
@mouseenter="miniState = false"
@mouseleave="miniState = true"
>
<q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: 0 }">
<q-list padding>
<q-item-label header>Location Queue</q-item-label>
<q-separator />
<LocationMark
v-for="key in locationQueueOrder"
:key="key"
:loc_id="key"
:active="locationQueueData[key].loc_id === currentLocation.loc_id"
:start="locationQueueData[key].start"
:address="locationQueueData[key].address"
:latitude="locationQueueData[key].latitude"
:longitude="locationQueueData[key].longitude"
:end="locationQueueData[key].end"
@item-clicked="zoomToCoods"
/>
</q-list>
</q-scroll-area>
</q-drawer>
<q-page-container>
<q-page>
<div style="height: 560px; width: 90vw; color: #000000">
<L-Map <L-Map
@ready="onMapReady"
ref="mapRef" ref="mapRef"
:zoom="zoom"
:center="center" :center="center"
style="height: 500px; width: 100%" :zoom="zoom"
style="height: 550px; width: 100%"
@click="updateMarker" @click="updateMarker"
@ready="onMapReady"
> >
<L-Tile-Layer <L-Tile-Layer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
layer-type="base" layer-type="base"
name="OpenStreetMap" name="OpenStreetMap"
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
@click="updateMarker($event.latlng)" @click="updateMarker($event.latlng)"
></L-Tile-Layer> ></L-Tile-Layer>
<L-Marker :lat-lng="markerLatLng" @click="handleMarkerClick"></L-Marker> <L-Layer-Group>
<L-Routing-Machine <L-Marker v-if="markerLatLng" :lat-lng="markerLatLng" @click="handleMarkerClick">
</L-Marker>
</L-Layer-Group>
<L-Layer-Group v-if="locationQueueOrder">
<L-Marker
v-for="locid in locationQueueOrder"
:key="locid"
:icon="getCustomIcon(locid)"
:lat-lng="[locationQueueData[locid].latitude, locationQueueData[locid].longitude]"
>
</L-Marker>
</L-Layer-Group>
<L-Layer-Group v-if="routeLayer">
<LRoutingMachine
v-bind="routingOptions"
@routingstart="debugRoutingEvent" @routingstart="debugRoutingEvent"
@routesfound="debugRoutingEvent" @routesfound="debugRoutingEvent"
@routingerror="debugRoutingEvent" @routingerror="debugRoutingEvent"
:waypoints="waypoints"
/> />
</L-Layer-Group>
<L-Layer-Group v-if="findMyUpdate">
<L-Marker
v-if="findMyUpdate"
:icon="fmIcon"
:lat-lng="[findMyUpdate.latitude, findMyUpdate.longitude]"
></L-Marker>
<L-Circle
:fillOpacity="0.5"
:lat-lng="[findMyUpdate.latitude, findMyUpdate.longitude]"
:radius="findMyUpdate.horizontalAccuracy"
color="firebrick"
fillColor="indianred"
></L-Circle>
</L-Layer-Group>
</L-Map> </L-Map>
</div> </div>
</q-page>
</q-page-container>
</q-layout>
</div>
</template> </template>
<script setup lang="ts"> <script lang="ts" setup>
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { ref } from 'vue'; import { computed, onMounted, reactive, ref } from 'vue';
import { GeoSearchControl, OpenStreetMapProvider } from 'leaflet-geosearch'; import { GeoSearchControl, OpenStreetMapProvider } from 'leaflet-geosearch';
import LRoutingMachine from 'components/LRoutingMachine.vue'; import 'leaflet-routing-machine/dist/leaflet-routing-machine.css';
import { Icon, PinCirclePanel, PinStarPanel } from 'leaflet-extra-markers';
import 'leaflet-geosearch/dist/geosearch.css'; import 'leaflet-geosearch/dist/geosearch.css';
import 'leaflet/dist/leaflet.css'; import 'leaflet/dist/leaflet.css';
import LRoutingMachine from 'components/LRoutingMachine.vue';
import LocationMark from 'components/LocationMark.vue';
import type { coords, SearchControlProps } from 'src/types'; import type { coords, SearchControlProps } from 'src/types';
import type { Map, LeafletMouseEvent } from 'leaflet'; import type { LeafletMouseEvent, Map } from 'leaflet';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useSocketioStore } from 'stores/socketio'; import { useSocketioStore } from 'stores/socketio';
import { useLeafletStore } from 'stores/leaflet'; import { useLeafletStore } from 'stores/leaflet';
import SetLocationDialog from 'components/SetLocationDialog.vue'; import SetLocationDialog from 'components/SetLocationDialog.vue';
import { LMap, LMarker, LTileLayer } from '@vue-leaflet/vue-leaflet'; import { LCircle, LLayerGroup, LMap, LMarker, LTileLayer } from '@vue-leaflet/vue-leaflet';
import { favorites } from 'constants/favorites';
const waypoints = [
[ 38.7436056, -9.2304153 ],
[ 38.7436056, -0.131281 ],
];
const leafletStore = useLeafletStore(); const leafletStore = useLeafletStore();
const socketStore = useSocketioStore(); const { zoom, center, markerLatLng, qLocDrawer } = storeToRefs(leafletStore);
const { zoom, center, markerLatLng } = storeToRefs(socketStore);
const $q = useQuasar();
const mapRef = ref(null);
const responseMessage = ref('');
const debugRoutingEvent = (event) => { const socketStore = useSocketioStore();
console.log(`${event.type} event: `, event); const { currentLocation, nextLocation, locationQueueData, locationQueueOrder, findMyUpdate } =
}; storeToRefs(socketStore);
const $q = useQuasar();
const mapRef = ref();
const responseMessage = ref('');
const routeStart = ref(null);
const routeEnd = ref(null);
const routeLayer = ref(false);
const miniState = ref(true);
const onMapReady = (map: Map) => { const onMapReady = (map: Map) => {
const provider = new OpenStreetMapProvider(); const provider = new OpenStreetMapProvider();
@@ -81,6 +185,48 @@ const onMapReady = (map: Map) => {
map.addControl(searchControl); map.addControl(searchControl);
}; };
const fmIcon = new Icon({
color: 'indianred',
accentColor: 'firebrick',
content: 'FM',
contentColor: 'white',
scale: 1,
svg: PinCirclePanel,
});
const getCustomIcon = (locid: string) => {
const locationIndex = locationQueueOrder.value.indexOf(locid);
if (currentLocation.value && currentLocation.value.loc_id === locid) {
return new Icon({
color: 'pink',
accentColor: 'black',
content: '*',
contentColor: 'black',
scale: 1.5,
svg: PinStarPanel,
});
}
return new Icon({
color: 'blue',
accentColor: 'firebrick',
content: locationIndex.toString(),
contentColor: 'white',
scale: 1,
svg: PinCirclePanel,
});
};
const routingOptions = reactive({
waypoints: [
[40.910773020811, -73.891069806448],
[40.90930366920829, -73.87658695470259],
],
});
const debugRoutingEvent = (event) => {
console.log(`${event.type} event: `, event);
};
function updateMarker(event: LeafletMouseEvent) { function updateMarker(event: LeafletMouseEvent) {
markerLatLng.value = [event.latlng.lat, event.latlng.lng]; markerLatLng.value = [event.latlng.lat, event.latlng.lng];
center.value = [event.latlng.lat, event.latlng.lng]; center.value = [event.latlng.lat, event.latlng.lng];
@@ -114,10 +260,17 @@ function handleMarkerClick(event: LeafletMouseEvent) {
} }
function setLocation(coords: coords, delay: number) { function setLocation(coords: coords, delay: number) {
let notType: string = 'positive';
try { try {
responseMessage.value = socketStore.simulationControl('add', delay, coords.lat, coords.lng); const setCmdRsp = socketStore.simulationControl('add', delay, coords.lat, coords.lng);
$q.notify({ type: 'positive', message: responseMessage.value }); if (setCmdRsp.sts === 'error') {
notType = 'negative';
}
if (setCmdRsp.msg) {
responseMessage.value = setCmdRsp.msg;
}
} catch (error: unknown) { } catch (error: unknown) {
notType = 'negative';
if (error instanceof Error) { if (error instanceof Error) {
console.error('Error setting location:', error.message); console.error('Error setting location:', error.message);
responseMessage.value = `Failed to set location: ${error.message}`; responseMessage.value = `Failed to set location: ${error.message}`;
@@ -125,14 +278,65 @@ function setLocation(coords: coords, delay: number) {
console.error('Error setting location:', error); console.error('Error setting location:', error);
responseMessage.value = `Failed to set location: Unknown error`; responseMessage.value = `Failed to set location: Unknown error`;
} }
$q.notify({ type: 'negative', message: responseMessage.value }); } finally {
$q.notify({ type: notType, message: responseMessage.value });
} }
} }
function zoomToCoods(arg: string) {
leafletStore.setCenter(
locationQueueData.value[arg].latitude,
locationQueueData.value[arg].longitude,
);
leafletStore.setZoom(50);
qLocDrawer.value = false;
}
function zoomTo(loc: string) {
switch (loc) {
case 'fmLoc':
if (findMyUpdate.value && findMyUpdate.value.latitude && findMyUpdate.value.longitude) {
leafletStore.setCenter(findMyUpdate.value.latitude, findMyUpdate.value.longitude);
}
$q.notify({ type: 'negative', message: 'Find My Location not available' });
break;
case 'simLoc':
if (
currentLocation.value &&
currentLocation.value.latitude &&
currentLocation.value.longitude
) {
leafletStore.setCenter(currentLocation.value.latitude, currentLocation.value.longitude);
}
$q.notify({ type: 'negative', message: 'Simulation Location not available' });
break;
case 'nextLoc':
if (nextLocation.value && nextLocation.value.latitude && nextLocation.value.longitude) {
leafletStore.setCenter(nextLocation.value.latitude, nextLocation.value.longitude);
}
$q.notify({ type: 'negative', message: 'Next Location not available' });
break;
default:
$q.notify({ type: 'negative', message: 'Invalid location' });
break;
}
}
onMounted(() => {
if (findMyUpdate.value && findMyUpdate.value.latitude && findMyUpdate.value.longitude) {
leafletStore.setCenter(findMyUpdate.value.latitude, findMyUpdate.value.longitude);
} else {
leafletStore.setCenter(favorites.home.coords.lat, favorites.home.coords.lng);
}
});
</script> </script>
<style> <style lang="sass">
.l-map { .l-map
height: 100vh; height: 100vh
width: 100vw; width: 100vw
}
.q-item.q-router-link--active, .q-item--active
background-color: $accent
color: $primary
</style> </style>

View File

@@ -0,0 +1,226 @@
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useSocketioStore } from 'stores/socketio';
import { computed, onMounted, onUnmounted, ref } from 'vue';
const socketStore = useSocketioStore();
const { currentLocation, locationQueueOrder } = storeToRefs(socketStore);
const props = defineProps({
address: {
type: String,
required: true,
},
latitude: {
type: Number,
required: true,
},
longitude: {
type: Number,
required: true,
},
icon: {
type: String,
default: 'location_on',
},
start: {
type: String,
required: true,
},
end: {
type: String,
required: false,
},
delay: {
type: Number,
default: 0,
},
active: {
type: Boolean,
default: false,
},
loc_id: {
type: String,
required: true,
},
});
// Define custom events that this component can emit
const emit = defineEmits(['item-clicked']);
function itemClicked() {
const param1 = props.loc_id;
emit('item-clicked', param1);
}
const currentTime = ref(new Date());
let timerId: number;
const calculateDeltaT = computed(() => {
let delta = '';
if (props.active) {
delta = 'now';
} else if (props.end && props.end !== '') {
const pastTime: Date = new Date(props.end);
const nowMs: number = currentTime.value.getTime();
const pastMs: number = pastTime.getTime();
const diffMs: number = Math.abs(nowMs - pastMs);
const seconds: number = Math.floor(diffMs / 1000);
const minutes: number = Math.floor(seconds / 60);
const hours: number = Math.floor(minutes / 60);
const days: number = Math.floor(hours / 24);
if (days > 0) {
delta = `${days} day${days > 1 ? 's' : ''} ago`;
} else if (hours > 0) {
delta = `${hours} hour${hours > 1 ? 's' : ''} ago`;
} else if (minutes > 0) {
delta = `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
} else {
delta = `${seconds} second${seconds > 1 ? 's' : ''} ago`;
}
} else {
const futureTime: Date = new Date(props.start);
const nowMs: number = currentTime.value.getTime();
const futureMs: number = futureTime.getTime();
const diffMs: number = Math.abs(nowMs - futureMs);
const seconds: number = Math.floor(diffMs / 1000);
const minutes: number = Math.floor(seconds / 60);
const hours: number = Math.floor(minutes / 60);
const days: number = Math.floor(hours / 24);
if (days > 0) {
delta = `in ${days} day${days > 1 ? 's' : ''}`;
} else if (hours > 0) {
delta = `in ${hours} hour${hours > 1 ? 's' : ''}`;
} else if (minutes > 0) {
delta = `in ${minutes} minute${minutes > 1 ? 's' : ''}`;
} else {
delta = `in ${seconds} second${seconds > 1 ? 's' : ''}`;
}
}
return delta;
});
const humanReadableDateTime = (iso: string) => {
return new Date(iso).toLocaleDateString('en-US', {
// year: 'numeric',
// month: 'long',
// day: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
};
const stateAbbrevMap: Record<string, string> = {
Alabama: 'AL',
Alaska: 'AK',
Arizona: 'AZ',
Arkansas: 'AR',
California: 'CA',
Colorado: 'CO',
Connecticut: 'CT',
Delaware: 'DE',
Florida: 'FL',
Georgia: 'GA',
Hawaii: 'HI',
Idaho: 'ID',
Illinois: 'IL',
Indiana: 'IN',
Iowa: 'IA',
Kansas: 'KS',
Kentucky: 'KY',
Louisiana: 'LA',
Maine: 'ME',
Maryland: 'MD',
Massachusetts: 'MA',
Michigan: 'MI',
Minnesota: 'MN',
Mississippi: 'MS',
Missouri: 'MO',
Montana: 'MT',
Nebraska: 'NE',
Nevada: 'NV',
'New Hampshire': 'NH',
'New Jersey': 'NJ',
'New Mexico': 'NM',
'New York': 'NY',
'North Carolina': 'NC',
'North Dakota': 'ND',
Ohio: 'OH',
Oklahoma: 'OK',
Oregon: 'OR',
Pennsylvania: 'PA',
'Rhode Island': 'RI',
'South Carolina': 'SC',
'South Dakota': 'SD',
Tennessee: 'TN',
Texas: 'TX',
Utah: 'UT',
Vermont: 'VT',
Virginia: 'VA',
Washington: 'WA',
'West Virginia': 'WV',
Wisconsin: 'WI',
Wyoming: 'WY',
};
function formatAddress(input: string): string {
const parts = input.split(',').map((p) => p.trim());
if (parts.length < 5) return input; // fallback safety
const streetNumber = parts[0];
const streetName = parts[1];
const zip = parts.at(-2) ?? '';
const stateFull = parts.at(-3) ?? '';
const cityRaw = parts.at(-5) ?? '';
const city = cityRaw.replace(/^City of\s+/i, '');
const state = stateAbbrevMap[stateFull] ?? stateFull;
return `${streetNumber} ${streetName}, ${city}, ${state} ${zip}`;
}
onMounted(() => {
const update = () => {
currentTime.value = new Date();
timerId = requestAnimationFrame(update);
};
timerId = requestAnimationFrame(update);
});
onUnmounted(() => {
cancelAnimationFrame(timerId);
});
</script>
<template>
<q-item v-ripple clickable :active="active" @click="itemClicked">
<q-item-section>
<q-item-label
>{{ formatAddress(address) }}
<q-tooltip> {{ latitude }}, {{ longitude }} </q-tooltip>
</q-item-label>
<q-item-label caption lines="1" v-if="start">
start: {{ humanReadableDateTime(start) }}
</q-item-label>
<q-item-label caption lines="1" v-if="end">
end: {{ humanReadableDateTime(end) }}
</q-item-label>
</q-item-section>
<q-item-section side top>
<q-item-label caption lines="1">
{{ calculateDeltaT }}
</q-item-label>
<q-item-section avatar class="q-pt-md">
<q-btn dense color="primary" round icon="location_on" class="q-ml-md">
<q-badge color="accent" floating>{{
active ? '*' : locationQueueOrder.indexOf(loc_id)
}}</q-badge>
</q-btn>
</q-item-section>
</q-item-section>
</q-item>
</template>
<style scoped></style>

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useLeafletStore } from 'stores/leaflet'; import { useLeafletStore } from 'stores/leaflet';
import type { Control, coords, CtrlAttr, CtrlAttrs } from 'components/models'; import type { coords, CtrlAttrs } from 'components/models';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { socket } from 'boot/socketio'; import { socket } from 'boot/socketio';
import ConfirmCommandDialog from 'components/ConfirmCommandDiaglog.vue'; import ConfirmCommandDialog from 'components/ConfirmCommandDiaglog.vue';
@@ -18,18 +18,16 @@ const leafletStore = useLeafletStore();
const socketStore = useSocketioStore(); const socketStore = useSocketioStore();
const { center, markerLatLng } = storeToRefs(leafletStore); const { center, markerLatLng } = storeToRefs(leafletStore);
const { simulationRunning, simulationState, simulationQueueLength, icloudMonitor, testMode } = storeToRefs(socketStore);
const { simulationRunning, simulationState, simulationQueueLength } = storeToRefs(socketStore);
const menuOpen = ref(); const menuOpen = ref();
function handleFavClick(coords: coords) { function handleFavClick(coords: coords) {
center.value = [coords.lat, coords.lng]; center.value = [coords.lat, coords.lng];
markerLatLng.value = [coords.lat, coords.lng]; markerLatLng.value = [coords.lat, coords.lng];
} }
function handleControlClick(cmdAttr: CtrlAttr) { function handleControlClick(cmdAttr) {
if (cmdAttr.cnfrm) { if (cmdAttr.cnfrm) {
$q.dialog({ $q.dialog({
component: ConfirmCommandDialog, component: ConfirmCommandDialog,
@@ -38,25 +36,33 @@ function handleControlClick(cmdAttr: CtrlAttr) {
}, },
}) })
.onOk(() => { .onOk(() => {
if (cmdAttr.cmdClass === 'simulation_control') { if (cmdAttr.cmdClass === 'sim_cntrl_class') {
let notType: string = 'positive';
let notMsg: string = '';
try { try {
const ack = socketStore.simulationControl(cmdAttr.cmd, cmdAttr.delay); const ack = socketStore.simulationControl(cmdAttr['cmd'], cmdAttr.delay);
$q.notify({ type: 'positive', message: ack }); if (ack.sts === 'error') {
notType = 'negative';
}
if (ack.msg) {
notMsg = ack.msg;
}
} catch (error: unknown) { } catch (error: unknown) {
notType = 'negative';
if (error instanceof Error) { if (error instanceof Error) {
console.error('Simulation Command ERROR: ', error.message); console.error('Simulation Command ERROR: ', error.message);
const ack = `Simulation Command Error: ${error.message}`; notMsg = `Simulation Command Error: ${error.message}`;
$q.notify({ type: 'negative', message: ack });
} else { } else {
console.error('Simmulation Command Error: ', error); console.error('Simulation Command Error: ', error);
const ack = 'Simulation Command Error: Unknow error'; notMsg = 'Simulation Command Error: Unknow error';
$q.notify({ type: 'negative', message: ack });
} }
} finally {
$q.notify({ type: notType, message: notMsg });
} }
} }
if (ctrl.cmdClass === 'device_control') { if (cmdAttr.cmdClass === 'dev_cntrl_class') {
socket.emit('device_control', { command: ctrl.cmd, delay: 0 }, (response) => { socket.emit('device_control', { command: cmdAttr.cmd, delay: 0 }, (response) => {
console.log(response.status, response.command); console.log(response.status, response.command);
}); });
} }
@@ -68,22 +74,57 @@ function handleControlClick(cmdAttr: CtrlAttr) {
console.log('Dialog dismissed'); console.log('Dialog dismissed');
}); });
} else { } else {
if (ctrl.cmdClass === 'simulation_control') { if (cmdAttr.cmdClass === 'sim_cntrl_class') {
let notType: string = 'positive';
let notMsg: string = '';
try { try {
const response = socketStore.simulationControl(ctrl.cmd); const ack = socketStore.simulationControl(cmdAttr.cmd, cmdAttr.delay);
$q.notify({ type: 'positive', message: response }); if (ack.sts === 'error') {
notType = 'negative';
}
if (ack.msg) {
notMsg = ack.msg;
}
} catch (error: unknown) { } catch (error: unknown) {
notType = 'negative';
if (error instanceof Error) { if (error instanceof Error) {
console.error('Error: ' + error.message); console.error('Simulation Command ERROR: ', error.message);
$q.notify({ type: 'negative', message: error.toString() }); notMsg = `Simulation Command Error: ${error.message}`;
} else { } else {
console.error('Error setting location:', error); console.error('Simulation Command Error: ', error);
$q.notify({ type: 'negative', message: error.toString() }); notMsg = 'Simulation Command Error: Unknow error';
}
} finally {
$q.notify({ type: notType, message: notMsg });
} }
} }
if (cmdAttr.cmdClass === 'icloud-monitor_cntrl_class') {
let notType: string = 'positive';
let notMsg: string = '';
try {
const ack = socketStore.icloudMonitorControl(cmdAttr.cmd);
if (ack.sts === 'error') {
notType = 'negative';
} }
if (ctrl.cmdClass === 'device_control') { if (ack.msg) {
socket.emit('device_control', { command: ctrl.cmd, delay: 0 }, (response) => { notMsg = ack.msg;
}
} catch (error: unknown) {
notType = 'negative';
if (error instanceof Error) {
console.error('Simulation Command ERROR: ', error.message);
notMsg = `Simulation Command Error: ${error.message}`;
} else {
console.error('Simulation Command Error: ', error);
notMsg = 'Simulation Command Error: Unknow error';
}
} finally {
$q.notify({ type: notType, message: notMsg });
}
}
if (cmdAttr.cmdClass === 'dev_cntrl_class') {
socket.emit('device_control', { command: cmdAttr.cmd, delay: 0 }, (response) => {
console.log(response.status, response.command); console.log(response.status, response.command);
}); });
} }
@@ -92,53 +133,59 @@ function handleControlClick(cmdAttr: CtrlAttr) {
</script> </script>
<template> <template>
<q-toolbar class="bg-primary text-white"> <q-toolbar :class="testMode ? 'bg-warning text-black' : 'bg-primary text-white'">
<q-btn @click="$emit('drawer')" flat round dense icon="menu" class="q-mr-sm" /> <q-btn @click="$emit('drawer'); leafletStore.toggleQLocDrawer()" flat round dense icon="menu" class="q-mr-sm" />
<q-separator dark inset /> <q-separator dark inset />
<q-space /> <q-space />
<q-btn :icon-right="menuOpen ? 'arrow_drop_up' : 'arrow_drop_down'" stretch flat label="Favorites" v-if="route.name === 'Leaflet'"> <q-btn
:icon-right="menuOpen ? 'arrow_drop_up' : 'arrow_drop_down'"
stretch
flat
label="Favorites"
v-if="route.name === 'Leaflet'"
>
<q-menu @show="menuOpen = true" @hide="menuOpen = false" anchor="bottom end" self="top end"> <q-menu @show="menuOpen = true" @hide="menuOpen = false" anchor="bottom end" self="top end">
<q-list> <q-list>
<template v-for="fav, index) in favorites" :key="index"> <template v-for="(favObj, favId) in favorites" :key="favId">
<q-item <q-item
v-if="fav.coords" v-if="favObj.coords"
clickable clickable
v-ripple v-ripple
v-close-popup v-close-popup
@click="handleFavClick(fav.coords)" @click="handleFavClick(favObj.coords)"
> >
<q-item-section avatar> <q-item-section avatar>
<q-avatar :icon="fav.icon" color="primary" text-color="white" /> <q-avatar :icon="favObj.icon" color="primary" text-color="white" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label>{{ fav.name }}</q-item-label> <q-item-label>{{ favObj.name }}</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item v-else clickable v-ripple> <q-item v-else clickable v-ripple>
<q-item-section avatar> <q-item-section avatar>
<q-avatar :icon="fav.icon" color="primary" text-color="white" /> <q-avatar :icon="favObj.icon" color="primary" text-color="white" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label>{{ fav.name }}</q-item-label> <q-item-label>{{ favObj.name }}</q-item-label>
</q-item-section> </q-item-section>
<q-item-section side> <q-item-section side>
<q-icon name="keyboard_arrow_right" /> <q-icon name="keyboard_arrow_right" />
</q-item-section> </q-item-section>
<q-menu anchor="top end" self="top start"> <q-menu anchor="bottom start" self="bottom end">
<q-list> <q-list>
<q-item <q-item
v-for="(f, indx) in fav.subitems" v-for="(favSubObj, favSubId) in favObj.subitems"
:key="indx" :key="favSubId"
clickable clickable
v-ripple v-ripple
v-close-popup v-close-popup
@click="handleFavClick(f.coords)" @click="handleFavClick(favSubObj.coords)"
> >
<q-item-section avatar> <q-item-section avatar>
<q-avatar :icon="f.icon" color="primary" text-color="white" /> <q-avatar :icon="favSubObj.icon" color="primary" text-color="white" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label>{{ f.name }}</q-item-label> <q-item-label>{{ favSubObj.name }}</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
</q-list> </q-list>
@@ -156,12 +203,13 @@ function handleControlClick(cmdAttr: CtrlAttr) {
clickable clickable
v-ripple v-ripple
v-close-popup v-close-popup
@click="handleControlClick(controls.sim_start)"> @click="handleControlClick(controls.simulation.start)"
>
<q-item-section avatar> <q-item-section avatar>
<q-avatar :icon="controls.sim_start.icon" color="primary" text-color="white" /> <q-avatar :icon="controls.simulation.start.icon" color="primary" text-color="white" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label> {{ controls.sim_start.name }} </q-item-label> <q-item-label> {{ controls.simulation.start.name }} </q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item <q-item
@@ -169,13 +217,13 @@ function handleControlClick(cmdAttr: CtrlAttr) {
clickable clickable
v-ripple v-ripple
v-close-popup v-close-popup
@click="handleControlClick(controls.sim_pause)" @click="handleControlClick(controls.simulation.pause)"
> >
<q-item-section avatar> <q-item-section avatar>
<q-avatar :icon="controls.sim_pause.icon" color="primary" text-color="white" /> <q-avatar :icon="controls.simulation.pause.icon" color="primary" text-color="white" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label> {{ controls.sim_pause.name }} </q-item-label> <q-item-label> {{ controls.simulation.pause.name }} </q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item <q-item
@@ -183,27 +231,27 @@ function handleControlClick(cmdAttr: CtrlAttr) {
clickable clickable
v-ripple v-ripple
v-close-popup v-close-popup
@click="handleControlClick(controls.sim_resume)" @click="handleControlClick(controls.simulation.resume)"
> >
<q-item-section avatar> <q-item-section avatar>
<q-avatar :icon="controls.sim_resume.icon" color="primary" text-color="white" /> <q-avatar :icon="controls.simulation.resume.icon" color="primary" text-color="white" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label> {{ controls.sim_resume.name }} </q-item-label> <q-item-label> {{ controls.simulation.resume.name }} </q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item <q-item
v-if="simulationQueueLength > 0" v-if="simulationQueueLength && simulationQueueLength > 0"
clickable clickable
v-ripple v-ripple
v-close-popup v-close-popup
@click="handleControlClick(controls.sim_clear)" @click="handleControlClick(controls.simulation.clear)"
> >
<q-item-section avatar> <q-item-section avatar>
<q-avatar :icon="controls.sim_clear.icon" color="primary" text-color="white" /> <q-avatar :icon="controls.simulation.clear.icon" color="primary" text-color="white" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label> {{ controls.sim_clear.name }} </q-item-label> <q-item-label> {{ controls.simulation.clear.name }} </q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item <q-item
@@ -211,31 +259,75 @@ function handleControlClick(cmdAttr: CtrlAttr) {
clickable clickable
v-ripple v-ripple
v-close-popup v-close-popup
@click="handleControlClick(controls.sim_end)" @click="handleControlClick(controls.simulation.end)"
> >
<q-item-section avatar> <q-item-section avatar>
<q-avatar :icon="controls.sim_end.icon" color="primary" text-color="white" /> <q-avatar :icon="controls.simulation.end.icon" color="primary" text-color="white" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label> {{ controls.sim_end.name }} </q-item-label> <q-item-label> {{ controls.simulation.end.name }} </q-item-label>
</q-item-section>
</q-item>
<q-separator spaced />
<q-item-label header>iCloud Monitor Controls</q-item-label>
<q-item
v-if="!icloudMonitor"
clickable
v-ripple
v-close-popup
@click="handleControlClick(controls.icloudmonitor.start)"
>
<q-item-section avatar>
<q-avatar
v-if="icloudMonitor"
:icon="controls.icloudmonitor.start.icon"
color="primary"
text-color="white"
/>
</q-item-section>
<q-item-section>
<q-item-label> {{ controls.icloudmonitor.start.name }} </q-item-label>
</q-item-section>
</q-item>
<q-item
clickable
v-ripple
v-close-popup
@click="handleControlClick(controls.icloudmonitor.stop)"
>
<q-item-section avatar>
<q-avatar :icon="controls.icloudmonitor.stop.icon" color="primary" text-color="white" />
</q-item-section>
<q-item-section>
<q-item-label> {{ controls.icloudmonitor.stop.name }} </q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-separator spaced /> <q-separator spaced />
<q-item-label header>Device Controls</q-item-label> <q-item-label header>Device Controls</q-item-label>
<q-item clickable v-ripple v-close-popup @click="handleControlClick(controls.dev_reboot)"> <q-item
clickable
v-ripple
v-close-popup
@click="handleControlClick(controls.device.reboot)"
>
<q-item-section avatar> <q-item-section avatar>
<q-avatar :icon="controls.dev_reboot.icon" color="primary" text-color="white" /> <q-avatar :icon="controls.device.reboot.icon" color="primary" text-color="white" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label> {{ controls.dev_reboot.name }} </q-item-label> <q-item-label> {{ controls.device.reboot.name }} </q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item clickable v-ripple v-close-popup @click="handleControlClick(controls.dev_shutdown)"> <q-item
clickable
v-ripple
v-close-popup
@click="handleControlClick(controls.device.shutdown)"
>
<q-item-section avatar> <q-item-section avatar>
<q-avatar :icon="controls.dev_shutdown.icon" color="primary" text-color="white" /> <q-avatar :icon="controls.device.shutdown.icon" color="primary" text-color="white" />
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label> {{ controls.dev_shutdown.name }} </q-item-label> <q-item-label> {{ controls.device.shutdown.name }} </q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
</q-list> </q-list>

View File

@@ -9,7 +9,14 @@
<span class="q-ml-sm"> <span class="q-ml-sm">
Are you sure you want to set location to {{ latitude }}, {{ longitude }} ? Are you sure you want to set location to {{ latitude }}, {{ longitude }} ?
</span> </span>
<q-input dense v-model="delay" autofocus @keyup.enter="onOkClick" label="Delay (seconds)" type="number" /> <q-input
dense
v-model="delay"
autofocus
@keyup.enter="onOkClick"
label="Delay (seconds)"
type="number"
/>
</q-card-section> </q-card-section>
<q-card-actions align="right"> <q-card-actions align="right">
<q-btn flat label="OK" color="primary" @click="onOkClick" /> <q-btn flat label="OK" color="primary" @click="onOkClick" />
@@ -37,6 +44,6 @@ defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef: dlgRef, onDialogOK, onDialogCancel } = useDialogPluginComponent(); const { dialogRef: dlgRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
function onOkClick() { function onOkClick() {
onDialogOK(); onDialogOK(delay.value);
} }
</script> </script>

View File

@@ -8,6 +8,7 @@ const {
deviceConnected, deviceConnected,
tunnelConnected, tunnelConnected,
simulationRunning, simulationRunning,
icloudMonitor,
currentLocation, currentLocation,
nextLocation, nextLocation,
} = storeToRefs(socketioStore); } = storeToRefs(socketioStore);
@@ -33,23 +34,54 @@ function statusDevColor(state: string | boolean): string {
<q-toolbar class="bg-primary text-white"> <q-toolbar class="bg-primary text-white">
<div class="flex col q-gutter-md align-center justify-start content-center"> <div class="flex col q-gutter-md align-center justify-start content-center">
<q-space /> <q-space />
<span>Status:</span> <div style="width: 80vw" class="flex justify-end">
<span> <q-btn
<q-badge :color="statusDevColor(sockConnected)" rounded class="q-mr-sm" /> size="sm"
<q-btn label="WebSocket" @click="socketioStore.toggleSock()" /> rounded
</span> push
<span> icon="settings"
<q-badge :color="statusDevColor(deviceConnected)" rounded class="q-mr-sm" /> class="q-mr-sm"
Device Connection @click="socketioStore.toggleSock()"
</span> >
<span> <q-badge :color="statusDevColor(sockConnected)" rounded floating class="q-mr-sm" />
<q-badge :color="statusDevColor(tunnelConnected)" rounded class="q-mr-sm" /> </q-btn>
tunneld <q-btn size="sm" rounded icon="phone_iphone" class="q-mr-sm">
</span> <q-badge
<span> :color="statusDevColor(deviceConnected)"
<q-badge :color="statusDevColor(simulationRunning)" rounded class="q-mr-sm" /> @click="socketioStore.requestUpdate()"
Location Simulation rounded
</span> floating
class="q-mr-sm"
/>
</q-btn>
<q-btn
size="sm"
@click="socketioStore.toggleTunneld()"
rounded
icon="subway"
class="q-mr-sm"
>
<q-badge :color="statusDevColor(tunnelConnected)" rounded floating class="q-mr-sm" />
</q-btn>
<q-btn
size="sm"
@click="socketioStore.requestUpdate()"
rounded
icon="location_on"
class="q-mr-sm"
>
<q-badge :color="statusDevColor(simulationRunning)" rounded floating class="q-mr-sm" />
</q-btn>
<q-btn
size="sm"
@click="socketioStore.requestUpdate()"
rounded
icon="cloud"
class="q-mr-sm"
>
<q-badge :color="statusDevColor(icloudMonitor)" rounded floating class="q-mr-sm" />
</q-btn>
</div>
</div> </div>
</q-toolbar> </q-toolbar>
</template> </template>

View File

@@ -0,0 +1,41 @@
<template>
<q-dialog ref="dlgRef" persistent>
<q-card>
<q-card-section class="row items-center">
<q-avatar icon="password_2" color="primary" text-color="white" />
<span class="q-ml-sm"> iCloud Account requires Two-factor authentication.</span>
</q-card-section>
<q-card-section>
<q-input
dense
v-model="code"
mask="######"
autofocus
@keyup.enter="onOkClick"
label="Delay (seconds)"
type="number"
:rules="[(val) => val.length === 6 || 'Must be 6 digits']"
/>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="OK" color="primary" @click="onOkClick" />
<q-btn flat label="Cancel" color="primary" @click="onDialogCancel" />
</q-card-actions>
</q-card>
</q-dialog>
</template>
<script setup lang="ts">
import { useDialogPluginComponent } from 'quasar';
import { ref } from 'vue';
const code = ref('');
defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef: dlgRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
function onOkClick() {
onDialogOK(code.value);
}
</script>

View File

@@ -1,12 +1,14 @@
export type SimulationCommands = "start" | "pause" | "resume" | "clear" | "end" | "add";
export type DeviceCommands= "start_tunnel" | "stop_tunnel" | "shutdown";
export interface CtrlAttrs { export interface CtrlAttrs {
[key: string]: CtrlAttr; [key: string]: CtrlAttr;
} }
export type SimulationCommands = 'start' | 'pause' | 'resume' | 'clear' | 'end' | 'add';
export type DeviceCommands = 'start_tunnel' | 'stop_tunnel' | 'shutdown' | 'reboot';
export type TunnelCommands = 'start' | 'start-watcher' | 'end-watcher' | 'shutdown' | 'restart' | 'clear' | 'cancel' ;
export interface CtrlAttr { export interface CtrlAttr {
name: string; name: string;
cmd: string; cmd: string;
@@ -16,6 +18,15 @@ export interface CtrlAttr {
delay: number; delay: number;
} }
export interface DevCtrlAttr {
name: string;
cmd: DeviceCommands;
cmdClass: 'device_control';
icon: string;
cnfrm: boolean;
delay: number;
}
export interface LocationQueue { export interface LocationQueue {
[key: string]: LocationMark [key: string]: LocationMark
} }
@@ -24,9 +35,155 @@ interface LocationMark {
loc_id: string; loc_id: string;
latitude: number | undefined | null; latitude: number | undefined | null;
longitude: number | undefined | null; longitude: number | undefined | null;
address?: string | undefined | null;
delay?: number | undefined | null; delay?: number | undefined | null;
start_time: string | undefined | null; start?: string | undefined | null;
end_time?: string | undefined | null ; end?: string | undefined | null ;
}
// SERVER TO CLIENT
export interface ServerToClientEvents {
noArg: () => void;
withAck: (a: string, callback: (b: number) => void) => void;
simulation_status: (c: SimulationStatus) => void;
status: (d: StatusUpdate) => void;
device_status: (d: DeviceStatus) => void;
error: (data: ErrorFull) => void;
message: (e: string) => void;
icloud_2fa_request: (callback: (e: number) => void) => void;
fmf_update: (d: FindMyUpdate) => void;
}
interface SimulationStatus {
status: boolean;
data: {
latitude: number;
longitude: number;
start: string;
end?: string;
next_move?: number;
};
}
export interface StatusUpdate {
connected_clients: { [key: string]: string };
current_location: {
loc_id: string;
latitude: number | undefined | null;
longitude: number | undefined | null;
start?: string | undefined | null;
};
device_name: string | undefined | null;
fmf_location: FindMyUpdate | undefined | null;
icloud: {
consumer_queue: number | undefined | null;
consumer_task: string | undefined | null;
monitor_enabled: boolean;
monitor_task: string | undefined | null;
monitor_running: boolean;
};
next_move?: number | undefined | null;
set_location_enabled: boolean;
simulation_queue: {
active: boolean;
data: {
[key: string]: LocationMark;
};
order: string[];
state: string | undefined | null;
worker_task: string | undefined | null;
};
test_mode: boolean;
tunnel: string | undefined | null;
tunnel_watcher_running: boolean;
device_count?: number | undefined | null;
udid?: string | null;
product_version?: string | null | undefined;
phone_number?: string | null | undefined;
developer_mode_enabled?: boolean | undefined | null;
ddi_mounted?: boolean;
rsd_address?: string | null | undefined;
rsd_port?: number | undefined | null;
lockdown_trusted_port?: number | undefined | null;
lockdown_untrusted_port?: number | undefined | null;
lockdown_trusted_reachable?: boolean | undefined | null;
lockdown_untrusted_reachable?: boolean | undefined | null;
dtservicehub_reachable?: boolean | undefined | null;
}
interface DeviceStatus {
device_connected: boolean;
device_count: number;
udid?: string | null;
device_name?: string | null;
product_version?: string | null;
phone_number?: string | null;
developer_mode_enabled?: boolean;
ddi_mounted?: boolean;
rsd_address?: string | null;
rsd_port?: number;
lockdown_trusted_port?: number;
lockdown_untrusted_port?: number;
lockdown_trusted_reachable?: boolean;
lockdown_untrusted_reachable?: boolean;
dtservicehub_reachable?: boolean;
}
export interface ErrorFull {
type: string;
error: string;
}
export interface FindMyUpdate {
altitude: number;
batteryLevel: number;
deviceDisplayName: string;
deviceStatus: number;
horizontalAccuracy: number;
latitude: number;
longitude: number;
name: string;
timeStamp: number;
verticalAccuracy: number;
}
// END SERVER TO CLIENT
// CLIENT TO SERVER
export interface ClientToServerEvents {
message: (e: string, callback: (b: boolean, r: string) => void) => void;
request_update: (callback: (response: StatusUpdate) => void) => void;
simulation_control: (
args: {
command: SimulationCommands;
latitude?: number | null | undefined;
longitude?: number | null | undefined;
delay?: number | undefined;
},
callback: (response: SimulationControlResponse) => void,
) => void;
device_control: (
args: {
command: DeviceCommands;
delay?: number;
},
callback?: (response: DeviceControlResponse) => void,
) => void;
tunnel_control: (
args: {
command: TunnelCommands;
delay?: number;
},
callback?: (response: DeviceControlResponse) => void,
) => void;
icloud_monitor_control: (
args: {
command: string;
},
callback?: (response: iCloudMonitorResponse) => void,
) => void;
} }
export interface SimulationControlResponse { export interface SimulationControlResponse {
@@ -47,119 +204,20 @@ interface DeviceControlResponse {
delay?: number; delay?: number;
} }
interface StatusUpdate { export interface iCloudMonitorResponse {
simulation_active: boolean; status: string;
set_location_enabled: boolean; command: string;
queue: number, icloud_monitor_enabled?: boolean | undefined | null;
latitude: number | undefined | null; icloud_monitor_running?: boolean | undefined | null;
longitude: number | undefined | null; message?: string | undefined | null;
next_move?: number | undefined | null;
queue_list: LocationQueue[]
queue_state: string | undefined | null;
queue_status: boolean;
simulation_task: string | undefined | null;
test_mode: boolean
tunnel: string | undefined | null;
device_count?: number | undefined | null;
udid?: string | null;
device_name?: string | null | undefined;
product_version?: string | null | undefined;
phone_number?: string | null | undefined;
developer_mode_enabled?: boolean | undefined | null;
ddi_mounted?: boolean ;
rsd_address?: string | null | undefined;
rsd_port?: number | undefined | null;
lockdown_trusted_port?: number | undefined | null;
lockdown_untrusted_port?: number | undefined | null;
lockdown_trusted_reachable?: boolean | undefined | null;
lockdown_untrusted_reachable?: boolean | undefined | null;
dtservicehub_reachable?: boolean | undefined | null
} }
export interface ServerToClientEvents { // END CLIENT TO SERVER
noArg: () => void;
withAck: (a: string, callback: (b: number) => void) => void;
simulationStatus: (c: SimulationStatus) => void;
status: (d: StatusUpdate) => void;
device_status: (d: DeviceStatus) => void;
error: (data: ErrorFull) => void;
message: (e: string) => void;
}
export interface ClientToServerEvents {
message: (e: string, callback: (b: boolean, r: string) => void) => void;
simulation_control: (
args: {
command: SimulationCommands,
latitude?: number | null | undefined,
longitude?: number | null | undefined,
delay?: number | undefined
},
callback: (response: SimulationControlResponse) => void
) => void;
device_control: (
args: {
command: DeviceCommands,
delay?: number
},
callback?: (response: DeviceControlResponse) => void
) => void;
}
interface SimulationStatus {
status: boolean;
data: {
latitude: number;
longitude: number;
start: string;
end?: string;
next_move?: number;
};
}
export interface Meta { export interface Meta {
totalCount: number; totalCount: number;
} }
interface DeviceStatus {
device_connected: boolean;
device_count: number;
udid?: string | null;
device_name?: string | null;
product_version?: string | null;
phone_number?: string | null;
developer_mode_enabled?: boolean;
ddi_mounted?: boolean;
rsd_address?: string | null;
rsd_port?: number;
lockdown_trusted_port?: number;
lockdown_untrusted_port?: number;
lockdown_trusted_reachable?: boolean;
lockdown_untrusted_reachable?: boolean;
dtservicehub_reachable?: boolean;
}
export type Control = DeviceControl | SimulationControl;
export interface DeviceControl {
id: number;
name: string;
cmd: DeviceCommands
cmdClass: "device_control"
icon: string;
confirm: boolean;
}
export interface SimulationControl {
id: number;
name: string;
cmd: SimulationCommands;
cmdClass: 'simulation_control';
icon: string;
confirm: boolean;
}
export interface coords { export interface coords {
lat: number; lat: number;
lng: number; lng: number;
@@ -183,9 +241,11 @@ export interface SearchControlProps {
export interface CurrentLocation { export interface CurrentLocation {
loc_id: string; loc_id: string;
latitude: number; latitude: number| null | undefined;
longitude: number; longitude: number| null | undefined;
next_move?: number | null // start_time?: string | null | undefined;
// end_time?: string | null | undefined
next_move?: number | null | undefined;
} }
export interface NextLocation { export interface NextLocation {
@@ -195,7 +255,4 @@ export interface NextLocation {
time_at_location?: number | null; time_at_location?: number | null;
} }
export interface ErrorFull {
type: string;
error: string;
}

View File

@@ -1,62 +1,82 @@
import type { CtrlAttrs } from 'components/models'; import type { CtrlAttrs } from 'components/models';
export const controls: CtrlAttrs = { export const controls = {
sim_start: { simulation: {
start: {
name: 'Start Location Sim', name: 'Start Location Sim',
cmd: 'start', cmd: 'start',
cmdClass: 'simulation_control', cmdClass: 'sim_cntrl_class',
icon: 'play_arrow', icon: 'play_arrow',
cnfrm: false, cnfrm: false,
delay: 0, delay: 0,
}, },
sim_pause: { pause: {
name: 'Pause Location Sim', name: 'Pause Location Sim',
cmd: 'pause', cmd: 'pause',
cmdClass: 'simulation_control', cmdClass: 'sim_cntrl_class',
icon: 'pause', icon: 'pause',
cnfrm: false, cnfrm: false,
delay: 0, delay: 0,
}, },
sim_resume: { resume: {
name: 'Resume Location Simulation', name: 'Resume Location Simulation',
cmd: 'resume', cmd: 'resume',
cmdClass: 'simulation_control', cmdClass: 'sim_cntrl_class',
icon: 'play_arrow', icon: 'play_arrow',
cnfrm: false, cnfrm: false,
delay: 0, delay: 0,
}, },
sim_clear: { clear: {
name: 'Clear Location Queue', name: 'Clear Location Queue',
cmd: 'clear', cmd: 'clear',
cmdClass: 'simulation_control', cmdClass: 'sim_cntrl_class',
icon: 'directions_off', icon: 'directions_off',
cnfrm: false, cnfrm: false,
delay: 0, delay: 0,
}, },
sim_end: { end: {
name: 'End Location Sim', name: 'End Location Sim',
cmd: 'end', cmd: 'end',
cmdClass: 'simulation_control', cmdClass: 'sim_cntrl_class',
icon: 'stop', icon: 'stop',
cnfrm: true, cnfrm: true,
delay: 0, delay: 0,
}, },
dev_shutdown: { },
device: {
shutdown: {
name: 'Shutdown', name: 'Shutdown',
cmd: 'shutdown', cmd: 'shutdown',
cmdClass: 'device_control', cmdClass: 'dev_cntrl_class',
icon: 'power_settings_new', icon: 'power_settings_new',
cnfrm: true, cnfrm: true,
delay: 5, delay: 5,
}, },
dev_reboot: { reboot: {
name: 'Reboot', name: 'Reboot',
cmd: 'reboot', cmd: 'reboot',
cmdClass: 'device_control', cmdClass: 'dev_cntrl_class',
icon: 'restart_alt', icon: 'restart_alt',
cnfrm: true, cnfrm: true,
delay: 5, delay: 5,
}
}, },
icloudmonitor: {
start: {
name: 'Start iCloud Monitor',
cmd: 'start',
cmdClass: 'icloud-monitor_cntrl_class',
icon: 'play_arrow',
cnfrm: false,
delay: 0,
},
stop: {
name: 'Stop iCloud Monitor',
cmd: 'stop',
cmdClass: 'icloud-monitor_cntrl_class',
icon: 'stop',
cnfrm: false,
delay: 0,
}
}
}; };

View File

@@ -1,5 +1,5 @@
export const favorites = [ export const favorites = {
{ home: {
name: 'Home', name: 'Home',
icon: 'home', icon: 'home',
coords: { coords: {
@@ -7,11 +7,11 @@ export const favorites = [
lng: -73.891069806448, lng: -73.891069806448,
}, },
}, },
{ work_places: {
name: "Work Places", name: 'Work Places',
icon: "work", icon: 'work',
subitems: [ subitems: {
{ jeong: {
name: 'Jeong', name: 'Jeong',
icon: 'language_korean_latin', icon: 'language_korean_latin',
coords: { coords: {
@@ -20,17 +20,17 @@ export const favorites = [
}, },
address: '35-02 150th Pl, Flushing, NY 11354', address: '35-02 150th Pl, Flushing, NY 11354',
}, },
{ santos: {
name: 'Santos', name: 'Santos',
icon: 'rice_bowl', icon: 'rice_bowl',
coords: { coords: {
lat: 40.74504671877868, lat: 40.74504671877868,
lng: -73.8880099638491, lng: -73.8880099638491,
}, },
address: '77-08 Broadway, Elmhurst, NY 11373' address: '77-08 Broadway, Elmhurst, NY 11373',
}, },
{ natalya_qns: {
name: 'Natalyaa (Qns)', name: 'Natalya (Qns)',
icon: 'currency_ruble', icon: 'currency_ruble',
coords: { coords: {
lat: 40.69644966409178, lat: 40.69644966409178,
@@ -38,8 +38,8 @@ export const favorites = [
}, },
address: '110-14 Jamaica Ave, Richmond Hill, NY 11418', address: '110-14 Jamaica Ave, Richmond Hill, NY 11418',
}, },
{ natalya_bx: {
name: 'Natalyaa (Bronx)', name: 'Natalya (Bronx)',
icon: 'currency_ruble', icon: 'currency_ruble',
coords: { coords: {
lat: 40.85384419116598, lat: 40.85384419116598,
@@ -47,7 +47,7 @@ export const favorites = [
}, },
address: '2109 Matthews Ave, Bronx, NY 10462', address: '2109 Matthews Ave, Bronx, NY 10462',
}, },
{ office: {
name: 'Linwood Plaza', name: 'Linwood Plaza',
icon: 'dermatology', icon: 'dermatology',
coords: { coords: {
@@ -56,9 +56,9 @@ export const favorites = [
}, },
address: '158 Linwood Plaza, Fort Lee, NJ 07024', address: '158 Linwood Plaza, Fort Lee, NJ 07024',
}, },
],
}, },
{ },
strg: {
name: 'Man Mini Storage', name: 'Man Mini Storage',
icon: 'box', icon: 'box',
coords: { coords: {
@@ -67,8 +67,8 @@ export const favorites = [
}, },
address: '31-08 Northern Blvd, Long Island City, NY 11101', address: '31-08 Northern Blvd, Long Island City, NY 11101',
}, },
{ acme: {
name: 'Acmd', name: 'Acme',
icon: 'grocery', icon: 'grocery',
coords: { coords: {
lat: 40.90930366920829, lat: 40.90930366920829,
@@ -76,4 +76,4 @@ export const favorites = [
}, },
address: '31-08 Northern Blvd, Long Island City, NY 11101', address: '31-08 Northern Blvd, Long Island City, NY 11101',
}, },
]; };

View File

@@ -1,15 +1,15 @@
import { Utilities } from "@vue-leaflet/vue-leaflet"; import { Utilities } from '@vue-leaflet/vue-leaflet';
import type * as L from "leaflet"; import type L from 'leaflet';
import type { IRouter, IGeocoder, LineOptions } from "leaflet-routing-machine"; import type { IRouter, IGeocoder, LineOptions } from 'leaflet-routing-machine';
// Props typing // ---- Props typing ----
export interface RoutingControlProps { export interface RoutingControlProps {
waypoints: L.LatLng[]; waypoints: L.Routing.Waypoint[];
router?: IRouter; router?: IRouter;
plan?: any; // L.Routing.Plan (can refine if you typed it) plan?: L.Routing.Plan;
fitSelectedRoutes?: string | boolean; fitSelectedRoutes?: string | boolean;
lineOptions?: LineOptions; lineOptions?: LineOptions;
routeLine?: (...args: any[]) => any; routeLine?: (route: any) => L.Layer;
autoRoute?: boolean; autoRoute?: boolean;
routeWhileDragging?: boolean; routeWhileDragging?: boolean;
routeDragInterval?: number; routeDragInterval?: number;
@@ -19,30 +19,30 @@ export interface RoutingControlProps {
altLineOptions?: LineOptions; altLineOptions?: LineOptions;
} }
// Vue-style prop definitions (still needed for runtime) // ---- Vue-compatible prop definition ----
export const routingControlProps = { export const routingControlProps = {
waypoints: { waypoints: {
type: Array as () => L.LatLng[], type: Array as () => L.Routing.Waypoint[],
default: () => [], default: () => [],
}, },
router: { router: {
type: Object as () => IRouter, type: Object as () => IRouter | undefined,
default: undefined, default: undefined,
}, },
plan: { plan: {
type: Object as () => any, type: Object as () => L.Routing.Plan | undefined,
default: undefined, default: undefined,
}, },
fitSelectedRoutes: { fitSelectedRoutes: {
type: [String, Boolean] as () => string | boolean, type: [String, Boolean] as unknown as () => string | boolean,
default: "smart", default: 'smart',
}, },
lineOptions: { lineOptions: {
type: Object as () => LineOptions, type: Object as () => LineOptions | undefined,
default: undefined, default: undefined,
}, },
routeLine: { routeLine: {
type: Function as () => (...args: any[]) => any, type: Function as unknown as () => ((route: any) => L.Layer) | undefined,
default: undefined, default: undefined,
}, },
autoRoute: { autoRoute: {
@@ -59,7 +59,7 @@ export const routingControlProps = {
}, },
waypointMode: { waypointMode: {
type: String, type: String,
default: "connect", default: 'connect',
}, },
useZoomParameter: { useZoomParameter: {
type: Boolean, type: Boolean,
@@ -70,17 +70,17 @@ export const routingControlProps = {
default: false, default: false,
}, },
altLineOptions: { altLineOptions: {
type: Object as () => LineOptions, type: Object as () => LineOptions | undefined,
default: undefined, default: undefined,
}, },
}; };
// Setup function // ---- Setup function ----
export const setupRoutingControl = (props: RoutingControlProps) => { export const setupRoutingControl = (props: RoutingControlProps) => {
const options = Utilities.propsToLeafletOptions( const options = Utilities.propsToLeafletOptions(
props, props,
routingControlProps routingControlProps,
); ) as L.Routing.RoutingControlOptions;
return { return {
options, options,

View File

@@ -1,19 +1,36 @@
import { defineStore, acceptHMRUpdate } from 'pinia'; import { defineStore, acceptHMRUpdate } from 'pinia';
import { favorites } from 'constants/favorites'
interface State { interface State {
zoom: number zoom: number
center: [number, number] center: [number, number] | [null, null] | null
markerLatLng: [number, number] markerLatLng: [number, number] | [null, null] | null
qLocDrawer: boolean
} }
export const useLeafletStore = defineStore('leaflet', { export const useLeafletStore = defineStore('leaflet', {
state: (): State => { state: (): State => {
return { return {
zoom: 10, zoom: 10,
center: [40.71278, -74.00594], center: [favorites.home.coords.lat, favorites.home.coords.lng],
markerLatLng: [40.71278, -74.00594], markerLatLng: null,
qLocDrawer: false,
} }
}, },
actions: {
setCenter(lat: number, lng: number) {
this.center = [lat, lng];
},
setZoom(zoom: number) {
this.zoom = zoom;
},
setMarkerLatLng(lat: number, lng: number) {
this.markerLatLng = [lat, lng];
},
toggleQLocDrawer() {
this.qLocDrawer = !this.qLocDrawer
}
}
}) })
if (import.meta.hot) { if (import.meta.hot) {

View File

@@ -1,48 +1,61 @@
import { defineStore, acceptHMRUpdate } from 'pinia'; import { defineStore, acceptHMRUpdate } from 'pinia';
import { socket } from 'boot/socketio'; import { socket } from 'boot/socketio';
import { useQuasar } from 'quasar';
import iCloudCodeDialog from 'components/iCloudCodeDialog.vue';
import type { import type {
CurrentLocation, CurrentLocation,
NextLocation, NextLocation,
ErrorFull, ErrorFull,
SimulationControl,
SimulationCommands,
LocationQueue, LocationQueue,
SimulationControlResponse, SimulationControlResponse,
StatusUpdate StatusUpdate,
FindMyUpdate,
iCloudMonitorResponse,
} from 'components/models'; } from 'components/models';
const $q = useQuasar();
const debugLog: boolean = true;
export const useSocketioStore = defineStore('socketio', { export const useSocketioStore = defineStore('socketio', {
state: () => { state: () => {
return { return {
sockConnected: false as boolean, sockConnected: false as boolean,
socketID: null as string | null | undefined, socketID: null as string | null | undefined,
testMode: null as boolean | undefined | null,
deviceConnected: false as boolean, deviceConnected: false as boolean,
tunnelConnected: false as boolean, tunnelConnected: false as boolean,
simulationRunning: false as boolean | string, simulationRunning: false as boolean | string,
simulationState: null as string | null | undefined, simulationState: null as string | null | undefined,
simulationQueneLength: 0 as number, simulationQueueLength: 0 as number | null | undefined,
currentLocation: null as CurrentLocation | null, currentLocation: null as CurrentLocation | null | undefined,
nextLocation: null as NextLocation | null, nextLocation: null as NextLocation | null,
messageList: [''] as string[], messageList: [''] as string[],
errorList: [] as ErrorFull[], errorList: [] as ErrorFull[],
locationQueue: [] as LocationQueue[], locationQueueData: {} as LocationQueue,
leafLetZoom: 10 as number, locationQueueOrder: [] as string[],
leafletZoom: 10 as number,
icloudMonitor: false as boolean,
findMyUpdate: null as FindMyUpdate | null | undefined,
}; };
}, },
getters: { getters: {
sockState: (state) => state.sockConnected, sockState: (state) => state.sockConnected,
deviceState: (state) => state.deviceConnected, deviceState: (state) => state.deviceConnected,
markerLatLng: (state) => { lMarkerLatLng: (state): [number, number] => {
return [state.currentLocation.latitude, state.currentLocation.longitude] if (
state.currentLocation == null ||
!state.currentLocation.latitude ||
!state.currentLocation.longitude
) {
return [0, 0];
}
return [state.currentLocation.latitude, state.currentLocation.longitude];
}, },
center(): [number, number] { lCenter(): [number, number] {
return this.leafletCurrentMarker return this.lMarkerLatLng;
}, },
zoom: (state) => state.leafletZoom, lZoom: (state): number => state.leafletZoom,
}, },
actions: { actions: {
setSockStatus() { setSockStatus() {
@@ -54,9 +67,13 @@ export const useSocketioStore = defineStore('socketio', {
socket.on('connect', () => { socket.on('connect', () => {
this.setSockStatus(); this.setSockStatus();
socket.emit('message', 'Hello from client', (e: boolean) => { socket.emit('message', 'Hello from client', (e: boolean) => {
if (debugLog) {
console.log('Message delivered: ' + e); console.log('Message delivered: ' + e);
}
}); });
if (debugLog) {
console.log('Connected to server'); console.log('Connected to server');
}
}); });
socket.on('disconnect', () => { socket.on('disconnect', () => {
@@ -67,31 +84,79 @@ export const useSocketioStore = defineStore('socketio', {
socket.on('message', (e: string) => { socket.on('message', (e: string) => {
this.setSockStatus(); this.setSockStatus();
this.messageList.push(e); this.messageList.push(e);
if (debugLog) {
console.log('Websock message received!'); console.log('Websock message received!');
}
}); });
socket.on('error', (data: ErrorFull) => { socket.on('error', (data: ErrorFull) => {
this.setSockStatus(); this.setSockStatus();
const errorFull = { type: data.type, error: data.error }; const errorFull = { type: data.type, error: data.error };
this.errorList.push(errorFull); this.errorList.push(errorFull);
console.error('Error Received: ', data);
}); });
socket.on('status', (data: StatusUpdate): void => { socket.on('status', (data: StatusUpdate): void => {
console.log("StatusUpdate received: ", data); if (debugLog) {
this.simulationRunning = data.simulation_active; console.log('StatusUpdate received: ', data);
this.simulationState = data.queue_state; }
this.simulationQueneLength = data.quene_length; this.digestUpdate(data);
this.tunnelConnected = !!data.tunnel; });
this.currentLocation = { loc_id: data.loc_id, latitude: data.latitude, longitude: data.longitude, next_move: data.next_mode };
socket.on('fmf_update', (data: FindMyUpdate): void => {
if (debugLog) {
console.log('event: fmf_update received: ', data);
}
this.findMyUpdate = data;
});
socket.on('icloud_2fa_request', (callback) => {
if (debugLog) {
console.log('iCloud 2FA Request');
}
$q.dialog({
component: iCloudCodeDialog,
})
.onOk((code: number) => {
if (callback && typeof callback === 'function') {
callback(code);
}
})
.onCancel(() => {
if (debugLog) {
console.log('Dialog cancelled');
}
})
.onDismiss(() => {
if (debugLog) {
console.log('Dialog dismissed');
}
});
}); });
socket.onAny((eventName, ...args) => { socket.onAny((eventName, ...args) => {
this.setSockStatus(); console.log('Event received: ', eventName, ' Args: ', args, '');
console.log(`Received event: ${eventName}`, args); /* if (serverToClientKnownEvents.includes(eventName)) {
console.log('Known event received: ', eventName, ' Args: ', args, '');
} else {
console.log(
'Known events: ',
serverToClientKnownEvents,
' Received Event: ',
eventName,
' Args: ',
args,
'',
);
console.log(`Received UNKNOWN Event: ${eventName}`, args);
}
*/
}); });
}, },
connect() { connect() {
if (debugLog) {
console.log('Connecting to server...'); console.log('Connecting to server...');
}
socket.connect(); socket.connect();
this.setSockStatus(); this.setSockStatus();
}, },
@@ -108,63 +173,202 @@ export const useSocketioStore = defineStore('socketio', {
} }
this.setSockStatus(); this.setSockStatus();
}, },
setSimulationRunning(isRunning: boolean, states: string ): void { setSimulationRunning(isRunning: boolean, states: string): void {
this.setSockStatus(); this.setSockStatus();
this.simulationState = states; this.simulationState = states;
this.simulationRunning = isRunning; this.simulationRunning = isRunning;
}, },
simulationControl(command: SimulationCommands, delay?: number, latitude?: number | null, longitude?: number | null): string | never { icloudMonitorControl(command: string) {
let fnctRtn: { sts: string; msg?: string | undefined } = { sts: '', msg: '' };
switch (command) {
case 'start':
if (debugLog) {
console.log('socketStore: got command: icloudMonitor start');
}
if (this.icloudMonitor) {
fnctRtn = { sts: 'error', msg: 'iCloud Monitor is already running' };
throw new Error('iCloud Monitor is already running');
}
if (debugLog) {
console.log('Emitting icloud_monitor_control: start');
}
socket.emit(
'icloud_monitor_control',
{ command: 'start' },
(response: iCloudMonitorResponse) => {
fnctRtn.sts = response.status;
if (response.status == 'error') {
if (response.message) {
if (debugLog) {
console.log(response.message);
}
fnctRtn.msg = response.message;
} else {
fnctRtn.msg = 'Error';
}
throw new Error(fnctRtn.msg);
} else {
fnctRtn = { sts: 'OK', msg: 'iCloud Monitor: ' + response.status };
this.icloudMonitor = true;
}
});
break;
case 'stop':
if (debugLog) {
console.log('socketStore: got command: icloudMonitor stop');
}
if (!this.icloudMonitor) {
fnctRtn = { sts: 'error', msg: 'iCloud Monitor is not running' };
throw new Error('iCloud Monitor is not running');
}
if (debugLog) {
console.log('Emitting icloud_monitor_control: stop');
}
socket.emit(
'icloud_monitor_control',
{ command: 'stop' },
(response: iCloudMonitorResponse) => {
fnctRtn.sts = response.status;
if (response.status == 'error') {
if (response.message) {
if (debugLog) {
console.log(response.message);
}
fnctRtn.msg = response.message;
} else {
fnctRtn.msg = 'Error';
}
throw new Error(fnctRtn.msg);
} else {
fnctRtn = { sts: 'OK', msg: 'iCloud Monitor: ' + response.status };
this.icloudMonitor = false;
}
});
break;
case 'status':
if (debugLog) {
console.log('socketStore: got command: icloudMonitor status');
}
if (debugLog) {
console.log('Emitting icloud_monitor_control: status');
}
socket.emit(
'icloud_monitor_control',
{ command: 'status' },
(response: iCloudMonitorResponse) => {
fnctRtn.sts = response.status;
if (response.status == 'error') {
if (response.message) {
if (debugLog) {
console.log(response.message);
}
fnctRtn.msg = response.message;
} else {
fnctRtn.msg = 'Error';
}
throw new Error(fnctRtn.msg);
} else {
fnctRtn = {
sts: 'OK',
msg:
'iCloud Monitor Enabled: ' +
response.icloud_monitor_enabled +
'iCloud Monitor Running: ' +
response.icloud_monitor_running
};
}
this.icloudMonitor = !!(response.icloud_monitor_enabled && response.icloud_monitor_running);
});
break;
default:
fnctRtn = { sts: 'error', msg: 'Invalid command' };
throw new Error('Invalid command');
}
return fnctRtn;
},
simulationControl(
command: string,
delay?: number,
latitude?: number | null,
longitude?: number | null,
) {
let fnctRtn: { sts: string; msg?: string | undefined } = { sts: '', msg: '' };
this.setSockStatus(); this.setSockStatus();
switch (command) { switch (command) {
case 'start': case 'start':
console.log("socketStore: got command: start"); if (debugLog) {
if (this.simulationRunning || this.simulationState == "RUNNING" || this.simulationState == "PAUSED") { console.log('socketStore: got command: start');
}
if (
this.simulationRunning ||
this.simulationState == 'RUNNING' ||
this.simulationState == 'PAUSED'
) {
fnctRtn = { sts: 'error', msg: 'Simulation is already running' };
throw new Error('Simulation is already running' + this.simulationState); throw new Error('Simulation is already running' + this.simulationState);
} }
console.log("Emmitting simulation_control: start") if (debugLog) {
socket.emit('simulation_control', { command: 'start', delay: 0, latitude: null, longitude: null}, (response: SimulationControlResponse) => { console.log('Emitting simulation_control: start');
if (response.status == "error") { }
socket.emit(
'simulation_control',
{ command: 'start', delay: 0, latitude: null, longitude: null },
(response: SimulationControlResponse) => {
if (response.status == 'error') {
fnctRtn = { sts: 'error', msg: response.message?.toString() };
throw new Error(response.message); throw new Error(response.message);
} else { } else {
fnctRtn = { sts: 'OK', msg: response.message?.toString() };
this.simulationState = response.status; this.simulationState = response.status;
if (debugLog) {
console.log(response.message); console.log(response.message);
return response.message;
} }
}); // return response.message;
}
},
);
break; break;
case 'pause': case 'pause':
if (this.simulationState !== "RUNNING" ) { if (this.simulationState !== 'RUNNING') {
throw new Error('Simulation is not running'); throw new Error('Simulation is not running');
} }
socket.emit('simulation_control', { command: 'pause' }, (response: SimulationControlResponse) => { socket.emit(
if (response.status === "error") { 'simulation_control',
{ command: 'pause' },
(response: SimulationControlResponse) => {
if (response.status === 'error') {
throw new Error(response.message); throw new Error(response.message);
} else { } else {
this.simulationState = response.status; this.simulationState = response.status;
if (debugLog) {
console.log(response.message); console.log(response.message);
}
return response.message; return response.message;
} }
}); },
);
break; break;
case 'resume': case 'resume':
if (this.simulationState !== "PAUSED") { if (this.simulationState !== 'PAUSED') {
throw new Error('Simulation is not paused'); throw new Error('Simulation is not paused');
} }
socket.emit('simulation_control', { command: 'resume' }, (response) => { socket.emit('simulation_control', { command: 'resume' }, (response) => {
if (response.status == "error") { if (response.status == 'error') {
throw new Error(response.message) throw new Error(response.message);
} else { } else {
this.simulationState = response.status; this.simulationState = response.status;
console.log(response.message) if (debugLog) {
return response.message console.log(response.message);
}
return response.message;
} }
}); });
break; break;
case 'clear': case 'clear':
if (this.simulationQueueLength== 0) { if (this.simulationQueueLength == 0) {
throw new Error('Simulation queue is empty'); throw new Error('Simulation queue is empty');
} }
if (this.simulationState == "STOPPED " || !this.simulationRunning) { if (this.simulationState == 'STOPPED ' || !this.simulationRunning) {
throw new Error('Simulation is not running'); throw new Error('Simulation is not running');
} }
socket.emit('simulation_control', { command: 'clear' }, (response) => { socket.emit('simulation_control', { command: 'clear' }, (response) => {
@@ -172,13 +376,15 @@ export const useSocketioStore = defineStore('socketio', {
throw new Error(response.message); throw new Error(response.message);
} else { } else {
this.simulationState = response.status; this.simulationState = response.status;
if (debugLog) {
console.log(response.message); console.log(response.message);
}
return response.message; return response.message;
} }
}); });
break; break;
case 'end': case 'end':
if (this.simulationState == "ENDED" || !this.simulationRunning) { if (this.simulationState == 'ENDED' || !this.simulationRunning) {
throw new Error('Simulation is already ended'); throw new Error('Simulation is already ended');
} }
socket.emit('simulation_control', { command: 'end' }, (response) => { socket.emit('simulation_control', { command: 'end' }, (response) => {
@@ -186,24 +392,31 @@ export const useSocketioStore = defineStore('socketio', {
throw new Error(response.message); throw new Error(response.message);
} else { } else {
this.simulationState = response.status; this.simulationState = response.status;
if (debugLog) {
console.log(response.message); console.log(response.message);
}
return response.message; return response.message;
} }
}); });
break; break;
case 'add': case 'add':
if (this.simulationState == "ENDED" || !this.simulationRunning) { // if (this.simulationState == 'ENDED' || !this.simulationRunning) {
throw new Error('Simulation is not running'); // throw new Error('Simulation is not running');
} // }
if (!latitude || !longitude) { if (!latitude || !longitude) {
throw new Error ("latitude or longitude not set"); throw new Error('latitude or longitude not set');
} }
socket.emit('simulation_control',{ command: 'add', latitude: latitude, longitude: longitude, delay: delay }, (response) => { socket.emit(
if (response.status == "error") { 'simulation_control',
throw new Error(response.message) { command: 'add', latitude: latitude, longitude: longitude, delay: delay },
(response) => {
if (response.status == 'error') {
throw new Error(response.message);
} else { } else {
this.simulationState = response.status; this.simulationState = response.status;
console.log("response from simulate_control_add: ", response); if (debugLog) {
console.log('response from simulate_control_add: ', response);
}
const locMrk = { const locMrk = {
[response.loc_id]: { [response.loc_id]: {
loc_id: response.loc_id, loc_id: response.loc_id,
@@ -213,14 +426,39 @@ export const useSocketioStore = defineStore('socketio', {
start_time: response.start_time, start_time: response.start_time,
}, },
}; };
this.locationQueue.push(locMrk);
return response.message; return response.message;
} }
}); },
);
break; break;
default: default:
fnctRtn = { sts: 'error', msg: 'Invalid command' };
throw new Error('Invalid command'); throw new Error('Invalid command');
} }
return fnctRtn;
},
requestUpdate(): void {
socket.emit('request_update', (response: StatusUpdate) => {
this.digestUpdate(response);
});
},
digestUpdate(data: StatusUpdate): void {
this.deviceConnected = !!(data.udid && data.tunnel);
this.testMode = data.test_mode;
this.simulationRunning = data.simulation_queue.active;
this.simulationState = data.simulation_queue.state;
this.simulationQueueLength = data.simulation_queue.order.length;
this.tunnelConnected = !!data.tunnel;
this.icloudMonitor = data.icloud.monitor_enabled;
this.currentLocation = {
loc_id: data.current_location.loc_id,
latitude: data.current_location.latitude,
longitude: data.current_location.longitude,
next_move: data.next_move,
};
this.locationQueueData = data.simulation_queue.data;
this.locationQueueOrder = data.simulation_queue.order;
this.findMyUpdate = data.fmf_location;
}, },
setDeviceState(state: boolean) { setDeviceState(state: boolean) {
this.deviceConnected = state; this.deviceConnected = state;