Skip to content

Commit

Permalink
test
Browse files Browse the repository at this point in the history
  • Loading branch information
RaulTrombin committed Dec 20, 2024
1 parent 05d6424 commit a9eb084
Show file tree
Hide file tree
Showing 13 changed files with 1,954 additions and 666 deletions.
Binary file modified ping-viewer-next-frontend/bun.lockb
Binary file not shown.
18 changes: 9 additions & 9 deletions ping-viewer-next-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
"core-js": "^3.39.0",
"roboto-fontface": "^0.10.0",
"vue": "3.5.8",
"vuetify": "^3.7.4"
"vuetify": "^3.7.6"
},
"devDependencies": {
"@biomejs/biome": "1.9.2",
"@vitejs/plugin-vue": "^5.1.5",
"@vitejs/plugin-vue": "^5.2.1",
"autoprefixer": "^10.4.20",
"eslint": "^8.57.1",
"eslint-config-standard": "^17.1.0",
Expand All @@ -25,18 +25,18 @@
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.6.0",
"eslint-plugin-vue": "^9.31.0",
"pinia": "^2.2.6",
"eslint-plugin-vue": "^9.32.0",
"pinia": "^2.3.0",
"postcss": "^8.4.49",
"sass": "1.77.6",
"tailwindcss": "^3.4.14",
"tailwindcss": "^3.4.17",
"unplugin-auto-import": "^0.17.8",
"unplugin-fonts": "^1.1.1",
"unplugin-vue-components": "^0.27.4",
"unplugin-vue-router": "^0.10.8",
"unplugin-fonts": "^1.3.1",
"unplugin-vue-components": "^0.27.5",
"unplugin-vue-router": "^0.10.9",
"vite": "^5.4.11",
"vite-plugin-vue-layouts": "^0.11.0",
"vite-plugin-vuetify": "^2.0.4",
"vue-router": "^4.4.5"
"vue-router": "^4.5.0"
}
}
1,096 changes: 909 additions & 187 deletions ping-viewer-next-frontend/src/App.vue

Large diffs are not rendered by default.

345 changes: 345 additions & 0 deletions ping-viewer-next-frontend/src/components/ConnectionManager.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,345 @@
<template>
<div class="connection-manager">
<div class="config-menu" :class="{ 'glass disable-hover': glass }" v-show="isOpen">
<div :class="['menu-content', { 'glass-inner disable-hover': glass }]">
<!-- Header Section -->
<div class="menu-header d-flex justify-space-between align-center mb-4">
<div class="text-h6">Device Management</div>
<div class="d-flex gap-2">
<v-btn color="primary" size="small" @click="autoCreateDevices" :loading="isAutoCreating">
<v-icon start>mdi-plus-network</v-icon>
Auto Create
</v-btn>
<v-btn color="primary" size="small" @click="refreshDevices" :loading="isRefreshing">
<v-icon>mdi-refresh</v-icon>
</v-btn>
</div>
</div>

<!-- Device List -->
<div class="device-list mb-4">
<div v-if="isLoading" class="d-flex justify-center my-4">
<v-progress-circular indeterminate />
</div>

<div v-else-if="devices.length === 0" class="text-center pa-4 text-medium-emphasis">
<v-icon size="48" class="mb-2">mdi-devices</v-icon>
<div>No devices found.</div>
<div class="text-caption">Try clicking 'Auto Create' to discover devices.</div>
</div>

<v-list v-else :class="{ 'glass-inner': glass }" density="compact">
<v-list-item v-for="device in devices" :key="device.id" :value="device" class="mb-2"
:class="{ 'glass-inner': glass }">
<template v-slot:prepend>
<v-icon :icon="device.device_type === 'Ping360' ? 'mdi-radar' : 'mdi-altimeter'" />
</template>

<v-list-item-title>{{ device.device_type }}</v-list-item-title>
<v-list-item-subtitle class="text-truncate">{{ device.id }}</v-list-item-subtitle>

<template v-slot:append>
<div class="d-flex align-center">
<v-chip :color="getStatusColor(device.status)" size="small" class="mr-2">
{{ device.status }}
</v-chip>
<v-btn color="primary" size="small" @click="selectDevice(device)">
Open
</v-btn>
</div>
</template>
</v-list-item>
</v-list>
</div>

<!-- Manual Creation Section -->
<v-expand-transition>
<div v-if="showManualCreate" :class="{ 'glass-inner': glass }" class="pa-4 rounded">
<v-form @submit.prevent="createDevice">
<v-select v-model="newDevice.device_selection" :items="deviceTypes" label="Device Type" class="mb-4" />
<v-select v-model="newDevice.connectionType" :items="connectionTypes" label="Connection Type"
class="mb-4" />

<template v-if="newDevice.connectionType === 'UdpStream'">
<v-text-field v-model="newDevice.udp.ip" label="IP Address" class="mb-4"
:rules="[v => !!v || 'IP is required']" />
<v-text-field v-model.number="newDevice.udp.port" type="number" label="Port" class="mb-4"
:rules="[v => !!v || 'Port is required']" />
</template>

<template v-else-if="newDevice.connectionType === 'SerialStream'">
<v-text-field v-model="newDevice.serial.path" label="Serial Path" class="mb-4"
:rules="[v => !!v || 'Path is required']" />
<v-text-field v-model.number="newDevice.serial.baudrate" type="number" label="Baudrate" class="mb-4"
:rules="[v => !!v || 'Baudrate is required']" />
</template>

<div class="d-flex justify-end gap-2">
<v-btn color="error" variant="text" @click="showManualCreate = false">Cancel</v-btn>
<v-btn color="primary" :loading="isCreating" type="submit">Create</v-btn>
</div>
</v-form>
</div>
</v-expand-transition>

<!-- Footer Actions -->
<div class="menu-actions mt-4">
<v-btn block :class="{ 'glass-inner': glass }" @click="showManualCreate = !showManualCreate">
<v-icon start>{{ showManualCreate ? 'mdi-minus' : 'mdi-plus' }}</v-icon>
{{ showManualCreate ? 'Cancel Manual Creation' : 'Manual Create' }}
</v-btn>
</div>
</div>
</div>
</div>
</template>

<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
const props = defineProps({
serverUrl: {
type: String,
required: true,
},
glass: {
type: Boolean,
default: false,
},
isOpen: {
type: Boolean,
required: true,
},
});
const emit = defineEmits(['update:isOpen', 'select-device']);
// State
const devices = ref([]);
const isLoading = ref(false);
const isRefreshing = ref(false);
const isAutoCreating = ref(false);
const isCreating = ref(false);
const showManualCreate = ref(false);
const error = ref(null);
// Form state
const newDevice = ref({
device_selection: 'Auto',
connectionType: 'UdpStream',
udp: {
ip: 'blueos.local',
port: 12345,
},
serial: {
path: '/dev/ttyUSB0',
baudrate: 2500000,
},
});
// Constants
const deviceTypes = [
{ title: 'Auto Detect', value: 'Auto' },
{ title: 'Ping1D', value: 'Ping1D' },
{ title: 'Ping360', value: 'Ping360' },
];
const connectionTypes = [
{ title: 'UDP', value: 'UdpStream' },
{ title: 'Serial', value: 'SerialStream' },
];
// Utility functions
const getStatusColor = (status) => {
switch (status) {
case 'ContinuousMode':
return 'success';
case 'Running':
return 'info';
case 'Error':
return 'error';
default:
return 'warning';
}
};
// API functions
const fetchDevices = async () => {
try {
const response = await fetch(`${props.serverUrl}/device_manager/request`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
command: 'List',
module: 'DeviceManager',
}),
});
if (!response.ok) throw new Error('Failed to fetch devices');
const data = await response.json();
devices.value = data.DeviceInfo || [];
error.value = null;
} catch (err) {
console.error('Error fetching devices:', err);
error.value = `Failed to fetch devices: ${err.message}`;
}
};
const refreshDevices = async () => {
isRefreshing.value = true;
await fetchDevices();
isRefreshing.value = false;
};
const autoCreateDevices = async () => {
isAutoCreating.value = true;
error.value = null;
try {
const response = await fetch(`${props.serverUrl}/device_manager/request`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
command: 'AutoCreate',
module: 'DeviceManager',
}),
});
if (!response.ok) throw new Error('Failed to auto-create devices');
await refreshDevices();
} catch (err) {
console.error('Error auto-creating devices:', err);
error.value = `Failed to auto-create devices: ${err.message}`;
} finally {
isAutoCreating.value = false;
}
};
const createDevice = async () => {
isCreating.value = true;
error.value = null;
try {
const source =
newDevice.value.connectionType === 'UdpStream'
? {
UdpStream: {
ip: newDevice.value.udp.ip,
port: newDevice.value.udp.port,
},
}
: {
SerialStream: {
path: newDevice.value.serial.path,
baudrate: newDevice.value.serial.baudrate,
},
};
const response = await fetch(`${props.serverUrl}/device_manager/request`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
command: 'Create',
module: 'DeviceManager',
payload: {
device_selection: newDevice.value.device_selection,
source,
},
}),
});
if (!response.ok) throw new Error('Failed to create device');
await refreshDevices();
showManualCreate.value = false;
} catch (err) {
console.error('Error creating device:', err);
error.value = `Failed to create device: ${err.message}`;
} finally {
isCreating.value = false;
}
};
const selectDevice = (device) => {
emit('select-device', device);
emit('update:isOpen', false);
};
// Lifecycle hooks
let refreshInterval;
onMounted(() => {
isLoading.value = true;
fetchDevices().finally(() => {
isLoading.value = false;
});
refreshInterval = setInterval(fetchDevices, 5000);
});
onUnmounted(() => {
if (refreshInterval) {
clearInterval(refreshInterval);
}
});
</script>

<style scoped>
.connection-manager {
transition: all 0.3s ease;
transform-origin: top left;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
/* Responsive adjustments */
@media (max-width: 600px) {
.connection-menu-wrapper .config-menu {
width: calc(100vw - var(--button-size) - var(--button-gap) * 2);
max-width: 400px;
}
}
.config-menu {
width: 400px;
max-width: calc(100vw - var(--button-size));
border-radius: var(--border-radius);
overflow: hidden;
background: rgb(var(--v-theme-background));
}
.menu-content {
padding: 1rem;
}
.device-list {
max-height: 50vh;
overflow-y: auto;
}
/* :deep(.v-list) {
background: transparent;
} */
.menu-actions {
border-top: 1px solid rgba(var(--v-border-color), 0.12);
padding-top: 1rem;
}
</style>
Loading

0 comments on commit a9eb084

Please sign in to comment.