Compare commits
3 commits
main
...
FrontendAd
Author | SHA1 | Date | |
---|---|---|---|
2a4e032c99 | |||
Mikail Killi | 644dd6394e | ||
ff2d6c82c9 |
|
@ -39,7 +39,7 @@ async fn main() -> std::io::Result<()> {
|
||||||
let cors = if cfg!(debug_assertions) {
|
let cors = if cfg!(debug_assertions) {
|
||||||
actix_cors::Cors::permissive()
|
actix_cors::Cors::permissive()
|
||||||
} else {
|
} else {
|
||||||
actix_cors::Cors::default()
|
actix_cors::Cors::permissive() //change to default on push
|
||||||
};
|
};
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(cors)
|
.wrap(cors)
|
||||||
|
|
|
@ -43,7 +43,7 @@ docker image remove apfelnetzwerk-backend
|
||||||
docker ps
|
docker ps
|
||||||
```
|
```
|
||||||
|
|
||||||
3. attach a shell to the running postgres docker container
|
3. attach a shell to the running backend docker container
|
||||||
```bash
|
```bash
|
||||||
docker exec -it <it/name> /bin/bash
|
docker exec -it <it/name> /bin/bash
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { store } from "@/store";
|
|
||||||
let axiosInstance = axios.create({
|
let axiosInstance = axios.create({
|
||||||
baseURL: import.meta.env.VITE_BACKEND_URL,
|
baseURL: import.meta.env.VITE_BACKEND_URL,
|
||||||
});
|
});
|
||||||
axiosInstance.interceptors.request.use((config) => {
|
axiosInstance.interceptors.request.use((config) => {
|
||||||
config.headers.Authorization = `Bearer ${store.token}`;
|
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,29 +3,9 @@
|
||||||
<v-toolbar color="main" dark prominent>
|
<v-toolbar color="main" dark prominent>
|
||||||
<img src="../assets/turbologo.svg" alt="logo" class="logo" width="75" />
|
<img src="../assets/turbologo.svg" alt="logo" class="logo" width="75" />
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<!-- Users, Groups, Licenses -->
|
|
||||||
<div>
|
|
||||||
<!-- USER MANAGEMENT -->
|
|
||||||
<template v-if="!user" />
|
|
||||||
<UsersDialog v-else-if="user.admin" />
|
|
||||||
<UsersEditActions v-else :admin-menu="false" :user="user!" />
|
|
||||||
</div>
|
|
||||||
<!-- Search -->
|
|
||||||
<v-text-field
|
|
||||||
class="compact-form mr-2"
|
|
||||||
label="Search"
|
|
||||||
variant="solo"
|
|
||||||
density="compact"
|
|
||||||
prepend-inner-icon="mdi-magnify"
|
|
||||||
hide-details
|
|
||||||
single-line
|
|
||||||
clearable
|
|
||||||
v-model="search"
|
|
||||||
rounded="pill"
|
|
||||||
></v-text-field>
|
|
||||||
|
|
||||||
<!-- Logout -->
|
<!-- Logout -->
|
||||||
<v-btn icon variant="outlined" @click="handlelogout">
|
<v-btn @click="handlelogout">
|
||||||
<LogOut />
|
<LogOut />
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
|
@ -46,13 +26,7 @@ import { axiosInstance } from "@/client";
|
||||||
import { User } from "@/types";
|
import { User } from "@/types";
|
||||||
import { search } from "@/store";
|
import { search } from "@/store";
|
||||||
|
|
||||||
const { data: user } = useQuery({
|
|
||||||
queryKey: ["user"],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await axiosInstance.get<User>("/users/me");
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function handlelogout() {
|
function handlelogout() {
|
||||||
store.setToken(null);
|
store.setToken(null);
|
||||||
|
|
|
@ -27,11 +27,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
<span class="mr-2">Battery:</span>
|
<span class="mr-2">Battery:</span>
|
||||||
<span>{{ node.battery }}%</span>
|
<span>{{ node.batteryCurrent }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="d-flex align-items-center mb-2">
|
||||||
<span class="mr-2">Runtime:</span>
|
<span class="mr-2">Runtime:</span>
|
||||||
<span>{{ node.runtime }} hours</span>
|
<span>{{ node.uptime }} hours</span>
|
||||||
</div>
|
</div>
|
||||||
</v-expansion-panel-text>
|
</v-expansion-panel-text>
|
||||||
</v-expansion-panel>
|
</v-expansion-panel>
|
||||||
|
|
|
@ -9,54 +9,28 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import HeaderBar from "./HeaderBar.vue";
|
import HeaderBar from "./HeaderBar.vue";
|
||||||
import CategoryContainer from "./CategoryContainer.vue";
|
|
||||||
import { useQuery } from "@tanstack/vue-query";
|
import { useQuery } from "@tanstack/vue-query";
|
||||||
import { axiosInstance } from "@/client";
|
import { axiosInstance } from "@/client";
|
||||||
import { LicenseGroup, License } from "@/types";
|
import { NodeGroup } from "@/types";
|
||||||
import { search, key } from "@/store";
|
|
||||||
import MiniSearch from "minisearch";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
import TableCategory from "./TableCategory.vue";
|
import TableCategory from "./TableCategory.vue";
|
||||||
|
|
||||||
const { isPending, isError, data, error } = useQuery({
|
|
||||||
queryKey: ["licenses"],
|
const { data } = useQuery({
|
||||||
|
queryKey: ["nodes"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const res = await axiosInstance.get<LicenseGroup[]>("/licenses");
|
const res = await axiosInstance.get<NodeGroup[]>("/nodes");
|
||||||
console.log(res.data);
|
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
refetchInterval: 60 * 1000,
|
select: (data) => {
|
||||||
|
return data.map((group) => {
|
||||||
|
return {
|
||||||
|
title: group.name,
|
||||||
|
value: group.nodes,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchEngine = computed(() => {
|
|
||||||
let minisearch = new MiniSearch({
|
|
||||||
fields: ["name", "description", "id"],
|
|
||||||
searchOptions: {
|
|
||||||
boost: { name: 2 },
|
|
||||||
prefix: true,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
let licenses: License[] = [];
|
|
||||||
data.value?.forEach((group) => {
|
|
||||||
group.licenses.forEach((license) => licenses.push(license));
|
|
||||||
});
|
|
||||||
console.log(licenses);
|
|
||||||
minisearch.addAll(licenses);
|
|
||||||
return minisearch;
|
|
||||||
});
|
|
||||||
|
|
||||||
const searching = computed(() => search.value !== "");
|
|
||||||
|
|
||||||
const visibleIds = computed(() => {
|
|
||||||
return searchEngine.value.search(search.value).map((searchResult) => {
|
|
||||||
return searchResult.id;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
provide(key, {
|
|
||||||
visibleIds,
|
|
||||||
searching,
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th>Node/Name</th>
|
<th>Node/Name</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Latitude</th> <!-- Separate column for Latitude -->
|
<th>Latitude</th>
|
||||||
<th>Longitude</th> <!-- Separate column for Longitude -->
|
<th>Longitude</th>
|
||||||
<th>Battery</th>
|
<th>Battery</th>
|
||||||
<th>Gemessene - Temperatur</th>
|
<th>Gemessene - Temperatur</th>
|
||||||
<th>Laufzeit</th>
|
<th>Laufzeit</th>
|
||||||
|
@ -20,9 +20,15 @@
|
||||||
{{ node.status }}
|
{{ node.status }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ node.position.lat }}</td>
|
<td
|
||||||
<td>{{ node.position.lng }}</td>
|
contenteditable="true"
|
||||||
<td>{{ node.battery }}%</td>
|
@blur="validateAndUpdateLatLng(node, 'lat', $event)"
|
||||||
|
>{{ node.position.lat }}</td>
|
||||||
|
<td
|
||||||
|
contenteditable="true"
|
||||||
|
@blur="validateAndUpdateLatLng(node, 'lng', $event)"
|
||||||
|
>{{ node.position.lng }}</td>
|
||||||
|
<td>{{ calculateBatteryPercentage(node.batteryVoltage, node.minVoltage, node.maxVoltage) }}%</td>
|
||||||
<td>{{ node.temperature }}°C</td>
|
<td>{{ node.temperature }}°C</td>
|
||||||
<td>{{ node.runtime }}</td>
|
<td>{{ node.runtime }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -32,6 +38,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
//folgende Mockdaten rausnehmen und mit axios requests abfragen
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -40,7 +49,9 @@ export default {
|
||||||
name: "XXXX-XXXX-XXXX-XXXX",
|
name: "XXXX-XXXX-XXXX-XXXX",
|
||||||
status: "ONLINE",
|
status: "ONLINE",
|
||||||
position: { lat: 40.7128, lng: 74.0012 },
|
position: { lat: 40.7128, lng: 74.0012 },
|
||||||
battery: 98,
|
batteryVoltage: 3.9,
|
||||||
|
minVoltage: 3.0,
|
||||||
|
maxVoltage: 4.2,
|
||||||
temperature: 100,
|
temperature: 100,
|
||||||
runtime: "12h 30m 12s",
|
runtime: "12h 30m 12s",
|
||||||
},
|
},
|
||||||
|
@ -48,7 +59,9 @@ export default {
|
||||||
name: "XXXX-XXXX-XXXX-XXXX",
|
name: "XXXX-XXXX-XXXX-XXXX",
|
||||||
status: "OFFLINE",
|
status: "OFFLINE",
|
||||||
position: { lat: 40.7128, lng: 74.0012 },
|
position: { lat: 40.7128, lng: 74.0012 },
|
||||||
battery: 19,
|
batteryVoltage: 3.2,
|
||||||
|
minVoltage: 3.0,
|
||||||
|
maxVoltage: 4.2,
|
||||||
temperature: 100,
|
temperature: 100,
|
||||||
runtime: "30m",
|
runtime: "30m",
|
||||||
},
|
},
|
||||||
|
@ -56,7 +69,9 @@ export default {
|
||||||
name: "Localnode-3",
|
name: "Localnode-3",
|
||||||
status: "ONLINE",
|
status: "ONLINE",
|
||||||
position: { lat: 40.7128, lng: 74.0012 },
|
position: { lat: 40.7128, lng: 74.0012 },
|
||||||
battery: 66,
|
batteryVoltage: 3.7,
|
||||||
|
minVoltage: 3.0,
|
||||||
|
maxVoltage: 4.2,
|
||||||
temperature: 100,
|
temperature: 100,
|
||||||
runtime: "12h 30m 12s",
|
runtime: "12h 30m 12s",
|
||||||
},
|
},
|
||||||
|
@ -64,7 +79,9 @@ export default {
|
||||||
name: "XXXX-XXXX-XXXX-XXXX",
|
name: "XXXX-XXXX-XXXX-XXXX",
|
||||||
status: "ONLINE",
|
status: "ONLINE",
|
||||||
position: { lat: 40.7128, lng: 74.0012 },
|
position: { lat: 40.7128, lng: 74.0012 },
|
||||||
battery: 79,
|
batteryVoltage: 3.8,
|
||||||
|
minVoltage: 3.0,
|
||||||
|
maxVoltage: 4.2,
|
||||||
temperature: 100,
|
temperature: 100,
|
||||||
runtime: "12h 30m 12s",
|
runtime: "12h 30m 12s",
|
||||||
},
|
},
|
||||||
|
@ -72,7 +89,9 @@ export default {
|
||||||
name: "XXXX-XXXX-XXXX-XXXX",
|
name: "XXXX-XXXX-XXXX-XXXX",
|
||||||
status: "ONLINE",
|
status: "ONLINE",
|
||||||
position: { lat: 40.7128, lng: 74.0012 },
|
position: { lat: 40.7128, lng: 74.0012 },
|
||||||
battery: 10,
|
batteryVoltage: 3.1,
|
||||||
|
minVoltage: 3.0,
|
||||||
|
maxVoltage: 4.2,
|
||||||
temperature: 100,
|
temperature: 100,
|
||||||
runtime: "12h 30m 12s",
|
runtime: "12h 30m 12s",
|
||||||
},
|
},
|
||||||
|
@ -80,7 +99,9 @@ export default {
|
||||||
name: "XXXX-XXXX-XXXX-XXXX",
|
name: "XXXX-XXXX-XXXX-XXXX",
|
||||||
status: "OFFLINE",
|
status: "OFFLINE",
|
||||||
position: { lat: 40.7128, lng: 74.0012 },
|
position: { lat: 40.7128, lng: 74.0012 },
|
||||||
battery: 0,
|
batteryVoltage: 3.0,
|
||||||
|
minVoltage: 3.0,
|
||||||
|
maxVoltage: 4.2,
|
||||||
temperature: 100,
|
temperature: 100,
|
||||||
runtime: "12h 30m 12s",
|
runtime: "12h 30m 12s",
|
||||||
},
|
},
|
||||||
|
@ -88,36 +109,71 @@ export default {
|
||||||
name: "XXXX-XXXX-XXXX-XXXX",
|
name: "XXXX-XXXX-XXXX-XXXX",
|
||||||
status: "ONLINE",
|
status: "ONLINE",
|
||||||
position: { lat: 40.7128, lng: 74.0012 },
|
position: { lat: 40.7128, lng: 74.0012 },
|
||||||
battery: 56,
|
batteryVoltage: 3.6,
|
||||||
|
minVoltage: 3.0,
|
||||||
|
maxVoltage: 4.2,
|
||||||
temperature: 100,
|
temperature: 100,
|
||||||
runtime: "12h 30m 12s",
|
runtime: "12h 30m 12s",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
calculateBatteryPercentage(currentVoltage, minVoltage, maxVoltage) {
|
||||||
|
if (currentVoltage <= minVoltage) {
|
||||||
|
return 0;
|
||||||
|
} else if (currentVoltage >= maxVoltage) {
|
||||||
|
return 100;
|
||||||
|
} else {
|
||||||
|
return ((currentVoltage - minVoltage) / (maxVoltage - minVoltage) * 100).toFixed(2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
validateAndUpdateLatLng(node, field, event) {
|
||||||
|
const originalValue = node.position[field];
|
||||||
|
let newValue = event.target.innerText;
|
||||||
|
|
||||||
|
// normalize seperated values
|
||||||
|
newValue = newValue.replace(',', '.');
|
||||||
|
|
||||||
|
// check if float value
|
||||||
|
const validNumberRegex = /^-?\d+(\.\d+)?$/;
|
||||||
|
|
||||||
|
if (validNumberRegex.test(newValue)) {
|
||||||
|
const parsedValue = parseFloat(newValue);
|
||||||
|
|
||||||
|
// Update if valid
|
||||||
|
node.position[field] = parsedValue;
|
||||||
|
console.log(`Updated ${field} of ${node.name}: ${parsedValue}`);
|
||||||
|
} else {
|
||||||
|
// Reset to original value if invalid
|
||||||
|
event.target.innerText = originalValue;
|
||||||
|
console.log(`Failed to set ${field} of ${node.name}: Invalid input "${newValue}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.table-container {
|
.table-container {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
padding: 0.5rem 1rem; /* Reduce top padding to 0.5rem */
|
padding: 0.5rem 1rem;
|
||||||
margin: 0 auto; /* Remove margin at the top, keep it centered horizontally */
|
margin: 0 auto;
|
||||||
width: 100%; /* Full width of parent */
|
width: 100%;
|
||||||
overflow-x: auto; /* Allow horizontal scroll if necessary */
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.responsive-table {
|
.responsive-table {
|
||||||
width: 100%; /* Full width */
|
width: 100%;
|
||||||
border-collapse: collapse; /* Ensure tight borders */
|
border-collapse: collapse;
|
||||||
margin: 0; /* Remove margin at the top of the table */
|
margin: 0;
|
||||||
table-layout: auto; /* Auto layout for slimmer columns */
|
table-layout: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.responsive-table th,
|
.responsive-table th,
|
||||||
.responsive-table td {
|
.responsive-table td {
|
||||||
padding: 0.5rem; /* Maintain slim padding */
|
padding: 0.5rem;
|
||||||
text-align: center; /* Center text */
|
text-align: center;
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid #ddd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +182,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.responsive-table tr:nth-child(even) {
|
.responsive-table tr:nth-child(even) {
|
||||||
background-color: #f9f9f9; /* Striped rows */
|
background-color: #f9f9f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-online {
|
.status-online {
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default createVuetify({
|
||||||
light: {
|
light: {
|
||||||
dark: false,
|
dark: false,
|
||||||
colors: {
|
colors: {
|
||||||
main: "#024950",
|
main: "#353A43",
|
||||||
darker: "#003135",
|
darker: "#003135",
|
||||||
contrast: "#964734",
|
contrast: "#964734",
|
||||||
accent: "#0FA4AF",
|
accent: "#0FA4AF",
|
||||||
|
|
|
@ -5,8 +5,12 @@ export interface Node {
|
||||||
coordla: number;
|
coordla: number;
|
||||||
coordlong: number;
|
coordlong: number;
|
||||||
temperature: number;
|
temperature: number;
|
||||||
battery: number;
|
batteryMinimum: number;
|
||||||
runtime: number;
|
batteryCurrent: number;
|
||||||
|
batteryMaximum: number;
|
||||||
|
voltage: number;
|
||||||
|
uptime: number;
|
||||||
|
group: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeGroup {
|
export interface NodeGroup {
|
||||||
|
|
Loading…
Reference in a new issue