Commit 4c761710 authored by 22stmo1bif's avatar 22stmo1bif
Browse files

Refactor sensor schemas for improved data structure; update...

Refactor sensor schemas for improved data structure; update RoomDataRangeResponse to use nested dictionaries
Add authentication handling and logout functionality in NavBar component
Enhance AboutView with additional project details and links
Revamp ChartView for better data loading and display; implement default time window and improved styling
Update HomeView styles for better readability; remove deprecated HomeViewOLD component
parent 7e8ad6c5
...@@ -2,9 +2,6 @@ from ninja import Schema ...@@ -2,9 +2,6 @@ from ninja import Schema
from typing import List, Dict, Optional from typing import List, Dict, Optional
from datetime import datetime
class SensorData(Schema): class SensorData(Schema):
co2: int co2: int
humidity: float humidity: float
...@@ -13,7 +10,7 @@ class SensorData(Schema): ...@@ -13,7 +10,7 @@ class SensorData(Schema):
class RoomDataRangeResponse(Schema): class RoomDataRangeResponse(Schema):
room: str room: str
data: Dict[datetime, SensorData] data: Dict[str, Dict[str, float]]
class RoomDataPoint(Schema): class RoomDataPoint(Schema):
......
<script setup lang="ts"> <script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router' import { RouterLink, RouterView } from 'vue-router'
import { useAuthStore } from '../stores/auth'
import { useRouter } from 'vue-router'
const authStore = useAuthStore()
const router = useRouter()
const logout = async () => {
try {
await authStore.logout(router)
} catch (error) {
console.error('Logout error:', error)
}
}
</script> </script>
<template> <template>
...@@ -14,10 +27,20 @@ import { RouterLink, RouterView } from 'vue-router' ...@@ -14,10 +27,20 @@ import { RouterLink, RouterView } from 'vue-router'
<nav> <nav>
<RouterLink to="/">Home</RouterLink> <RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink> <RouterLink to="/about">About</RouterLink>
<RouterLink to="/login">Login</RouterLink>
<RouterLink to="/register">Registrieren</RouterLink> <!-- Nur anzeigen wenn NICHT eingeloggt -->
<RouterLink to="/buildingsView">Gebäude</RouterLink> <template v-if="!authStore.isAuthenticated">
<RouterLink to="/dashboard">Device Dashboard</RouterLink> <RouterLink to="/login">Login</RouterLink>
<RouterLink to="/register">Registrieren</RouterLink>
</template>
<!-- Nur anzeigen wenn eingeloggt -->
<template v-if="authStore.isAuthenticated">
<RouterLink to="/buildingsView">Gebäude</RouterLink>
<RouterLink to="/dashboard">Device Dashboard</RouterLink>
<span class="user-info">{{ authStore.user?.username }}</span>
<button @click="logout" class="logout-btn">Logout</button>
</template>
</nav> </nav>
</div> </div>
</header> </header>
...@@ -66,6 +89,28 @@ nav a:hover { ...@@ -66,6 +89,28 @@ nav a:hover {
color: darkred; color: darkred;
} }
.user-info {
margin-left: 1rem;
color: #333;
font-weight: 500;
}
.logout-btn {
margin-left: 1rem;
padding: 0.4rem 0.8rem;
background: #dc3545;
color: white;
border: none;
border-radius: 4px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
}
.logout-btn:hover {
background: #c82333;
}
main { main {
padding-top: 80px; /* Abstand für den festen Header */ padding-top: 80px; /* Abstand für den festen Header */
} }
......
...@@ -51,17 +51,30 @@ ...@@ -51,17 +51,30 @@
<h2>Projektteam</h2> <h2>Projektteam</h2>
<p> <p>
Das Projekt wurde an der HFT Stuttgart unter anderem von Eric Duminil, Tobias Erhart, Das Ursprüngliche Projekt wurde an der HFT Stuttgart unter anderem von Eric Duminil, Tobias Erhart,
Myriam Guedey, Nathalie Heimsch, Benjamin Hueber, Robert Otto und Jonas Stave entwickelt. Myriam Guedey, Nathalie Heimsch, Benjamin Hueber, Robert Otto und Jonas Stave entwickelt.
</p>
<p>Die Implementierung dieser Webseite und eines Servers mit MQTT-Broker, Stream-Processing,
InfluxDB Datenbank, django backend, vue.js frontend
entsand in einem Team-Projekt im Pervasive Computing Wahlpflichfach.
</p> </p>
<p> <p>
Weitere Infos und Bauanleitungen unter: Weitere Infos und zum ursprünglichen Projekt unter:
<a href="https://transfer.hft-stuttgart.de/gitlab/co2ampel" target="_blank"> <a href="https://transfer.hft-stuttgart.de/gitlab/co2ampel" target="_blank">
https://transfer.hft-stuttgart.de/gitlab/co2ampel https://transfer.hft-stuttgart.de/gitlab/co2ampel
</a> </a>
</p> </p>
<p>
Weitere Infos zum weiterführenden Projekt:
<a href="https://transfer.hft-stuttgart.de/gitlab/co2ampel2" target="_blank">
https://transfer.hft-stuttgart.de/gitlab/co2ampel2
</a>
</p>
<h2>Technologie</h2> <h2>Technologie</h2>
<ul> <ul>
......
<template>
<div class="chart-view">
<h2>Daten für Raum: {{ room }}</h2>
<div class="time-form">
<label>
Start:
<input class="datetime" type="datetime-local" v-model="start" />
</label>
<label>
Stop:
<input class="datetime" type="datetime-local" v-model="stop" />
</label>
<button @click="loadData">Zeitraum aktualisieren</button>
<button @click="loadDataWithDefaultWindow" class="default-btn">Zeige die letzte Woche</button>
</div>
<div class="chart-grid">
<ChartCard
title="CO₂ Verlauf"
:labels="displayLabels"
:data="co2Data"
borderColor="#ff6384"
/>
<ChartCard
title="Temperatur Verlauf"
:labels="displayLabels"
:data="temperatureData"
borderColor="#36a2eb"
/>
<ChartCard
title="Luftfeuchtigkeit Verlauf"
:labels="displayLabels"
:data="humidityData"
borderColor="#4bc0c0"
/>
</div>
</div>
</template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref, onMounted, computed } from 'vue' import { defineComponent, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import ChartCard from '../components/ChartCard.vue' import ChartCard from '../components/ChartCard.vue'
import api from '@/services/apiClient' // Axios instance
export default defineComponent({ export default defineComponent({
name: 'ChartView', name: 'ChartView',
components: { ChartCard }, components: { ChartCard },
setup() { setup() {
const route = useRoute() const route = useRoute()
const room = decodeURIComponent(route.params.room as string) const room = decodeURIComponent(route.params.room as string)
const labels = ref<string[]>([]) const labels = ref<string[]>([])
const co2Data = ref<number[]>([]) const co2Data = ref<number[]>([])
const temperatureData = ref<number[]>([]) const temperatureData = ref<number[]>([])
const humidityData = ref<number[]>([]) const humidityData = ref<number[]>([])
const start = ref('') const start = ref('')
const stop = ref('') const stop = ref('')
const displayLabels = computed(() => { // Computed property for displaying max 6 labels
const total = labels.value.length const displayLabels = computed(() => {
if (total <= 6) return labels.value const total = labels.value.length
const result: string[] = Array(total).fill('') if (total <= 6) return labels.value
for (let i = 0; i < 6; i++) { const result: string[] = []
const idx = Math.round((i * (total - 1)) / 5) for (let i = 0; i < total; i++) {
result[idx] = labels.value[idx] result.push('')
} }
return result for (let i = 0; i < 6; i++) {
}) const idx = Math.round(i * (total - 1) / 5)
result[idx] = labels.value[idx]
function toISOStringSafe(value: string): string { }
try { return result
return new Date(value).toISOString() })
} catch {
return '' function toISOStringSafe(value: string): string {
} try {
} return new Date(value).toISOString()
} catch {
function setDefaultTimeWindow() { return ''
const now = new Date() }
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000) }
const formatForInput = (date: Date) => { function setDefaultTimeWindow() {
const year = date.getFullYear() const now = new Date()
const month = ('0' + (date.getMonth() + 1)).slice(-2) const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
const day = ('0' + date.getDate()).slice(-2)
const hours = ('0' + date.getHours()).slice(-2) // Format dates properly for datetime-local inputs
const minutes = ('0' + date.getMinutes()).slice(-2) const formatForInput = (date: Date) => {
return `${year}-${month}-${day}T${hours}:${minutes}` const year = date.getFullYear()
} const month = ('0' + (date.getMonth() + 1)).slice(-2)
const day = ('0' + date.getDate()).slice(-2)
start.value = formatForInput(weekAgo) const hours = ('0' + date.getHours()).slice(-2)
stop.value = formatForInput(now) const minutes = ('0' + date.getMinutes()).slice(-2)
} return `${year}-${month}-${day}T${hours}:${minutes}`
}
async function loadData() {
if (!start.value || !stop.value) return start.value = formatForInput(weekAgo)
stop.value = formatForInput(now)
const startISO = toISOStringSafe(start.value) }
const stopISO = toISOStringSafe(stop.value)
async function loadData() {
if (new Date(startISO) >= new Date(stopISO)) { if (!start.value || !stop.value) return
alert('❌ Der Startzeitpunkt muss vor dem Endzeitpunkt liegen.')
return const startISO = toISOStringSafe(start.value)
} const stopISO = toISOStringSafe(stop.value)
try { if (new Date(startISO) >= new Date(stopISO)) {
const response = await api.get('/room_data_range', { alert('❌ Der Startzeitpunkt muss vor dem Endzeitpunkt liegen.')
params: { return
room, }
start: startISO,
stop: stopISO, const url = `http://localhost:8000/api/room_data_range?room=${encodeURIComponent(
}, room
}) )}&start=${startISO}&stop=${stopISO}`
const entries = Object.entries(response.data.data).sort( try {
([a], [b]) => new Date(a).getTime() - new Date(b).getTime() const response = await fetch(url)
) if (!response.ok) throw new Error('Fehler beim Laden')
labels.value = entries.map(([timestamp]) => const json = await response.json()
new Date(timestamp).toLocaleString([], { const entries = Object.entries(json.data).sort(
day: '2-digit', ([a], [b]) => new Date(a).getTime() - new Date(b).getTime()
month: '2-digit', )
hour: '2-digit',
minute: '2-digit', labels.value = entries.map(([timestamp]) =>
}) new Date(timestamp).toLocaleString([], {
) day: '2-digit',
co2Data.value = entries.map(([, values]: any) => Number(values.co2)) month: '2-digit',
temperatureData.value = entries.map(([, values]: any) => Number(values.temperature)) hour: '2-digit',
humidityData.value = entries.map(([, values]: any) => Number(values.humidity)) minute: '2-digit',
} catch (err: any) { })
console.error('❌ Fehler beim Laden der Daten:', err) )
alert('Fehler beim Laden der Daten') co2Data.value = entries.map(([, values]: any) => Number(values.co2))
} temperatureData.value = entries.map(([, values]: any) =>
} Number(values.temperature)
)
async function loadDataWithDefaultWindow() { humidityData.value = entries.map(([, values]: any) =>
setDefaultTimeWindow() Number(values.humidity)
await loadData() )
} } catch (err) {
console.error('❌ Fehler beim Laden der Daten:', err)
onMounted(async () => { }
await loadDataWithDefaultWindow() }
})
// Function to load data with default last week window
return { async function loadDataWithDefaultWindow() {
room, setDefaultTimeWindow()
start, await loadData()
stop, }
labels,
displayLabels, onMounted(async () => {
co2Data, await loadDataWithDefaultWindow()
temperatureData, })
humidityData,
loadData, return {
loadDataWithDefaultWindow, room,
} start,
}, stop,
labels,
displayLabels,
co2Data,
temperatureData,
humidityData,
loadData,
loadDataWithDefaultWindow,
}
},
}) })
</script> </script>
<style scoped>
.chart-view {
padding: 2rem;
color: #1a1a1a;
}
.time-form {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
align-items: center;
flex-wrap: wrap;
}
.time-form input {
padding: 0.5rem;
font-size: 1rem;
border-radius: 5px;
}
.time-form button {
background: #007bff;
color: white;
padding: 0.6rem 1.2rem;
border: none;
border-radius: 4px;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s ease;
}
.time-form button:hover {
background-color: #0056b3;
}
.time-form .default-btn {
background: #28a745;
}
.time-form .default-btn:hover {
background-color: #218838;
}
.chart-grid {
display: flex;
flex-wrap: nowrap;
gap: 2rem;
justify-content: flex-start;
flex-direction: row;
}
</style>
...@@ -66,11 +66,13 @@ export default { ...@@ -66,11 +66,13 @@ export default {
border-radius: 12px; border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
text-align: center; text-align: center;
color: #333333;
} }
h1 { h1 {
font-size: 2rem; font-size: 2rem;
margin-bottom: 1rem; margin-bottom: 1rem;
color: #222222;
} }
.status-box { .status-box {
...@@ -79,6 +81,7 @@ h1 { ...@@ -79,6 +81,7 @@ h1 {
background-color: #ffffff; background-color: #ffffff;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 8px; border-radius: 8px;
color: #333333;
} }
button { button {
......
<script setup lang="ts">
import TheWelcome from '../components/TheWelcome.vue'
</script>
<template>
<main>
<TheWelcome />
</main>
</template>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment