Skip to content

Commit 957a852

Browse files
committed
feature:default camera permissions
เพิ่ม ui ของหน้า default camera permissions และทำ api updateAllPermissionsDefault เพื่ออัปเดตสิทธิ์การเข้าถึงของกล้องทั้งหมดเป็นค่าตาม default
1 parent d07b2fc commit 957a852

File tree

5 files changed

+219
-3
lines changed

5 files changed

+219
-3
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { Shield, LockKeyhole, UserLock, Clock } from "lucide-react";
5+
import { Separator } from "@/components/ui/separator";
6+
import { Button } from "@/components/ui/button";
7+
8+
/* --- UI: Section Card --- */
9+
function SectionCard({ title, icon: Icon, children }: any) {
10+
return (
11+
<div className="rounded-2xl border border-slate-200 bg-white shadow-sm p-5">
12+
<div className="flex items-center gap-2 mb-4">
13+
<Icon className="w-5 h-5 text-[var(--color-primary)]" />
14+
<h2 className="font-semibold text-[var(--color-primary)] text-base">{title}</h2>
15+
</div>
16+
{children}
17+
</div>
18+
);
19+
}
20+
21+
/* --- UI: Toggle --- */
22+
const Toggle = ({ label, enabled, setEnabled, hint }: any) => (
23+
<div className="py-2">
24+
<div className="grid grid-cols-[1fr_auto] items-center gap-3">
25+
<div>
26+
<div className="text-slate-800 text-sm sm:text-base">{label}</div>
27+
{hint && <div className="text-slate-500 text-xs mt-0.5">{hint}</div>}
28+
</div>
29+
30+
<button
31+
type="button"
32+
role="switch"
33+
aria-checked={enabled}
34+
onClick={() => setEnabled(!enabled)}
35+
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${enabled ? "bg-[var(--color-primary)]/90" : "bg-slate-300"
36+
}`}
37+
>
38+
<span
39+
className={`inline-block h-4 w-4 transform bg-white rounded-full transition ${enabled ? "translate-x-5" : "translate-x-1"
40+
}`}
41+
/>
42+
</button>
43+
</div>
44+
45+
<div className="border-b border-slate-100 mt-3" />
46+
</div>
47+
);
48+
49+
/* --- Main Component --- */
50+
export default function DefaultCameraPermissions() {
51+
// ค่า default
52+
const [requireAuth, setRequireAuth] = useState(false);
53+
const [restrictRole, setRestrictRole] = useState(false);
54+
const [logAccess, setLogAccess] = useState(false);
55+
56+
const [saving, setSaving] = useState(false);
57+
const [msg, setMsg] = useState("");
58+
59+
async function saveAllDefaults() {
60+
setSaving(true);
61+
setMsg("");
62+
63+
try {
64+
const res = await fetch("/api/cameras/permissions/default", {
65+
method: "PUT",
66+
headers: { "Content-Type": "application/json" },
67+
credentials: "include",
68+
body: JSON.stringify({
69+
require_auth: requireAuth,
70+
restrict: restrictRole,
71+
log: logAccess,
72+
}),
73+
});
74+
75+
const json = await res.json().catch(() => ({}));
76+
if (!res.ok) throw new Error(json?.message || "Update failed");
77+
78+
setMsg("Default permissions updated successfully!");
79+
} catch (err: any) {
80+
setMsg(err?.message || "Failed to update");
81+
} finally {
82+
setSaving(false);
83+
}
84+
}
85+
86+
return (
87+
<div className="w-full">
88+
<label className="font-bold text-lg text-[var(--color-primary)]">Default Camera Permissions</label>
89+
{/* Require Authentication */}
90+
<Toggle
91+
label={
92+
<span className="flex items-center gap-2">
93+
<LockKeyhole className="w-5 h-5 text-[var(--color-primary)]" />
94+
Require Authentication
95+
</span>
96+
}
97+
hint="Users must log in before accessing any camera."
98+
enabled={requireAuth}
99+
setEnabled={setRequireAuth}
100+
/>
101+
102+
{/* Restrict Role */}
103+
<Toggle
104+
label={
105+
<span className="flex items-center gap-2">
106+
<UserLock className="w-5 h-5 text-[var(--color-primary)]" />
107+
Restrict Role
108+
</span>
109+
}
110+
hint="Only allowed roles can access cameras."
111+
enabled={restrictRole}
112+
setEnabled={setRestrictRole}
113+
/>
114+
115+
{/* Log Access */}
116+
<Toggle
117+
label={
118+
<span className="flex items-center gap-2">
119+
<Clock className="w-5 h-5 text-[var(--color-primary)]" />
120+
Log Access Attempts
121+
</span>
122+
}
123+
hint="Track all camera access attempts."
124+
enabled={logAccess}
125+
setEnabled={setLogAccess}
126+
/>
127+
128+
{/* Set All to Default */}
129+
<div className="py-2">
130+
<div className="grid grid-cols-[1fr_auto] items-center gap-3">
131+
<div>
132+
<div className="text-slate-800 text-sm sm:text-base flex items-center gap-2">
133+
<Shield className="w-5 h-5 text-[var(--color-primary)]" />
134+
Set All to Default
135+
</div>
136+
<div className="text-slate-500 text-xs mt-0.5">
137+
Update default permissions for all cameras
138+
</div>
139+
</div>
140+
<Button
141+
onClick={saveAllDefaults}
142+
disabled={saving}
143+
className="bg-[#0077FF] text-white hover:bg-[#0063d6]"
144+
>
145+
Setting default all cameras
146+
</Button>
147+
</div>
148+
<div className="border-b border-slate-100 mt-3" />
149+
</div>
150+
151+
{msg && <p className="text-sm mt-3 text-slate-700">{msg}</p>}
152+
</div>
153+
);
154+
}

client/my-app/src/components/features/settings/DefaultCameras.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
"use client";
22

3-
3+
import DefaultCameraPermissions from "./DefaultCameraPermissions";
44
import CameraPermissions from "./CameraPermissionsTable";
55
import { Separator } from "@/components/ui/separator";
66

77
export default function DefaultCameras() {
88
return (
99
<div className="w-full rounded-xl border shadow-sm p-5 bg-white">
10-
10+
<DefaultCameraPermissions/>
1111
<Separator className="my-4 bg-slate-200" />
1212
<CameraPermissions />
1313
</div>

server/src/controllers/cameras.controller.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,4 +645,33 @@ export async function getCameraPermission(req: Request, res: Response, next: Nex
645645
} catch (err) {
646646
next(err);
647647
}
648-
}
648+
}
649+
650+
/**
651+
* อัปเดตสิทธิ์การเข้าถึงของกล้องทั้งหมดเป็นค่า default
652+
* ใช้สำหรับตั้งค่า require_auth, restrict, log ให้กับทุกกล้องในระบบ
653+
*
654+
* @param {Request} req - Request ที่มีข้อมูลใน body (require_auth, restrict, log)
655+
* @param {Response} res - Response สำหรับส่งผลลัพธ์การอัปเดตกลับไปยัง client
656+
* @param {NextFunction} next - Middleware สำหรับส่งต่อ error หากเกิดข้อผิดพลาด
657+
* @returns {Promise<Response>} ส่งคืนรายการสิทธิ์กล้องทั้งหมดหลังจากอัปเดต
658+
* @throws {Error} ถ้าเกิดข้อผิดพลาดระหว่างการอัปเดตในฐานข้อมูล
659+
*
660+
* @author Wongsakon
661+
* @lastModified 2025-12-05
662+
*/
663+
export async function updateAllPermissionsDefault(req: Request, res: Response, next: NextFunction) {
664+
try {
665+
const { require_auth, restrict, log } = req.body;
666+
667+
// เรียก service ใหม่ที่ทำ batch update
668+
const updatedList = await AccessControlService.updateAllPermissionsDefault(require_auth, restrict, log);
669+
670+
return res.status(200).json({
671+
message: 'Updated default permissions for all cameras successfully',
672+
data: updatedList
673+
});
674+
} catch (err) {
675+
next(err);
676+
}
677+
}

server/src/routes/cameras.routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ router.patch('/maintenance/:mnt_id', ctrl.softDeleteMaintenance);
8282
/* ========================== Access Control ========================== */
8383
router.get('/:cam_id/permission', ctrl.getPermissionByCameraId);
8484
router.put('/:cam_id/permission', ctrl.updatePermission);
85+
router.put('/permissions/default', ctrl.updateAllPermissionsDefault);
8586

8687

8788
export default router;

server/src/services/cameras/accessControl.service.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,36 @@ export async function getCameraPermission() {
9595

9696
const result = await pool.query(query);
9797
return result.rows;
98+
}
99+
100+
/**
101+
* อัปเดตสิทธิ์การเข้าถึงของกล้องทั้งหมดเป็นค่า default
102+
* ใช้สำหรับตั้งค่า require_auth, restrict, log ให้กับกล้องทุกตัวที่ใช้งานอยู่ในระบบ
103+
*
104+
* @param {boolean} require_auth - ระบุว่ากล้องทุกตัวต้องมีการยืนยันตัวตนก่อนเข้าถึงหรือไม่
105+
* @param {boolean} restrict - ระบุว่าจำกัดสิทธิ์การเข้าถึงเฉพาะบทบาทบางประเภทหรือไม่
106+
* @param {boolean} log - ระบุว่าต้องการบันทึกการเข้าถึงของผู้ใช้ทุกตัวหรือไม่
107+
* @returns {Promise<Model.CameraPermission[]>} รายการสิทธิ์การเข้าถึงของกล้องทั้งหมดหลังจากอัปเดต
108+
* @throws {Error} ถ้าเกิดข้อผิดพลาดระหว่างการอัปเดตในฐานข้อมูล
109+
*
110+
* @author Wongsakon
111+
* @lastModified 2025-12-05
112+
*/
113+
export async function updateAllPermissionsDefault(
114+
require_auth: boolean,
115+
restrict: boolean,
116+
log: boolean
117+
) {
118+
const { rows } = await pool.query(`
119+
UPDATE camera_permissions
120+
SET
121+
cap_require_auth = $1,
122+
cap_restrict = $2,
123+
cap_log = $3,
124+
cap_updated_at = CURRENT_TIMESTAMP
125+
WHERE cap_is_use = true
126+
RETURNING *;
127+
`, [require_auth, restrict, log]);
128+
129+
return rows.map(Mapping.mapPermissionToSaveResponse);
98130
}

0 commit comments

Comments
 (0)