first commit, i i have no idea what i have done

This commit is contained in:
tdv
2025-08-31 22:42:08 +03:00
commit c5632f6a37
177 changed files with 9173 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
import { useColorMode } from '@vueuse/core'
import Navbar from '@/customcompometns/Navbar.vue';
import Admincomponent from '@/customcompometns/Admincomponent.vue';
const mode = useColorMode()
</script>
<template>
<!-- <Navbar :custom-component="Admincomponent"/> -->
<Navbar>
<Admincomponent/>
</Navbar>
</template>

View File

@@ -0,0 +1,246 @@
<script setup lang="ts">
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'
import AssignDevice from '@/customcompometns/AssignDevice.vue'
import { useColorMode } from '@vueuse/core'
import Navbar from '@/customcompometns/Navbar.vue';
import type { PropType } from 'vue';
import { defineProps, defineEmits, reactive, watch, ref, computed } from 'vue';
import { v4 as uuidv4 } from 'uuid'
import { useRouter } from 'vue-router'
import { api } from '@/lib/api'
const mode = useColorMode()
type CreateDevicePayload = {
guid: string
name: string
userIds: number[]
}
type CreateUserPayload = {
username: string
password: string
role: string // 'admin' | 'user'
}
const router = useRouter()
const props = defineProps({
modelValue: {
type: Boolean as PropType<boolean>,
required: true,
},
})
// local form state
const device_form = reactive({
guid: uuidv4(), // default fresh UUID
name: '',
})
const user_form = reactive({
username: '',
password: '',
isAdmin: false,
})
// userIds from AssignDevice (expects v-model of string[] ids)
const selectedUserIds = ref<string[]>([])
watch(
() => props.modelValue,
(val) => {
if (val) {
device_form.guid = uuidv4()
device_form.name = ''
selectedUserIds.value = []
user_form.username = ''
user_form.password = ''
user_form.isAdmin = false
userError.value = null
userSubmitting.value = false
userSuccess.value = null
}
}
)
// simple UUIDv4 check (best-effort)
const uuidV4Re = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
const submitting = ref(false)
const errorMsg = ref<string | null>(null)
const userSubmitting = ref(false)
const userError = ref<string | null>(null)
const userSuccess = ref<string | null>(null)
const userRole = computed<string>(() => (user_form.isAdmin ? 'admin' : 'user'))
const canSubmit = computed(() => {
return (
!!device_form.guid &&
uuidV4Re.test(device_form.guid) &&
!!device_form.name.trim() &&
!submitting.value
)
})
async function submitDevice() {
errorMsg.value = null
if (!canSubmit.value) {
errorMsg.value = 'Please provide a valid GUID and name.'
return
}
const userIds: number[] = selectedUserIds.value
.map((s) => Number(s))
.filter((n) => Number.isFinite(n) && n >= 0)
const payload: CreateDevicePayload = {
guid: device_form.guid,
name: device_form.name.trim(),
userIds,
}
submitting.value = true
try {
await api.post('/devices/create', payload)
router.replace('/admin')
} catch (e: any) {
// keep client error generic
errorMsg.value = e?.response?.status === 403 ? 'Access denied.' : 'Failed to create device.'
} finally {
submitting.value = false
}
}
const canSubmitUser = computed(() => {
return (
user_form.username.trim().length >= 3 &&
user_form.password.length >= 8 && // basic client-side check; real policy enforced server-side
!userSubmitting.value
)
})
async function submitUser() {
userError.value = null
userSuccess.value = null
if (!canSubmitUser.value) {
userError.value = 'Username must be at least 3 chars; password at least 8.'
return
}
const payload: CreateUserPayload = {
username: user_form.username.trim(),
password: user_form.password, // do not trim passwords
role: userRole.value,
}
userSubmitting.value = true
try {
await api.post('/users/create', payload)
userSuccess.value = 'User created.'
router.replace('/admin')
} catch (e: any) {
userError.value = e?.response?.status === 403 ? 'Access denied.' : 'Failed to create user.'
} finally {
userSubmitting.value = false
}
}
</script>
<template>
<Navbar>
<div class="w-full py-8">
<div class="mx-auto">
<!-- Horizontal cards with gap -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 items-stretch">
<!-- Create device -->
<Card class="h-full flex flex-col">
<CardHeader>
<CardTitle>Create device</CardTitle>
</CardHeader>
<CardContent class="flex-1">
<!-- add vertical spacing between rows -->
<div class="grid gap-5">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="guid" class="text-right">GUID</Label>
<Input id="guid" class="col-span-3 w-full" v-model="device_form.guid" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="name" class="text-right">Name</Label>
<Input id="name" class="col-span-3 w-full" v-model="device_form.name" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="users" class="text-right">Allowed users</Label>
<!-- make the component span and fill -->
<AssignDevice id="users" class="col-span-3 w-full" v-model="selectedUserIds" />
</div>
<p v-if="errorMsg" class="text-sm text-red-600" aria-live="assertive">
{{ errorMsg }}
</p>
</div>
</CardContent>
<CardFooter>
<Button :disabled="!canSubmit" @click="submitDevice">
{{ submitting ? 'Saving' : 'Save' }}
</Button>
</CardFooter>
</Card>
<!-- Create user -->
<Card class="h-full flex flex-col">
<CardHeader>
<CardTitle>Create user</CardTitle>
</CardHeader>
<CardContent class="flex-1">
<div class="grid gap-5">
<div class="grid grid-cols-4 items-center gap-4">
<Label for="username" class="text-right">Username</Label>
<Input id="username" class="col-span-3 w-full" v-model="user_form.username" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="password" class="text-right">Password</Label>
<Input id="password" type="password" class="col-span-3 w-full"
v-model="user_form.password" />
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="isAdmin" class="text-right">Make admin</Label>
<div class="col-span-3">
<Switch id="isAdmin" v-model:checked="user_form.isAdmin"
v-model="user_form.isAdmin"
@update:checked="(v: any) => user_form.isAdmin = !!v"
@update:modelValue="(v: any) => user_form.isAdmin = !!v"/>
</div>
</div>
<p v-if="userError" class="text-sm text-red-600" aria-live="assertive">
{{ userError }}
</p>
<p v-if="userSuccess" class="text-sm text-green-600" aria-live="polite">
{{ userSuccess }}
</p>
</div>
</CardContent>
<CardFooter>
<Button :disabled="!canSubmitUser" @click="submitUser">
{{ userSubmitting ? 'Saving' : 'Save changes' }}
</Button>
</CardFooter>
</Card>
</div>
</div>
</div>
</Navbar>
</template>

View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
import { useColorMode } from '@vueuse/core'
import Navbar from '@/customcompometns/Navbar.vue';
import DeviceComponent from '@/customcompometns/DeviceComponent.vue';
const mode = useColorMode()
</script>
<template>
<Navbar>
<DeviceComponent></DeviceComponent>
</Navbar>
</template>

View File

@@ -0,0 +1,22 @@
<script setup lang="ts">
import { useColorMode } from '@vueuse/core'
import Navbar from '@/customcompometns/Navbar.vue';
import DevicesGrid from '@/customcompometns/DevicesGrid.vue';
import type { Device } from '@/lib/interfaces';
const mode = useColorMode()
const device_data: Device[] = [
{
guid: "c78dfff6-d7b1-4da9-b499-d5cf66533ae2",
devicename: "1",
assigned_users: "admin, lox"
},
]
</script>
<template>
<!-- <Navbar :custom-component="DevicesGrid"/> -->
<Navbar>
<DevicesGrid :devices="device_data" />
</Navbar>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
import { useColorMode } from '@vueuse/core'
const mode = useColorMode()
</script>
<template>
<div class="h-screen w-full flex items-center justify-center px-4">
<div class="text-center space-y-2">
<h1 class="text-2xl font-semibold">403 Forbidden</h1>
<p class="text-muted-foreground">You dont have permission to view this page.</p>
</div>
</div>
</template>

View File

@@ -0,0 +1,82 @@
<script setup lang="ts">
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { api } from '@/lib/api';
import { auth } from '@/lib/auth';
import { useColorMode } from '@vueuse/core'
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
const mode = useColorMode()
const router = useRouter()
const route = useRoute()
const form = ref({ username: '', password: '' })
const loading = ref(false)
const errorMsg = ref<string | null>(null)
async function onSubmit() {
errorMsg.value = null
if (!form.value.username || !form.value.password) {
errorMsg.value = 'Username and password are required.'
return
}
loading.value = true
try {
// POST /auth/signin { username, password } -> { accessToken }
const { data } = await api.post<{ accessToken: string }>('/auth/signin', {
username: form.value.username.trim(),
password: form.value.password
})
if (!data?.accessToken) throw new Error('Missing access token')
auth.setToken(data.accessToken)
// Redirect to originally requested route if present
const next = (route.query.redirect as string) || '/devices'
router.replace(next)
} catch (e: any) {
// Avoid leaking server error details to the UI
errorMsg.value = 'Invalid login or server unavailable.'
} finally {
loading.value = false
}
}
</script>
<template>
<div class="w-full h-screen flex items-center justify-center px-4">
<Card class="flex w-full mx-auto max-w-xl">
<CardHeader>
<CardTitle class="text-2xl">
Login
</CardTitle>
</CardHeader>
<CardContent>
<form class="grid gap-4" @submit.prevent="onSubmit" :aria-busy="loading">
<div class="grid gap-2">
<Label for="usernmae">User</Label>
<Input id="usernmae" required v-model.trim="form.username" autocapitalize="none"
spellcheck="false" />
</div>
<div class="grid gap-2">
<div class="flex items-center">
<Label for="password">Password</Label>
</div>
<Input id="password" type="password" required v-model="form.password" />
</div>
<Button type="submit" class="w-full" :disabled="loading">
<span v-if="!loading">Login</span>
<span v-else>Signing in</span>
</Button>
<p v-if="errorMsg" class="text-sm text-red-600" aria-live="assertive">
{{ errorMsg }}
</p>
</form>
</CardContent>
</Card>
</div>
</template>

View File

@@ -0,0 +1,12 @@
<script setup lang="ts">
import { useColorMode } from '@vueuse/core'
import Navbar from '@/customcompometns/Navbar.vue';
import UserSettings from '@/customcompometns/UserSettings.vue';
const mode = useColorMode()
</script>
<template>
<Navbar>
<UserSettings/>
</Navbar>
</template>