Commit 2f300868 authored by Patrick Ade's avatar Patrick Ade
Browse files

Refactor authentication and registration processes to use Axios; update API...

Refactor authentication and registration processes to use Axios; update API endpoints and improve error handling
parent 07514b02
......@@ -28,7 +28,7 @@ export const useAuthStore = defineStore('auth', {
actions: {
async login(username: string, password: string, router: Router | null = null): Promise<void> {
try {
const response = await api.post('/auth/login', {
const response = await api.post('/login', {
username,
password,
})
......@@ -64,7 +64,7 @@ export const useAuthStore = defineStore('auth', {
}
try {
const response = await api.get('/auth/me')
const response = await api.get('/user')
this.user = response.data
this.isAuthenticated = true
} catch (error) {
......
......@@ -2,32 +2,33 @@
import { ref } from 'vue'
import BauCard from '../components/BauCard.vue'
import RoomCard from '../components/RoomCard.vue'
import api from '@/services/apiClient'
const buildings = [
{
id: '1',
title: 'Bau 1',
description: 'Zentralgebäude mit Laboren',
image: new URL('@/assets/bau-1.png', import.meta.url).href,
},
{
id: '2',
title: 'Bau 2',
description: 'Informatik und IT-Räume',
image: new URL('@/assets/bau-2.png', import.meta.url).href,
},
{
id: '3',
title: 'Bau 3',
description: 'Informatik und IT-Räume',
image: new URL('@/assets/bau-3.png', import.meta.url).href,
},
{
id: '8',
title: 'Bau 8',
description: 'Informatik und IT-Räume',
image: new URL('@/assets/bau-8.png', import.meta.url).href,
}
{
id: '1',
title: 'Bau 1',
description: 'Zentralgebäude mit Laboren',
image: new URL('@/assets/bau-1.png', import.meta.url).href,
},
{
id: '2',
title: 'Bau 2',
description: 'Informatik und IT-Räume',
image: new URL('@/assets/bau-2.png', import.meta.url).href,
},
{
id: '3',
title: 'Bau 3',
description: 'Informatik und IT-Räume',
image: new URL('@/assets/bau-3.png', import.meta.url).href,
},
{
id: '8',
title: 'Bau 8',
description: 'Informatik und IT-Räume',
image: new URL('@/assets/bau-8.png', import.meta.url).href,
},
]
const selectedBuildingId = ref<string | null>(null)
......@@ -36,74 +37,73 @@ const isLoading = ref(false)
const error = ref<string | null>(null)
async function loadRooms(buildingId: string) {
selectedBuildingId.value = buildingId
rooms.value = []
isLoading.value = true
error.value = null
selectedBuildingId.value = buildingId
rooms.value = []
isLoading.value = true
error.value = null
try {
const response = await fetch(`http://localhost:8000/api/get_rooms_from_building?building=${buildingId}`)
const data = await response.json()
try {
const response = await api.get('/get_rooms_from_building', {
params: { building: buildingId },
})
if (response.ok) {
rooms.value = data.rooms
} else {
throw new Error(data.detail || 'Fehler beim Laden der Räume')
rooms.value = response.data.rooms
} catch (err: any) {
error.value = err.response?.data?.detail || err.message || 'Fehler beim Laden der Räume'
} finally {
isLoading.value = false
}
} catch (err: any) {
error.value = err.message
} finally {
isLoading.value = false
}
}
</script>
<template>
<div class="layout">
<!-- Linke Spalte: Gebäude -->
<div class="building-grid">
<BauCard
v-for="bau in buildings"
:key="bau.id"
:building-id="bau.id"
:title="bau.title"
:description="bau.description"
:image="bau.image"
@select="loadRooms"
/>
</div>
<div class="layout">
<!-- Linke Spalte: Gebäude -->
<div class="building-grid">
<BauCard
v-for="bau in buildings"
:key="bau.id"
:building-id="bau.id"
:title="bau.title"
:description="bau.description"
:image="bau.image"
@select="loadRooms"
/>
</div>
<!-- Rechte Spalte: Räume -->
<div class="room-panel">
<h3 v-if="selectedBuildingId">Räume in Bau {{ selectedBuildingId }}</h3>
<div v-if="isLoading">Lade Räume...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else>
<RoomCard v-for="room in rooms" :key="room" :room="room" />
</div>
<!-- Rechte Spalte: Räume -->
<div class="room-panel">
<h3 v-if="selectedBuildingId">Räume in Bau {{ selectedBuildingId }}</h3>
<div v-if="isLoading">Lade Räume...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else>
<RoomCard v-for="room in rooms" :key="room" :room="room" />
</div>
</div>
</div>
</div>
</template>
<style scoped>
.layout {
display: flex;
padding: 2rem;
gap: 2rem;
align-items: flex-start; /* wichtige Ergänzung */
display: flex;
padding: 2rem;
gap: 2rem;
align-items: flex-start;
/* wichtige Ergänzung */
}
.building-grid {
display: flex;
flex-direction: column; /* Gebäude untereinander */
gap: 1.5rem;
flex-shrink: 0;
display: flex;
flex-direction: column;
/* Gebäude untereinander */
gap: 1.5rem;
flex-shrink: 0;
}
.room-panel {
display: flex;
flex-direction: column;
gap: 1rem;
flex-grow: 1;
display: flex;
flex-direction: column;
gap: 1rem;
flex-grow: 1;
}
</style>
<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">
import { defineComponent, ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import ChartCard from '../components/ChartCard.vue'
import api from '@/services/apiClient' // Axios instance
export default defineComponent({
name: 'ChartView',
components: { ChartCard },
setup() {
const route = useRoute()
const room = decodeURIComponent(route.params.room as string)
const labels = ref<string[]>([])
const co2Data = ref<number[]>([])
const temperatureData = ref<number[]>([])
const humidityData = ref<number[]>([])
const start = ref('')
const stop = ref('')
// Computed property for displaying max 6 labels
const displayLabels = computed(() => {
const total = labels.value.length
if (total <= 6) return labels.value
const result: string[] = []
for (let i = 0; i < total; i++) {
result.push('')
}
for (let i = 0; i < 6; i++) {
const idx = Math.round(i * (total - 1) / 5)
result[idx] = labels.value[idx]
}
return result
})
function toISOStringSafe(value: string): string {
try {
return new Date(value).toISOString()
} catch {
return ''
}
}
function setDefaultTimeWindow() {
const now = new Date()
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
// Format dates properly for datetime-local inputs
const formatForInput = (date: Date) => {
const year = date.getFullYear()
const month = ('0' + (date.getMonth() + 1)).slice(-2)
const day = ('0' + date.getDate()).slice(-2)
const hours = ('0' + date.getHours()).slice(-2)
const minutes = ('0' + date.getMinutes()).slice(-2)
return `${year}-${month}-${day}T${hours}:${minutes}`
}
start.value = formatForInput(weekAgo)
stop.value = formatForInput(now)
}
async function loadData() {
if (!start.value || !stop.value) return
const startISO = toISOStringSafe(start.value)
const stopISO = toISOStringSafe(stop.value)
if (new Date(startISO) >= new Date(stopISO)) {
alert('❌ Der Startzeitpunkt muss vor dem Endzeitpunkt liegen.')
return
}
const url = `http://localhost:8000/api/room_data_range?room=${encodeURIComponent(
room
)}&start=${startISO}&stop=${stopISO}`
try {
const response = await fetch(url)
if (!response.ok) throw new Error('Fehler beim Laden')
const json = await response.json()
const entries = Object.entries(json.data).sort(
([a], [b]) => new Date(a).getTime() - new Date(b).getTime()
)
labels.value = entries.map(([timestamp]) =>
new Date(timestamp).toLocaleString([], {
day: '2-digit',
month: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
)
co2Data.value = entries.map(([, values]: any) => Number(values.co2))
temperatureData.value = entries.map(([, values]: any) =>
Number(values.temperature)
)
humidityData.value = entries.map(([, values]: any) =>
Number(values.humidity)
)
} catch (err) {
console.error('❌ Fehler beim Laden der Daten:', err)
}
}
// Function to load data with default last week window
async function loadDataWithDefaultWindow() {
setDefaultTimeWindow()
await loadData()
}
onMounted(async () => {
await loadDataWithDefaultWindow()
})
return {
room,
start,
stop,
labels,
displayLabels,
co2Data,
temperatureData,
humidityData,
loadData,
loadDataWithDefaultWindow,
}
},
name: 'ChartView',
components: { ChartCard },
setup() {
const route = useRoute()
const room = decodeURIComponent(route.params.room as string)
const labels = ref<string[]>([])
const co2Data = ref<number[]>([])
const temperatureData = ref<number[]>([])
const humidityData = ref<number[]>([])
const start = ref('')
const stop = ref('')
const displayLabels = computed(() => {
const total = labels.value.length
if (total <= 6) return labels.value
const result: string[] = Array(total).fill('')
for (let i = 0; i < 6; i++) {
const idx = Math.round((i * (total - 1)) / 5)
result[idx] = labels.value[idx]
}
return result
})
function toISOStringSafe(value: string): string {
try {
return new Date(value).toISOString()
} catch {
return ''
}
}
function setDefaultTimeWindow() {
const now = new Date()
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000)
const formatForInput = (date: Date) => {
const year = date.getFullYear()
const month = ('0' + (date.getMonth() + 1)).slice(-2)
const day = ('0' + date.getDate()).slice(-2)
const hours = ('0' + date.getHours()).slice(-2)
const minutes = ('0' + date.getMinutes()).slice(-2)
return `${year}-${month}-${day}T${hours}:${minutes}`
}
start.value = formatForInput(weekAgo)
stop.value = formatForInput(now)
}
async function loadData() {
if (!start.value || !stop.value) return
const startISO = toISOStringSafe(start.value)
const stopISO = toISOStringSafe(stop.value)
if (new Date(startISO) >= new Date(stopISO)) {
alert('❌ Der Startzeitpunkt muss vor dem Endzeitpunkt liegen.')
return
}
try {
const response = await api.get('/room_data_range', {
params: {
room,
start: startISO,
stop: stopISO,
},
})
const entries = Object.entries(response.data.data).sort(
([a], [b]) => new Date(a).getTime() - new Date(b).getTime()
)
labels.value = entries.map(([timestamp]) =>
new Date(timestamp).toLocaleString([], {
day: '2-digit',
month: '2-digit',
hour: '2-digit',
minute: '2-digit',
})
)
co2Data.value = entries.map(([, values]: any) => Number(values.co2))
temperatureData.value = entries.map(([, values]: any) => Number(values.temperature))
humidityData.value = entries.map(([, values]: any) => Number(values.humidity))
} catch (err: any) {
console.error('❌ Fehler beim Laden der Daten:', err)
alert('Fehler beim Laden der Daten')
}
}
async function loadDataWithDefaultWindow() {
setDefaultTimeWindow()
await loadData()
}
onMounted(async () => {
await loadDataWithDefaultWindow()
})
return {
room,
start,
stop,
labels,
displayLabels,
co2Data,
temperatureData,
humidityData,
loadData,
loadDataWithDefaultWindow,
}
},
})
</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>
<script lang="ts">
import { getCSRFToken } from '../stores/auth'
import api from '@/services/apiClient' // Your axios instance
export default {
data() {
......@@ -13,29 +13,23 @@ export default {
methods: {
async register() {
try {
const response = await fetch('http://localhost:8000/api/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken(),
},
body: JSON.stringify({
email: this.email,
password: this.password,
}),
credentials: 'include',
const response = await api.post('/register', {
username: this.email,
password: this.password,
})
const data = await response.json()
if (response.ok) {
if (response.status === 200) {
this.success = 'Registration successful! Please log in.'
setTimeout(() => {
this.$router.push('/login')
}, 1000)
} else {
this.error = data.error || 'Registration failed'
this.error = response.data.error || 'Registration failed'
}
} catch (err) {
this.error = 'An error occurred during registration: ' + err
} catch (err: any) {
this.error =
err.response?.data?.error ||
'An error occurred during registration: ' + err.message
}
},
},
......@@ -43,127 +37,128 @@ export default {
</script>
<template>
<div class="register-container">
<div class="register-card">
<h1>Registrieren</h1>
<p class="intro">Erstelle ein Konto, um Zugriff auf Sensor- und Raumdaten zu erhalten.</p>
<form @submit.prevent="register">
<div class="form-group">
<label for="email">Email</label>
<input
v-model="email"
id="email"
type="email"
required
placeholder="z.B. max@ht.de"
/>
</div>
<div class="form-group">
<label for="password">Passwort</label>
<input
v-model="password"
id="password"
type="password"
required
placeholder="••••••••"
/>
<div class="register-container">
<div class="register-card">
<h1>Registrieren</h1>
<p class="intro">
Erstelle ein Konto, um Zugriff auf Sensor- und Raumdaten zu erhalten.
</p>
<form @submit.prevent="register">
<div class="form-group">
<label for="email">Email</label>
<input
v-model="email"
id="email"
type="email"
required
placeholder="z.B. max@ht.de"
/>
</div>
<div class="form-group">
<label for="password">Passwort</label>
<input
v-model="password"
id="password"
type="password"
required
placeholder="••••••••"
/>
</div>
<button type="submit">Registrieren</button>
<p v-if="error" class="error">{{ error }}</p>
<p v-if="success" class="success">{{ success }}</p>
</form>
</div>
<button type="submit">Registrieren</button>
<p v-if="error" class="error">{{ error }}</p>
<p v-if="success" class="success">{{ success }}</p>
</form>
</div>
</div>
</template>
<style scoped>
.register-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 80vh;
display: flex;
justify-content: center;
align-items: center;
min-height: 80vh;
}
.register-card {
background-color: #f9f9f9;
padding: 2.5rem;
border-radius: 12px;
box-shadow: 0 2px 14px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 420px;
font-family: 'Segoe UI', sans-serif;
background-color: #f9f9f9;
padding: 2.5rem;
border-radius: 12px;
box-shadow: 0 2px 14px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 420px;
font-family: 'Segoe UI', sans-serif;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 0.5rem;
text-align: center;
color: #333;
margin-bottom: 0.5rem;
}
.intro {
text-align: center;
font-size: 0.95rem;
color: #666;
margin-bottom: 2rem;
text-align: center;
font-size: 0.95rem;
color: #666;
margin-bottom: 2rem;
}
.form-group {
margin-bottom: 1.5rem;
margin-bottom: 1.5rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #444;
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: #444;
}
input {
width: 100%;
padding: 0.6rem 0.75rem;
border: 1px solid #ccc;
border-radius: 6px;
font-size: 1rem;
width: 100%;
padding: 0.6rem 0.75rem;
border: 1px solid #ccc;
border-radius: 6px;
font-size: 1rem;
}
input:focus {
border-color: #007bff;
outline: none;
border-color: #007bff;
outline: none;
}
button {
width: 100%;
padding: 0.75rem;
background-color: #007bff;
color: white;
font-weight: bold;
border: none;
border-radius: 6px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s ease;
width: 100%;
padding: 0.75rem;
background-color: #007bff;
color: white;
font-weight: bold;
border: none;
border-radius: 6px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s ease;
}
button:hover {
background-color: #0056b3;
background-color: #0056b3;
}
.error {
margin-top: 1rem;
color: #dc3545;
text-align: center;
font-weight: 500;
margin-top: 1rem;
color: #dc3545;
text-align: center;
font-weight: 500;
}
.success {
margin-top: 1rem;
color: #28a745;
text-align: center;
font-weight: 500;
margin-top: 1rem;
color: #28a745;
text-align: center;
font-weight: 500;
}
</style>
\ No newline at end of file
</style>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getCSRFToken } from '../stores/auth'
import RoomCard from '../components/RoomCard.vue'
import api from '@/services/apiClient' // Custom Axios instance
const rooms = ref<string[]>([])
const error = ref<string | null>(null)
const fetchRooms = async () => {
try {
const response = await fetch('http://localhost:8000/api/get_rooms', {
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCSRFToken(),
},
credentials: 'include',
})
const data = await response.json()
if (response.ok && data.rooms) {
rooms.value = data.rooms
} else {
error.value = 'Fehler beim Laden der Räume.'
try {
const response = await api.get('/get_rooms')
if (response.data.rooms) {
rooms.value = response.data.rooms
} else {
error.value = 'Fehler beim Laden der Räume.'
}
} catch (err: any) {
error.value = err.response?.data?.detail || 'Serverfehler: ' + err.message
}
} catch (err) {
error.value = 'Serverfehler: ' + err
}
}
onMounted(fetchRooms)
</script>
<template>
<div class="room-container">
<h2>Räume</h2>
<p v-if="error" class="error">{{ error }}</p>
<div class="card-grid">
<RoomCard v-for="room in rooms" :key="room" :room="room" />
<div class="room-container">
<h2>Räume</h2>
<p v-if="error" class="error">{{ error }}</p>
<div class="card-grid">
<RoomCard v-for="room in rooms" :key="room" :room="room" />
</div>
</div>
</div>
</template>
<style scoped>
.room-container {
padding: 2rem;
padding: 2rem;
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
margin-top: 1rem;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.error {
color: red;
color: red;
}
</style>
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