Compare commits
17 Commits
7a4d012759
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0dc9d8ce31 | |||
| 4ebdca146f | |||
| 91d6a2789d | |||
| 54b32db820 | |||
| da0d438948 | |||
| d9253dfbe9 | |||
| c716067170 | |||
| a201deb851 | |||
| 8ffb1a2164 | |||
| 3677304991 | |||
| f395fb626a | |||
| a45ace4211 | |||
| c7210c6d80 | |||
| 53b9fbde40 | |||
| fd6d4817b5 | |||
| 413e2ff9cd | |||
| 2b0fd46b7f |
@@ -3,3 +3,5 @@ npm-debug.log
|
|||||||
Dockerfile
|
Dockerfile
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
.env
|
.env
|
||||||
|
dist
|
||||||
|
.git
|
||||||
|
|||||||
1
.env
1
.env
@@ -1,2 +1,3 @@
|
|||||||
VITE_BACKEND_API_HOST=http://localhost
|
VITE_BACKEND_API_HOST=http://localhost
|
||||||
VITE_BACKEND_API_PORT=3000
|
VITE_BACKEND_API_PORT=3000
|
||||||
|
VITE_PAGE_TITLE=ALAP-Telefonkönyv
|
||||||
83
.gitea/workflows/main.yml
Normal file
83
.gitea/workflows/main.yml
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
name: Build and Deploy Frontend
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy-frontend:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Checkout
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Docker smoke test
|
||||||
|
- name: Docker smoke test
|
||||||
|
run: docker ps -a
|
||||||
|
|
||||||
|
# Docker login a Gitea registry-be
|
||||||
|
- name: Docker login
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.REGISTRY_PASSWORD }}" \
|
||||||
|
| docker login git.petyi.eu -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||||
|
|
||||||
|
# Docker build (prod, nginx-es image)
|
||||||
|
- name: Docker build (frontend prod)
|
||||||
|
run: |
|
||||||
|
docker build \
|
||||||
|
-t git.petyi.eu/szakdolgozat/frontend:latest \
|
||||||
|
--build-arg VITE_BACKEND_API_HOST="http://k8s.petyi.eu" \
|
||||||
|
--build-arg VITE_BACKEND_API_PORT="30080" \
|
||||||
|
.
|
||||||
|
|
||||||
|
# Docker push
|
||||||
|
- name: Docker push
|
||||||
|
run: |
|
||||||
|
docker push git.petyi.eu/szakdolgozat/frontend:latest
|
||||||
|
|
||||||
|
# Kubeconfig beállítása (service accountos kubeconfig)
|
||||||
|
- name: Set up kubeconfig
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /tmp/kubeconfig
|
||||||
|
run: |
|
||||||
|
echo "${{ secrets.KUBECONFIG_B64 }}" | base64 -d > "$KUBECONFIG"
|
||||||
|
echo "==== kubeconfig contexts ===="
|
||||||
|
kubectl config get-contexts || true
|
||||||
|
|
||||||
|
# Kubernetes smoke test
|
||||||
|
- name: Kubernetes smoke test
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /tmp/kubeconfig
|
||||||
|
run: |
|
||||||
|
kubectl cluster-info
|
||||||
|
kubectl -n szakdolgozat get pods
|
||||||
|
|
||||||
|
# Rollout restart a frontend deploymentre
|
||||||
|
- name: Rollout restart frontend deployment
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /tmp/kubeconfig
|
||||||
|
run: |
|
||||||
|
kubectl -n szakdolgozat rollout restart deployment/telefonkonyv-frontend
|
||||||
|
kubectl -n szakdolgozat rollout status deployment/telefonkonyv-frontend --timeout=60s
|
||||||
|
|
||||||
|
# Frontend URL kiírása + Step Summary
|
||||||
|
- name: Show Frontend URL
|
||||||
|
env:
|
||||||
|
KUBECONFIG: /tmp/kubeconfig
|
||||||
|
run: |
|
||||||
|
FRONTEND_NODEPORT=$(kubectl -n szakdolgozat get svc telefonkonyv-frontend -o jsonpath='{.spec.ports[0].nodePort}')
|
||||||
|
URL="http://k8s.petyi.eu:${FRONTEND_NODEPORT}"
|
||||||
|
echo "Frontend URL: ${URL}"
|
||||||
|
|
||||||
|
# Ha a runner biztosítja a step summary-t (Gitea act_runner szokta),
|
||||||
|
# akkor írjuk bele oda is:
|
||||||
|
if [ -n "${GITHUB_STEP_SUMMARY}" ]; then
|
||||||
|
{
|
||||||
|
echo "### Frontend deployment"
|
||||||
|
echo ""
|
||||||
|
echo "- URL: ${URL}"
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
fi
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,7 +2,9 @@
|
|||||||
# gitignore template for Vue.js projects
|
# gitignore template for Vue.js projects
|
||||||
#
|
#
|
||||||
# Recommended template: Node.gitignore
|
# Recommended template: Node.gitignore
|
||||||
|
.git
|
||||||
|
|
||||||
|
dist
|
||||||
# TODO: where does this rule come from?
|
# TODO: where does this rule come from?
|
||||||
docs/_book
|
docs/_book
|
||||||
|
|
||||||
|
|||||||
29
Dockerfile
29
Dockerfile
@@ -1,4 +1,7 @@
|
|||||||
FROM node:20-alpine
|
# ================
|
||||||
|
# 1. BUILD STAGE
|
||||||
|
# ================
|
||||||
|
FROM node:20-alpine AS build
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
@@ -6,6 +9,26 @@ COPY package*.json ./
|
|||||||
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
EXPOSE 5173
|
COPY . .
|
||||||
|
|
||||||
# A futtatási parancsot a compose adja
|
ARG VITE_BACKEND_API_HOST=http://telefonkonyv-api
|
||||||
|
ARG VITE_BACKEND_API_PORT=3000
|
||||||
|
ENV VITE_BACKEND_API_HOST=${VITE_BACKEND_API_HOST}
|
||||||
|
ENV VITE_BACKEND_API_PORT=${VITE_BACKEND_API_PORT}
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# =================
|
||||||
|
# 2. RUNTIME STAGE
|
||||||
|
# =================
|
||||||
|
FROM nginx:1.27-alpine
|
||||||
|
|
||||||
|
RUN rm /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
COPY --from=build /usr/src/app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|||||||
16
Dockerfile.dev
Normal file
16
Dockerfile.dev
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 5173
|
||||||
|
|
||||||
|
# A futtatási parancsot a compose adja, de kell az alap is a k8s-hez
|
||||||
|
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0", "--port", "5173"]
|
||||||
|
|
||||||
|
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
build: .
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.dev
|
||||||
container_name: telefonkonyv-frontend
|
container_name: telefonkonyv-frontend
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env
|
env_file: .env
|
||||||
environment:
|
environment:
|
||||||
- CHOKIDAR_USEPOLLING=true
|
- CHOKIDAR_USEPOLLING=true
|
||||||
- BACKEND_API_HOST=${BACKEND_API_HOST}
|
- BACKEND_API_HOST=${VITE_BACKEND_API_HOST}
|
||||||
- BACKEND_API_PORT=${BACKEND_API_PORT}
|
- BACKEND_API_PORT=${VITE_BACKEND_API_PORT}
|
||||||
ports:
|
ports:
|
||||||
- "${FRONTEND_PORT:-5173}:5173"
|
- "${FRONTEND_PORT:-5173}:5173"
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
35
k8s/frontend-deployment.yaml
Normal file
35
k8s/frontend-deployment.yaml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
|
||||||
|
kind: Deployment
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
name: telefonkonyv-frontend
|
||||||
|
namespace: szakdolgozat
|
||||||
|
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: telefonkonyv-frontend
|
||||||
|
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: telefonkonyv-frontend
|
||||||
|
spec:
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: gitea-regcred
|
||||||
|
|
||||||
|
containers:
|
||||||
|
- name: frontend
|
||||||
|
image: git.petyi.eu/szakdolgozat/frontend:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
env:
|
||||||
|
- name: CHOKIDAR_USEPOLLING
|
||||||
|
value: "true"
|
||||||
|
- name: VITE_BACKEND_API_HOST
|
||||||
|
value: "http://telefonkonyv-api"
|
||||||
|
- name: VITE_BACKEND_API_PORT
|
||||||
|
value: "3000"
|
||||||
22
k8s/frontend-ingress.yaml
Normal file
22
k8s/frontend-ingress.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
|
||||||
|
kind: Ingress
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
name: telefonkonyv-frontend-ingress
|
||||||
|
namespace: szakdolgozat
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
|
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: app.k8s.petyi.eu
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: telefonkonyv-frontend
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
13
k8s/frontend-service.yaml
Normal file
13
k8s/frontend-service.yaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: telefonkonyv-frontend
|
||||||
|
namespace: szakdolgozat
|
||||||
|
spec:
|
||||||
|
type: NodePort
|
||||||
|
selector:
|
||||||
|
app: telefonkonyv-frontend
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
nodePort: 30573
|
||||||
13
nginx.conf
Normal file
13
nginx.conf
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Statikus fájlok
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
642
src/App.vue
642
src/App.vue
@@ -3,73 +3,276 @@
|
|||||||
<div v-if="pageStatus=='logout'" class="flex flex-col w-full h-full">
|
<div v-if="pageStatus=='logout'" class="flex flex-col w-full h-full">
|
||||||
<div class="flex flex-row-reverse font-serif p-2">
|
<div class="flex flex-row-reverse font-serif p-2">
|
||||||
<div class="flex">API Status:
|
<div class="flex">API Status:
|
||||||
|
|
||||||
<svg v-if="loginCheck" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="flex size-6 ml-2 fill-green-500">
|
<svg v-if="loginCheck" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="flex size-6 ml-2 fill-green-500">
|
||||||
<path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="flex size-6 ml-2 fill-red-500">
|
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="flex size-6 ml-2 fill-red-500">
|
||||||
<path fill-rule="evenodd" d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" clip-rule="evenodd" />
|
<path fill-rule="evenodd" d="M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" clip-rule="evenodd" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex mr-5">API Info: {{ apiBase }} </div>
|
<div class="flex mr-5">API Info: {{ apiBase }} </div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex w-full h-full justify-center items-center">
|
|
||||||
|
|
||||||
|
<div class="flex w-full h-full justify-center items-center">
|
||||||
<div class="flex flex-col w-1/4 h-max bg-stone-300 p-4 rounded-xl">
|
<div class="flex flex-col w-1/4 h-max bg-stone-300 p-4 rounded-xl">
|
||||||
<div class="flex flex-row font-serif text-2xl">{{loginErrorMsg}}</div>
|
<div class="flex flex-row font-serif text-2xl">{{loginErrorMsg}}</div>
|
||||||
<hr class="flex border-2 border-black mb-5">
|
<hr class="flex border-2 border-black mb-5">
|
||||||
<input v-model="userLoginName" type="text" class="flex p-2 mb-1 font-serif" placeholder="Felhasználónév">
|
<input v-model="userLoginName" type="text" class="flex p-2 mb-1 font-serif" placeholder="Felhasználónév">
|
||||||
<input v-model="userLoginPw" type="password" class="flex p-2 font-serif" :class="{hidden: isLoading}" placeholder="Jelszó">
|
<input v-model="userLoginPw" type="password" class="flex p-2 font-serif" :class="{hidden: isLoading}" placeholder="Jelszó">
|
||||||
<div class="flex flex-row-reverse w-full h-max" >
|
<div class="flex flex-row-reverse w-full h-max" >
|
||||||
<input @click="loginUser" type="submit" value="Login" class="flex bg-neutral-500 text-white mt-5 px-3 py-1 rounded-xl w-min"></input>
|
<input @click="loginUser" type="submit" value="Login" class="flex bg-neutral-500 text-white mt-5 px-3 py-1 rounded-xl w-min">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dashboard oldal nézet -->
|
||||||
|
<div v-else-if="pageStatus=='contacts' || pageStatus=='admin'" class="flex flex-row w-full h-full bg-red-300">
|
||||||
|
<!-- Menü -->
|
||||||
|
<div class="flex flex-col w-1/6 h-full bg-neutral-400 py-10 px-3 space-y-3">
|
||||||
|
<div class="flex flex-row h-fit w-full border-dashed border-2 border-neutral-300 p-1 rounded-lg justify-between" @click="switchContacts">
|
||||||
|
<div class="flex h-min w-fit ">Telefonkönyv</div>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flex size-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9.75v-4.5m0 4.5h4.5m-4.5 0 6-6m-3 18c-8.284 0-15-6.716-15-15V4.5A2.25 2.25 0 0 1 4.5 2.25h1.372c.516 0 .966.351 1.091.852l1.106 4.423c.11 .44-.054.902-.417 1.173l-1.293.97a1.062 1.062 0 0 0-.38 1.21 12.035 12.035 0 0 0 7.143 7.143c.441.162.928-.004 1.21-.38l.97-1.293a1.125 1.125 0 0 1 1.173-.417l4.423 1.106c.5.125.852.575.852 1.091V19.5a2.25 2.25 0 0 1-2.25 2.25h-2.25Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="userAdmin" class="flex flex-row h-fit w-full border-dashed border-2 border-neutral-300 p-1 rounded-lg justify-between" @click="switchAdmin">
|
||||||
|
<div class="flex h-min w-fit ">Admin</div>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flex size-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-row h-fit w-full border-dashed border-2 border-neutral-300 p-1 rounded-lg justify-between" @click="logout">
|
||||||
|
<button class="flex h-min w-fit">Kijelentkezés</button>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flex size-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M5.636 5.636a9 9 0 1 0 12.728 0M12 3v9" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Oldal -->
|
||||||
|
<div class="flex flex-col w-full h-full bg-neutral-200">
|
||||||
|
<div class="flex px-20 pt-10 pb-5 font-[tahoma] text-5xl"> {{ menuSel }} </div>
|
||||||
|
<hr class="flex border-2 border-black ml-20 mb-6">
|
||||||
|
|
||||||
|
<!-- Contacts -->
|
||||||
|
<div v-if="pageStatus == 'contacts'" class="flex flex-col bg-neutral-50 mx-20 py-5 px-2 h-full">
|
||||||
|
|
||||||
|
<!-- ÚJ KONTAKT LÉTREHOZÁSA -->
|
||||||
|
<form @submit.prevent="createContact" class="flex items-start flex-wrap gap-2 mb-4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label class="text-sm text-neutral-600">Név*</label>
|
||||||
|
<input v-model.trim="newContact.name" class="px-2 py-1 border rounded-lg" placeholder="Teszt Elek" required>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label class="text-sm text-neutral-600">Telefon*</label>
|
||||||
|
<input v-model.trim="newContact.phone" class="px-2 py-1 border rounded-lg" placeholder="+36 30 123 4567" required>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label class="text-sm text-neutral-600">Cím</label>
|
||||||
|
<input v-model.trim="newContact.address" class="px-2 py-1 border rounded-lg" placeholder="Budapest">
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col grow">
|
||||||
|
<label class="text-sm text-neutral-600">Megjegyzés</label>
|
||||||
|
<input v-model.trim="newContact.note" class="px-2 py-1 border rounded-lg" placeholder="VIP">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
:disabled="isCreating || !newContact.name || !newContact.phone"
|
||||||
|
class="px-3 py-2 rounded-lg bg-emerald-600 text-white disabled:opacity-50">
|
||||||
|
Hozzáadás
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span v-if="createMsg" class="text-sm ml-1"
|
||||||
|
:class="createOk ? 'text-emerald-700' : 'text-red-600'">{{ createMsg }}</span>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- TELEFONKÖNYV TÁBLA -->
|
||||||
|
<div class="flex flex-row items-center justify-between mb-3 mt-10">
|
||||||
|
<div class="text-lg font-medium">Kontaktok</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button @click="loadContacts" class="px-3 py-1 rounded-lg bg-neutral-800 text-white">Frissítés</button>
|
||||||
|
<span v-if="isLoadingContacts" class="text-sm opacity-70">Töltés…</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overflow-auto border border-neutral-200 rounded-xl">
|
||||||
|
<table class="min-w-full text-left">
|
||||||
|
<thead class="bg-neutral-100">
|
||||||
|
<tr class="text-sm text-neutral-700">
|
||||||
|
<th class="py-2 px-3 w-10">ID</th>
|
||||||
|
<th class="py-2 px-3">Név</th>
|
||||||
|
<th class="py-2 px-3">Telefon</th>
|
||||||
|
<th class="py-2 px-3">Cím</th>
|
||||||
|
<th class="py-2 px-3">Megjegyzés</th>
|
||||||
|
<th class="py-2 px-3 w-24 text-center">Művelet</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="c in contacts" :key="c.id" class="border-t hover:bg-neutral-50">
|
||||||
|
<td class="py-2 px-3 text-neutral-500">{{ c.id }}</td>
|
||||||
|
|
||||||
|
<!-- Név -->
|
||||||
|
<td class="py-2 px-3" @dblclick="startEdit(c,'name')">
|
||||||
|
<input v-if="isEditing(c,'name')" v-model="editBuffer.name"
|
||||||
|
@keyup.enter="commitEdit" @keyup.esc="cancelEdit" @blur="commitEdit"
|
||||||
|
class="w-full px-2 py-1 border rounded-lg outline-none">
|
||||||
|
<span v-else>{{ c.name }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Telefon -->
|
||||||
|
<td class="py-2 px-3" @dblclick="startEdit(c,'phone')">
|
||||||
|
<input v-if="isEditing(c,'phone')" v-model="editBuffer.phone"
|
||||||
|
@keyup.enter="commitEdit" @keyup.esc="cancelEdit" @blur="commitEdit"
|
||||||
|
class="w-full px-2 py-1 border rounded-lg outline-none">
|
||||||
|
<span v-else>{{ c.phone }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Cím -->
|
||||||
|
<td class="py-2 px-3" @dblclick="startEdit(c,'address')">
|
||||||
|
<input v-if="isEditing(c,'address')" v-model="editBuffer.address"
|
||||||
|
@keyup.enter="commitEdit" @keyup.esc="cancelEdit" @blur="commitEdit"
|
||||||
|
class="w-full px-2 py-1 border rounded-lg outline-none">
|
||||||
|
<span v-else>{{ c.address }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Megjegyzés -->
|
||||||
|
<td class="py-2 px-3" @dblclick="startEdit(c,'note')">
|
||||||
|
<input v-if="isEditing(c,'note')" v-model="editBuffer.note"
|
||||||
|
@keyup.enter="commitEdit" @keyup.esc="cancelEdit" @blur="commitEdit"
|
||||||
|
class="w-full px-2 py-1 border rounded-lg outline-none">
|
||||||
|
<span v-else>{{ c.note }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- Törlés -->
|
||||||
|
<td class="py-2 px-3 text-center">
|
||||||
|
<button @click="deleteContact(c.id)"
|
||||||
|
class="px-3 py-1 rounded-lg bg-red-600 text-white hover:bg-red-700">Törlés</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr v-if="contacts.length===0 && !isLoadingContacts">
|
||||||
|
<td colspan="6" class="py-6 text-center text-neutral-500">Nincs megjeleníthető kontakt.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-neutral-500 mt-2">Tipp: duplán kattints egy mezőre a szerkesztéshez. Enter ment, Esc mégse.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Admin -->
|
||||||
|
<div v-if="pageStatus == 'admin'" class="flex flex-col bg-neutral-50 mx-20 py-5 px-2 h-full">
|
||||||
|
|
||||||
|
<!-- ÚJ FELHASZNÁLÓ LÉTREHOZÁSA -->
|
||||||
|
<form @submit.prevent="createUser" class="flex items-start flex-wrap gap-2 mb-4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label class="text-sm text-neutral-600">Felhasználónév*</label>
|
||||||
|
<input v-model.trim="newUser.uname" class="px-2 py-1 border rounded-lg" placeholder="ujuser" required>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<label class="text-sm text-neutral-600">Jelszó*</label>
|
||||||
|
<input v-model.trim="newUser.pw" type="password" class="px-2 py-1 border rounded-lg" placeholder="••••••" required>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 mt-6 ml-2">
|
||||||
|
<input id="newIsAdmin" type="checkbox" v-model="newUser.admin" class="w-4 h-4">
|
||||||
|
<label for="newIsAdmin" class="text-sm text-neutral-700">Admin</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col grow">
|
||||||
|
<label class="text-sm text-neutral-600">Megjegyzés</label>
|
||||||
|
<input v-model.trim="newUser.note" class="px-2 py-1 border rounded-lg" placeholder="Megjegyzés">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
:disabled="isCreatingUser || !newUser.uname || !newUser.pw"
|
||||||
|
class="px-3 py-2 rounded-lg bg-emerald-600 text-white disabled:opacity-50">
|
||||||
|
Hozzáadás
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<span v-if="createUserMsg" class="text-sm ml-1"
|
||||||
|
:class="createUserOk ? 'text-emerald-700' : 'text-red-600'">{{ createUserMsg }}</span>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<!-- USERS TÁBLA -->
|
||||||
|
<div class="flex flex-row items-center justify-between mb-3 mt-4">
|
||||||
|
<div class="text-lg font-medium">Felhasználók</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button @click="loadUsers" class="px-3 py-1 rounded-lg bg-neutral-800 text-white">Frissítés</button>
|
||||||
|
<span v-if="isLoadingUsers" class="text-sm opacity-70">Töltés…</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overflow-auto border border-neutral-200 rounded-xl">
|
||||||
|
<table class="min-w-full text-left">
|
||||||
|
<thead class="bg-neutral-100">
|
||||||
|
<tr class="text-sm text-neutral-700">
|
||||||
|
<th class="py-2 px-3 w-10">ID</th>
|
||||||
|
<th class="py-2 px-3">Felhasználónév</th>
|
||||||
|
<th class="py-2 px-3">Admin</th>
|
||||||
|
<th class="py-2 px-3">Megjegyzés</th>
|
||||||
|
<th class="py-2 px-3">Jelszó (új)</th>
|
||||||
|
<th class="py-2 px-3 w-24 text-center">Művelet</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="u in users" :key="u.id" class="border-t hover:bg-neutral-50">
|
||||||
|
<td class="py-2 px-3 text-neutral-500">{{ u.id }}</td>
|
||||||
|
|
||||||
|
<!-- uname -->
|
||||||
|
<td class="py-2 px-3" @dblclick="startUserEdit(u,'uname')">
|
||||||
|
<input v-if="isUserEditing(u,'uname')" v-model="userEditBuffer.uname"
|
||||||
|
@keyup.enter="commitUserEdit" @keyup.esc="cancelUserEdit" @blur="commitUserEdit"
|
||||||
|
class="w-full px-2 py-1 border rounded-lg outline-none">
|
||||||
|
<span v-else>{{ u.uname }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- admin -->
|
||||||
|
<td class="py-2 px-3" @dblclick="startUserEdit(u,'admin')">
|
||||||
|
<template v-if="isUserEditing(u,'admin')">
|
||||||
|
<input type="checkbox" v-model="userEditBuffer.admin" @change="commitUserEdit" @keyup.esc="cancelUserEdit" class="w-4 h-4">
|
||||||
|
<span class="ml-2 uppercase">{{ userEditBuffer.admin ? 'igen' : 'nem' }}</span>
|
||||||
|
</template>
|
||||||
|
<span v-else class="uppercase">{{ u.admin ? 'igen' : 'nem' }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- note -->
|
||||||
|
<td class="py-2 px-3" @dblclick="startUserEdit(u,'note')">
|
||||||
|
<input v-if="isUserEditing(u,'note')" v-model="userEditBuffer.note"
|
||||||
|
@keyup.enter="commitUserEdit" @keyup.esc="cancelUserEdit" @blur="commitUserEdit"
|
||||||
|
class="w-full px-2 py-1 border rounded-lg outline-none">
|
||||||
|
<span v-else>{{ u.note }}</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- pw reset -->
|
||||||
|
<td class="py-2 px-3" @dblclick="startUserEdit(u,'pw')">
|
||||||
|
<input v-if="isUserEditing(u,'pw')" v-model="userEditBuffer.pw" type="password" placeholder="Új jelszó"
|
||||||
|
@keyup.enter="commitUserEdit" @keyup.esc="cancelUserEdit" @blur="commitUserEdit"
|
||||||
|
class="w-full px-2 py-1 border rounded-lg outline-none">
|
||||||
|
<span v-else class="text-neutral-400 italic">—</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<!-- törlés -->
|
||||||
|
<td class="py-2 px-3 text-center">
|
||||||
|
<button @click="deleteUser(u.id)"
|
||||||
|
class="px-3 py-1 rounded-lg bg-red-600 text-white hover:bg-red-700">Törlés</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr v-if="users.length===0 && !isLoadingUsers">
|
||||||
|
<td colspan="6" class="py-6 text-center text-neutral-500">Nincs felhasználó.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-xs text-neutral-500 mt-2">
|
||||||
|
Tipp: duplán katt egy mezőre a szerkesztéshez. A „Jelszó (új)” oszlopban megadott érték azonnal új jelszó lesz.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Dashboard oldal nézet -->
|
|
||||||
<div v-else-if="pageStatus=='contacts'" class="flex flex-row w-full h-full bg-red-300">
|
|
||||||
<!-- Menü -->
|
|
||||||
<div class="flex flex-col w-1/6 h-full bg-neutral-400 py-10 px-3 space-y-3">
|
|
||||||
|
|
||||||
<!-- Menü Item -->
|
|
||||||
<div class="flex flex-row h-fit w-full border-dashed border-2 border-neutral-300 p-1 rounded-lg justify-between">
|
|
||||||
<div class="flex h-min w-fit ">Telefonkönyv</div>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flex size-6">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9.75v-4.5m0 4.5h4.5m-4.5 0 6-6m-3 18c-8.284 0-15-6.716-15-15V4.5A2.25 2.25 0 0 1 4.5 2.25h1.372c.516 0 .966.351 1.091.852l1.106 4.423c.11.44-.054.902-.417 1.173l-1.293.97a1.062 1.062 0 0 0-.38 1.21 12.035 12.035 0 0 0 7.143 7.143c.441.162.928-.004 1.21-.38l.97-1.293a1.125 1.125 0 0 1 1.173-.417l4.423 1.106c.5.125.852.575.852 1.091V19.5a2.25 2.25 0 0 1-2.25 2.25h-2.25Z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Menü Item -->
|
|
||||||
<div class="flex flex-row h-fit w-full border-dashed border-2 border-neutral-300 p-1 rounded-lg justify-between">
|
|
||||||
<div class="flex h-min w-fit ">Admin</div>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flex size-6">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1 1 21.75 8.25Z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Menü Item -->
|
|
||||||
<div class="flex flex-row h-fit w-full border-dashed border-2 border-neutral-300 p-1 rounded-lg justify-between">
|
|
||||||
<div class="flex h-min w-fit ">Kijelentkezés</div>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="flex size-6">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5.636 5.636a9 9 0 1 0 12.728 0M12 3v9" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!-- Oldal -->
|
|
||||||
<div class="flex flex-col w-full h-full bg-neutral-200">
|
|
||||||
<div class="flex px-20 pt-10 pb-5 font-[tahoma] text-5xl "> {{ menuSel }} </div>
|
|
||||||
<hr class="flex border-2 border-black ml-20 mb-6">
|
|
||||||
|
|
||||||
<!-- Contents -->
|
|
||||||
<div class="flex flex-col bg-neutral-50 mx-20 py-5 px-2 h-full ">
|
|
||||||
asd
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -84,59 +287,348 @@ const loginCheck = ref(false)
|
|||||||
const loginErrorMsg = ref("Bejelentkezés...")
|
const loginErrorMsg = ref("Bejelentkezés...")
|
||||||
const userLoginName = ref("")
|
const userLoginName = ref("")
|
||||||
const userLoginPw = ref("")
|
const userLoginPw = ref("")
|
||||||
const userName = ref("")
|
|
||||||
const userPassw = ref("")
|
|
||||||
const userAdmin = ref(false)
|
const userAdmin = ref(false)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const menuSel = ref("Telefonkönyv") // Telefonkönyv, Admin, Kijelentkezés
|
const menuSel = ref("Telefonkönyv") // Telefonkönyv, Admin, Kijelentkezés
|
||||||
|
|
||||||
|
document.title = import.meta.env.VITE_PAGE_TITLE || "Telefonkönyv?"
|
||||||
|
|
||||||
|
// --- Telefonkönyv állapot ---
|
||||||
|
const contacts = ref([])
|
||||||
|
const isLoadingContacts = ref(false)
|
||||||
|
const editing = ref({ id: null, field: null })
|
||||||
|
const editBuffer = ref({ id: null, name: "", phone: "", address: "", note: "" })
|
||||||
|
|
||||||
|
// Új kontakt űrlap state
|
||||||
|
const newContact = ref({ name: '', phone: '', address: '', note: '' })
|
||||||
|
const isCreating = ref(false)
|
||||||
|
const createMsg = ref('')
|
||||||
|
const createOk = ref(false)
|
||||||
|
|
||||||
|
// --- Admin / users állapot ---
|
||||||
|
const users = ref([])
|
||||||
|
const isLoadingUsers = ref(false)
|
||||||
|
const userEditing = ref({ id: null, field: null })
|
||||||
|
const userEditBuffer = ref({ id: null, uname: '', admin: false, note: '', pw: '' })
|
||||||
|
|
||||||
|
// Új user űrlap
|
||||||
|
const newUser = ref({ uname: '', pw: '', admin: false, note: '' })
|
||||||
|
const isCreatingUser = ref(false)
|
||||||
|
const createUserMsg = ref('')
|
||||||
|
const createUserOk = ref(false)
|
||||||
|
|
||||||
|
async function loadUsers() {
|
||||||
|
if (!userAdmin.value) return
|
||||||
|
isLoadingUsers.value = true
|
||||||
|
try {
|
||||||
|
const res = await getWithBody(`${apiBase}/users`, {
|
||||||
|
authUname: userLoginName.value,
|
||||||
|
authPw: userLoginPw.value
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
if (data.ok) {
|
||||||
|
users.value = (data.users || []).map(u => ({ ...u, admin: !!u.admin }))
|
||||||
|
} else {
|
||||||
|
users.value = []
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
users.value = []
|
||||||
|
} finally {
|
||||||
|
isLoadingUsers.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createUser() {
|
||||||
|
if (!newUser.value.uname || !newUser.value.pw) return
|
||||||
|
isCreatingUser.value = true
|
||||||
|
createUserMsg.value = ''
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${apiBase}/users`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
authUname: userLoginName.value,
|
||||||
|
authPw: userLoginPw.value,
|
||||||
|
uname: newUser.value.uname,
|
||||||
|
pw: newUser.value.pw,
|
||||||
|
admin: !!newUser.value.admin,
|
||||||
|
note: newUser.value.note || null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
if (data.ok) {
|
||||||
|
createUserOk.value = true
|
||||||
|
createUserMsg.value = 'Felhasználó hozzáadva'
|
||||||
|
users.value.unshift({
|
||||||
|
id: data.id,
|
||||||
|
uname: newUser.value.uname,
|
||||||
|
admin: !!newUser.value.admin,
|
||||||
|
note: newUser.value.note || null
|
||||||
|
})
|
||||||
|
newUser.value = { uname: '', pw: '', admin: false, note: '' }
|
||||||
|
} else {
|
||||||
|
createUserOk.value = false
|
||||||
|
createUserMsg.value = data.error || 'Hiba történt'
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
createUserOk.value = false
|
||||||
|
createUserMsg.value = 'Hálózati hiba'
|
||||||
|
} finally {
|
||||||
|
isCreatingUser.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function patchUser(id, patch) {
|
||||||
|
const res = await fetch(`${apiBase}/users`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
authUname: userLoginName.value,
|
||||||
|
authPw: userLoginPw.value,
|
||||||
|
id,
|
||||||
|
...patch
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return res.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteUser(id) {
|
||||||
|
if (!confirm(`Biztosan törlöd a(z) #${id} felhasználót?`)) return
|
||||||
|
const res = await fetch(`${apiBase}/users`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
authUname: userLoginName.value,
|
||||||
|
authPw: userLoginPw.value,
|
||||||
|
id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
if (data.ok) {
|
||||||
|
users.value = users.value.filter(u => u.id !== id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startUserEdit(row, field) {
|
||||||
|
userEditing.value = { id: row.id, field }
|
||||||
|
userEditBuffer.value = { id: row.id, uname: row.uname, admin: !!row.admin, note: row.note ?? '', pw: '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
function isUserEditing(row, field) {
|
||||||
|
return userEditing.value.id === row.id && userEditing.value.field === field
|
||||||
|
}
|
||||||
|
|
||||||
|
async function commitUserEdit() {
|
||||||
|
if (!userEditing.value.id) return
|
||||||
|
const { id, field } = userEditing.value
|
||||||
|
let val = userEditBuffer.value[field]
|
||||||
|
|
||||||
|
// speciális esetek
|
||||||
|
if (field === 'admin') val = !!val
|
||||||
|
if (field === 'pw') {
|
||||||
|
if (!val) { cancelUserEdit(); return }
|
||||||
|
const res = await patchUser(id, { pw: val })
|
||||||
|
if (res.ok) userEditBuffer.value.pw = ''
|
||||||
|
userEditing.value = { id: null, field: null }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await patchUser(id, { [field]: val })
|
||||||
|
if (res.ok) {
|
||||||
|
const idx = users.value.findIndex(u => u.id === id)
|
||||||
|
if (idx !== -1) users.value[idx][field] = val
|
||||||
|
}
|
||||||
|
userEditing.value = { id: null, field: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelUserEdit() {
|
||||||
|
userEditing.value = { id: null, field: null }
|
||||||
|
userEditBuffer.value.pw = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function createContact () {
|
||||||
|
if (!newContact.value.name || !newContact.value.phone) return
|
||||||
|
isCreating.value = true
|
||||||
|
createMsg.value = ''
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${apiBase}/contacts`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
authUname: userLoginName.value,
|
||||||
|
authPw: userLoginPw.value,
|
||||||
|
name: newContact.value.name,
|
||||||
|
phone: newContact.value.phone,
|
||||||
|
address: newContact.value.address || null,
|
||||||
|
note: newContact.value.note || null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
if (data.ok) {
|
||||||
|
createOk.value = true
|
||||||
|
createMsg.value = 'Hozzáadva'
|
||||||
|
contacts.value.unshift({
|
||||||
|
id: data.id,
|
||||||
|
name: newContact.value.name,
|
||||||
|
phone: newContact.value.phone,
|
||||||
|
address: newContact.value.address || null,
|
||||||
|
note: newContact.value.note || null
|
||||||
|
})
|
||||||
|
newContact.value = { name: '', phone: '', address: '', note: '' }
|
||||||
|
} else {
|
||||||
|
createOk.value = false
|
||||||
|
createMsg.value = data.error || 'Hiba történt'
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
createOk.value = false
|
||||||
|
createMsg.value = 'Hálózati hiba'
|
||||||
|
} finally {
|
||||||
|
isCreating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchAdmin(){
|
||||||
|
pageStatus.value = "admin"
|
||||||
|
menuSel.value = "Admin"
|
||||||
|
}
|
||||||
|
|
||||||
|
function switchContacts(){
|
||||||
|
pageStatus.value = "contacts"
|
||||||
|
menuSel.value = "Telefonkönyv"
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getWithBody(url, bodyObj) {
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'X-HTTP-Method-Override': 'GET', 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(bodyObj)
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadContacts() {
|
||||||
|
isLoadingContacts.value = true
|
||||||
|
console.log(`${apiBase}/contacts`)
|
||||||
|
try {
|
||||||
|
const res = await getWithBody(`${apiBase}/contacts`, {
|
||||||
|
authUname: userLoginName.value,
|
||||||
|
authPw: userLoginPw.value
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
if (data.ok) contacts.value = data.contacts || []
|
||||||
|
else contacts.value = []
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
contacts.value = []
|
||||||
|
} finally {
|
||||||
|
isLoadingContacts.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function patchContact(id, patch) {
|
||||||
|
const res = await fetch(`${apiBase}/contacts`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
authUname: userLoginName.value,
|
||||||
|
authPw: userLoginPw.value,
|
||||||
|
id,
|
||||||
|
...patch
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return res.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteContact(id) {
|
||||||
|
if (!confirm(`Biztosan törlöd a(z) #${id} kontaktot?`)) return
|
||||||
|
const res = await fetch(`${apiBase}/contacts`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
authUname: userLoginName.value,
|
||||||
|
authPw: userLoginPw.value,
|
||||||
|
id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const data = await res.json()
|
||||||
|
if (data.ok) {
|
||||||
|
contacts.value = contacts.value.filter(c => c.id !== id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Inline edit kezelése ---
|
||||||
|
function startEdit(row, field) {
|
||||||
|
editing.value = { id: row.id, field }
|
||||||
|
editBuffer.value = { ...row }
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEditing(row, field) {
|
||||||
|
return editing.value.id === row.id && editing.value.field === field
|
||||||
|
}
|
||||||
|
|
||||||
|
async function commitEdit() {
|
||||||
|
if (!editing.value.id) return
|
||||||
|
const { id, field } = editing.value
|
||||||
|
const value = editBuffer.value[field]
|
||||||
|
const data = await patchContact(id, { [field]: value })
|
||||||
|
if (data.ok) {
|
||||||
|
// lokális frissítés
|
||||||
|
const idx = contacts.value.findIndex(c => c.id === id)
|
||||||
|
if (idx !== -1) contacts.value[idx][field] = value
|
||||||
|
}
|
||||||
|
editing.value = { id: null, field: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelEdit() {
|
||||||
|
editing.value = { id: null, field: null }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Alap (health + auth) ---
|
||||||
async function checkHealth() {
|
async function checkHealth() {
|
||||||
out.value = 'Hívás...'
|
out.value = 'Hívás...'
|
||||||
const res = await fetch(`${apiBase}/`)
|
const res = await fetch(`${apiBase}/`)
|
||||||
out.value = await res.json()
|
out.value = await res.json()
|
||||||
console.log(out.value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function authUser(u, p) {
|
async function authUser(u, p) {
|
||||||
out.value = 'Hívás...'
|
|
||||||
const res = await fetch(`${apiBase}/auth`, {
|
const res = await fetch(`${apiBase}/auth`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ authUname: u, authPw: p })
|
body: JSON.stringify({ authUname: u, authPw: p })
|
||||||
})
|
})
|
||||||
out.value = await res.json()
|
const data = await res.json()
|
||||||
console.log(out.value)
|
if (data.ok === true) {
|
||||||
|
|
||||||
if (out.value.ok == true) {
|
|
||||||
pageStatus.value = "contacts"
|
pageStatus.value = "contacts"
|
||||||
userAdmin.value = out.value.admin
|
userAdmin.value = data.admin
|
||||||
|
await loadContacts()
|
||||||
|
loginErrorMsg.value = "Sikeres bejelentkezés"
|
||||||
} else {
|
} else {
|
||||||
loginErrorMsg.value = "Hiba..."
|
loginErrorMsg.value = "Hibás felhasználónév vagy jelszó"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loginUser() {
|
async function loginUser() {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
console.log(`Authenticateing user ${userLoginName.value}`)
|
await authUser(userLoginName.value, userLoginPw.value)
|
||||||
authUser(userLoginName.value, userLoginPw.value)
|
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// async IIFE
|
function logout() {
|
||||||
(async () => {
|
pageStatus.value = "logout"
|
||||||
out.value = ""
|
contacts.value = []
|
||||||
await checkHealth()
|
userAdmin.value = false
|
||||||
if (out.value.status == "ok") {
|
userLoginPw.value = ""
|
||||||
loginCheck.value = true
|
loginErrorMsg.value = "Bejelentkezés..."
|
||||||
console.log("[API] ok")
|
}
|
||||||
}
|
|
||||||
else {
|
|
||||||
loginCheck.value = false
|
|
||||||
console.log("[API] not ok")
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
// async IIFE
|
||||||
|
;(async () => {
|
||||||
|
await checkHealth()
|
||||||
|
loginCheck.value = out.value?.status === "ok"
|
||||||
|
console.log(loginCheck.value ? "[API] ok" : "[API] not ok")
|
||||||
|
})()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user