diff --git a/examples/YOLO_server_api/frontend/dist/assets/detection-A_EFsCI6.js b/examples/YOLO_server_api/frontend/dist/assets/detection-A_EFsCI6.js deleted file mode 100644 index bc41aa1..0000000 --- a/examples/YOLO_server_api/frontend/dist/assets/detection-A_EFsCI6.js +++ /dev/null @@ -1,2 +0,0 @@ -import{checkAccess as H,showAppropriateLinks as R,authHeaders as N}from"./common-Ceipz8Vt.js";import"./headerFooterLoader-LYlhFStm.js";const P="/api";document.addEventListener("DOMContentLoaded",()=>{H([]),R(),document.getElementById("logout-btn");const k=document.getElementById("detection-form"),m=document.getElementById("detection-error"),u=document.getElementById("detection-result"),r=document.getElementById("file-drop-area"),f=document.getElementById("image-input"),y=document.getElementById("remove-image-btn");let p=0,E=0;function L(){const e=document.getElementById("image-canvas");e.getContext("2d").clearRect(0,0,e.width,e.height),f.value="",u.textContent="",m.textContent="",y.style.display="none"}y.addEventListener("click",()=>{L()}),document.querySelector(".choose-file-btn").addEventListener("click",e=>{e.preventDefault(),f.click()}),r.addEventListener("dragover",e=>{e.preventDefault(),r.classList.add("dragover")}),r.addEventListener("dragleave",()=>{r.classList.remove("dragover")}),r.addEventListener("drop",e=>{if(e.preventDefault(),r.classList.remove("dragover"),e.dataTransfer.files&&e.dataTransfer.files.length>0){const a=e.dataTransfer.files[0];f.files=e.dataTransfer.files,x(a)}}),f.addEventListener("change",e=>{e.target.files&&e.target.files[0]&&x(e.target.files[0])});function x(e){const a=new FileReader;a.onload=()=>{const t=document.getElementById("image-canvas"),o=t.getContext("2d"),n=new Image;n.onload=()=>{p=n.width,E=n.height;const c=r.clientWidth-40,d=r.clientHeight-40;let{width:i,height:s}=n;if(i>c){const l=c/i;i=c,s*=l}if(s>d){const l=d/s;s=d,i*=l}t.width=i,t.height=s,o.clearRect(0,0,t.width,t.height),o.drawImage(n,0,0,i,s),y.style.display="inline-block"},n.src=a.result},a.readAsDataURL(e)}k.addEventListener("submit",async e=>{e.preventDefault(),m.textContent="",u.textContent="";const a=document.getElementById("model-select").value,t=f.files[0];if(!t){m.textContent="Please select an image.";return}const o=new FormData;o.append("image",t),o.append("model",a);try{const n=await fetch(`${P}/detect`,{method:"POST",headers:N(),body:o});if(!n.ok){const d=await n.json();m.textContent=d.detail||"Detection failed.";return}const c=await n.json();B(c),S(c)}catch(n){console.error(n),m.textContent="Error performing detection."}});function B(e){const a=document.getElementById("image-canvas"),t=a.getContext("2d"),o=["Hardhat","Mask","NO-Hardhat","NO-Mask","NO-Safety Vest","Person","Safety Cone","Safety Vest","machinery","vehicle"],n={Hardhat:"green","Safety Vest":"green",machinery:"yellow",vehicle:"yellow","NO-Hardhat":"red","NO-Safety Vest":"red",Person:"orange","Safety Cone":"pink"};e.forEach(([c,d,i,s,l,b])=>{const v=o[b],w=n[v]||"blue",I=a.width/p,C=a.height/E,g=c*I,h=d*C,O=i*I,D=s*C;t.strokeStyle=w,t.lineWidth=2,t.strokeRect(g,h,O-g,D-h),t.fillStyle=w,t.fillRect(g,h-20,t.measureText(v).width+10,20),t.fillStyle="black",t.font="14px Arial",t.fillText(v,g+5,h-5)})}function S(e){const a={},t=["Hardhat","Mask","NO-Hardhat","NO-Mask","NO-Safety Vest","Person","Safety Cone","Safety Vest","machinery","vehicle"];t.forEach(o=>a[o]=0),e.forEach(([o,n,c,d,i,s])=>{const l=t[s];l&&(a[l]+=1)}),u.textContent=Object.entries(a).filter(([o,n])=>n>0).map(([o,n])=>`${o}: ${n}`).join(` -`)}}); diff --git a/examples/YOLO_server_api/frontend/dist/assets/detection-CjEUTsGE.js b/examples/YOLO_server_api/frontend/dist/assets/detection-CjEUTsGE.js new file mode 100644 index 0000000..639b960 --- /dev/null +++ b/examples/YOLO_server_api/frontend/dist/assets/detection-CjEUTsGE.js @@ -0,0 +1,2 @@ +import{checkAccess as I,showAppropriateLinks as E,authHeaders as w}from"./common-Ceipz8Vt.js";import"./headerFooterLoader-CkcITKwD.js";const B="/api";let m=0,f=0;document.addEventListener("DOMContentLoaded",()=>{I([]),E();const e=document.getElementById("logout-btn"),n=document.getElementById("detection-form"),t=document.getElementById("detection-error"),o=document.getElementById("detection-result"),a=document.getElementById("file-drop-area"),c=document.getElementById("image-input"),s=document.getElementById("remove-image-btn"),i=document.querySelector(".choose-file-btn");C(e),L(s),S(i,c),k(a,c),b(c,a,s),D(n,c,t,o)});function C(e){e&&e.addEventListener("click",()=>{window.location.href="/login.html"})}function L(e){e.addEventListener("click",N)}function S(e,n){e&&e.addEventListener("click",t=>{t.preventDefault(),n.click()})}function k(e,n){e.addEventListener("dragover",O),e.addEventListener("dragleave",R),e.addEventListener("drop",t=>x(t,e,n))}function b(e,n,t){e.addEventListener("change",o=>{const a=o.target.files&&o.target.files[0];a&&g(a,n,t)})}function D(e,n,t,o){e.addEventListener("submit",async a=>{a.preventDefault(),H(t,o);const c=document.getElementById("model-select").value,s=n.files[0];if(!s){t.textContent="Please select an image.";return}await V(s,c,t,o)})}function O(e){e.preventDefault(),e.currentTarget.classList.add("dragover")}function R(e){e.currentTarget.classList.remove("dragover")}function x(e,n,t){e.preventDefault(),n.classList.remove("dragover");const o=e.dataTransfer.files&&e.dataTransfer.files[0];if(o){t.files=e.dataTransfer.files;const a=document.getElementById("remove-image-btn");g(o,n,a)}}function H(e,n){e.textContent="",n.textContent=""}function N(){const e=document.getElementById("image-canvas");e.getContext("2d").clearRect(0,0,e.width,e.height);const t=document.getElementById("image-input");t.value="";const o=document.getElementById("detection-result"),a=document.getElementById("detection-error");o.textContent="",a.textContent="";const c=document.getElementById("remove-image-btn");c.style.display="none"}function g(e,n,t){const o=new FileReader;o.onload=()=>P(o.result,n,t),o.readAsDataURL(e)}function P(e,n,t){const o=new Image;o.onload=()=>{m=o.width,f=o.height,T(o,n),t.style.display="inline-block"},o.src=e}function T(e,n){const t=document.getElementById("image-canvas"),o=t.getContext("2d"),{width:a,height:c}=F(e,n);t.width=a,t.height=c,o.clearRect(0,0,t.width,t.height),o.drawImage(e,0,0,a,c)}function F(e,n){const t=n.clientWidth-40,o=n.clientHeight-40;let{width:a,height:c}=e;return{width:a,height:c}=M(a,c,t,o),{width:a,height:c}}function M(e,n,t,o){if(e>t){const a=t/e;e=t,n*=a}if(n>o){const a=o/n;n=o,e*=a}return{width:e,height:n}}async function V(e,n,t,o){const a=new FormData;a.append("image",e),a.append("model",n);try{const c=await fetch(`${B}/detect`,{method:"POST",headers:w(),body:a});if(!c.ok){const i=await c.json();t.textContent=i.detail||"Detection failed.";return}const s=await c.json();j(s),W(s,o)}catch(c){console.error(c),t.textContent="Error performing detection."}}function j(e){const n=document.getElementById("image-canvas"),t=n.getContext("2d");e.forEach(o=>X(t,o,n))}function X(e,n,t){const o=["Hardhat","Mask","NO-Hardhat","NO-Mask","NO-Safety Vest","Person","Safety Cone","Safety Vest","machinery","vehicle"],a={Hardhat:"green","Safety Vest":"green",machinery:"yellow",vehicle:"yellow","NO-Hardhat":"red","NO-Safety Vest":"red",Person:"orange","Safety Cone":"pink"},[c,s,i,h,A,y]=n,r=o[y],d=a[r]||"blue",{scaledX1:l,scaledY1:u,scaledX2:v,scaledY2:p}=Y(c,s,i,h,t);$(e,l,u,v,p,d),U(e,r,l,u,d)}function Y(e,n,t,o,a){const c=a.width/m,s=a.height/f;return{scaledX1:e*c,scaledY1:n*s,scaledX2:t*c,scaledY2:o*s}}function $(e,n,t,o,a,c){e.strokeStyle=c,e.lineWidth=2,e.strokeRect(n,t,o-n,a-t)}function U(e,n,t,o,a){e.fillStyle=a,e.fillRect(t,o-20,e.measureText(n).width+10,20),e.fillStyle="black",e.font="14px Arial",e.fillText(n,t+5,o-5)}function W(e,n){const t=["Hardhat","Mask","NO-Hardhat","NO-Mask","NO-Safety Vest","Person","Safety Cone","Safety Vest","machinery","vehicle"],o=_(t);q(e,t,o),z(n,o)}function _(e){const n={};return e.forEach(t=>n[t]=0),n}function q(e,n,t){e.forEach(([,,,,,o])=>{const a=n[o];a&&(t[a]+=1)})}function z(e,n){e.textContent=Object.entries(n).filter(([t,o])=>o>0).map(([t,o])=>`${t}: ${o}`).join(` +`)} diff --git a/examples/YOLO_server_api/frontend/dist/assets/headerFooterLoader-CkcITKwD.js b/examples/YOLO_server_api/frontend/dist/assets/headerFooterLoader-CkcITKwD.js new file mode 100644 index 0000000..906962a --- /dev/null +++ b/examples/YOLO_server_api/frontend/dist/assets/headerFooterLoader-CkcITKwD.js @@ -0,0 +1 @@ +const g="modulepreload",E=function(e){return"/"+e},u={},y=function(t,o,l){let i=Promise.resolve();if(o&&o.length>0){document.getElementsByTagName("link");const n=document.querySelector("meta[property=csp-nonce]"),r=(n==null?void 0:n.nonce)||(n==null?void 0:n.getAttribute("nonce"));i=Promise.allSettled(o.map(c=>{if(c=E(c),c in u)return;u[c]=!0;const s=c.endsWith(".css"),m=s?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${c}"]${m}`))return;const a=document.createElement("link");if(a.rel=s?"stylesheet":g,s||(a.as="script"),a.crossOrigin="",a.href=c,r&&a.setAttribute("nonce",r),document.head.appendChild(a),s)return new Promise((f,h)=>{a.addEventListener("load",f),a.addEventListener("error",()=>h(new Error(`Unable to preload CSS for ${c}`)))})}))}function d(n){const r=new Event("vite:preloadError",{cancelable:!0});if(r.payload=n,window.dispatchEvent(r),!r.defaultPrevented)throw n}return i.then(n=>{for(const r of n||[])r.status==="rejected"&&d(r.reason);return t().catch(d)})};document.addEventListener("DOMContentLoaded",async()=>{const e=document.getElementById("header-container"),t=document.getElementById("footer-container");e&&await v(e),t&&await p(t)});async function v(e){try{const o=await(await fetch("/header.html")).text();e.innerHTML=o,w()}catch(t){console.error("Error loading header:",t)}}function w(){const e=document.getElementById("logout-btn");e&&y(()=>import("./common-Ceipz8Vt.js"),[]).then(l=>{const{clearToken:i}=l;e.addEventListener("click",()=>{i(),window.location.href="/login.html"})});const t=document.getElementById("menu-toggle"),o=document.getElementById("nav-links");t&&o&&t.addEventListener("click",()=>{o.classList.toggle("expanded")})}async function p(e){try{const o=await(await fetch("/footer.html")).text();e.innerHTML=o,L()}catch(t){console.error("Error loading footer:",t)}}function L(){const e=document.getElementById("current-year");e&&(e.textContent=new Date().getFullYear())} diff --git a/examples/YOLO_server_api/frontend/dist/assets/headerFooterLoader-LYlhFStm.js b/examples/YOLO_server_api/frontend/dist/assets/headerFooterLoader-LYlhFStm.js deleted file mode 100644 index ab1b54c..0000000 --- a/examples/YOLO_server_api/frontend/dist/assets/headerFooterLoader-LYlhFStm.js +++ /dev/null @@ -1 +0,0 @@ -const g="modulepreload",E=function(i){return"/"+i},u={},y=function(a,e,c){let l=Promise.resolve();if(e&&e.length>0){document.getElementsByTagName("link");const t=document.querySelector("meta[property=csp-nonce]"),n=(t==null?void 0:t.nonce)||(t==null?void 0:t.getAttribute("nonce"));l=Promise.allSettled(e.map(o=>{if(o=E(o),o in u)return;u[o]=!0;const d=o.endsWith(".css"),m=d?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${o}"]${m}`))return;const r=document.createElement("link");if(r.rel=d?"stylesheet":g,d||(r.as="script"),r.crossOrigin="",r.href=o,n&&r.setAttribute("nonce",n),document.head.appendChild(r),d)return new Promise((h,f)=>{r.addEventListener("load",h),r.addEventListener("error",()=>f(new Error(`Unable to preload CSS for ${o}`)))})}))}function s(t){const n=new Event("vite:preloadError",{cancelable:!0});if(n.payload=t,window.dispatchEvent(n),!n.defaultPrevented)throw t}return l.then(t=>{for(const n of t||[])n.status==="rejected"&&s(n.reason);return a().catch(s)})};document.addEventListener("DOMContentLoaded",()=>{const i=document.getElementById("header-container"),a=document.getElementById("footer-container");i&&fetch("/header.html").then(e=>e.text()).then(e=>{i.innerHTML=e;const c=document.getElementById("logout-btn");c&&y(()=>import("./common-Ceipz8Vt.js"),[]).then(t=>{const{clearToken:n}=t;c.addEventListener("click",()=>{n(),window.location.href="/login.html"})});const l=document.getElementById("menu-toggle"),s=document.getElementById("nav-links");l&&s&&l.addEventListener("click",()=>{s.classList.toggle("expanded")})}).catch(e=>console.error("Error loading header:",e)),a&&fetch("/footer.html").then(e=>e.text()).then(e=>{a.innerHTML=e;const c=document.getElementById("current-year");c&&(c.textContent=new Date().getFullYear())}).catch(e=>console.error("Error loading footer:",e))}); diff --git a/examples/YOLO_server_api/frontend/dist/assets/login-6oBM0D2Y.js b/examples/YOLO_server_api/frontend/dist/assets/login-6oBM0D2Y.js new file mode 100644 index 0000000..bcb625d --- /dev/null +++ b/examples/YOLO_server_api/frontend/dist/assets/login-6oBM0D2Y.js @@ -0,0 +1 @@ +import{clearToken as s,setToken as i}from"./common-Ceipz8Vt.js";const d="/api";document.addEventListener("DOMContentLoaded",()=>{const n=document.getElementById("login-form"),e=document.getElementById("login-error");n.addEventListener("submit",t=>c(t,e))});async function c(n,e){n.preventDefault();const{username:t,password:o}=m();if(!t||!o){a(e,"Username and password are required.");return}try{await u(t,o),l()}catch(r){a(e,r.message||"Error logging in.")}}function m(){const n=document.getElementById("username").value.trim(),e=document.getElementById("password").value.trim();return{username:n,password:e}}async function u(n,e){s();const t=await fetch(`${d}/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:n,password:e})});if(!t.ok){const r=await t.json();throw new Error(r.detail||"Login failed.")}const o=await t.json();i(o.access_token)}function l(){window.location.href="./index.html"}function a(n,e){n.textContent=e} diff --git a/examples/YOLO_server_api/frontend/dist/assets/login-DjA6hhVY.js b/examples/YOLO_server_api/frontend/dist/assets/login-DjA6hhVY.js deleted file mode 100644 index 1738786..0000000 --- a/examples/YOLO_server_api/frontend/dist/assets/login-DjA6hhVY.js +++ /dev/null @@ -1 +0,0 @@ -import{clearToken as i,setToken as c}from"./common-Ceipz8Vt.js";const m="/api";document.addEventListener("DOMContentLoaded",()=>{const n=document.getElementById("login-form"),t=document.getElementById("login-error");n.addEventListener("submit",async o=>{o.preventDefault();const r=document.getElementById("username").value.trim(),a=document.getElementById("password").value.trim();try{i();const e=await fetch(`${m}/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:r,password:a})});if(!e.ok){const d=await e.json();t.textContent=d.detail||"Login failed";return}const s=await e.json();c(s.access_token),window.location.href="./index.html"}catch{t.textContent="Error logging in."}})}); diff --git a/examples/YOLO_server_api/frontend/dist/assets/main-C-9B3wGB.js b/examples/YOLO_server_api/frontend/dist/assets/main-C-9B3wGB.js deleted file mode 100644 index 7540a48..0000000 --- a/examples/YOLO_server_api/frontend/dist/assets/main-C-9B3wGB.js +++ /dev/null @@ -1 +0,0 @@ -import{checkAccess as r,clearToken as s,getToken as d,getUsernameFromToken as i,getUserRoleFromToken as l}from"./common-Ceipz8Vt.js";import"./headerFooterLoader-LYlhFStm.js";document.addEventListener("DOMContentLoaded",()=>{r([]);const e=document.getElementById("logout-btn");e&&e.addEventListener("click",()=>{s(),window.location.href="./login.html"});const t=d(),o=document.getElementById("user-info");if(t){const n=i(),c=l();o.textContent=`Logged in as ${n} (Role: ${c})`}else o.textContent="You are not logged in."}); diff --git a/examples/YOLO_server_api/frontend/dist/assets/main-dtSFUXYT.js b/examples/YOLO_server_api/frontend/dist/assets/main-dtSFUXYT.js new file mode 100644 index 0000000..6e12580 --- /dev/null +++ b/examples/YOLO_server_api/frontend/dist/assets/main-dtSFUXYT.js @@ -0,0 +1 @@ +import{checkAccess as c,clearToken as i,getToken as s,getUsernameFromToken as r,getUserRoleFromToken as d}from"./common-Ceipz8Vt.js";import"./headerFooterLoader-CkcITKwD.js";document.addEventListener("DOMContentLoaded",()=>{l(),u(),m()});function l(){c([])}function u(){const e=document.getElementById("logout-btn");e&&e.addEventListener("click",g)}function g(){i(),window.location.href="./login.html"}function m(){const e=s(),n=document.getElementById("user-info");if(e){const o=r(),t=d();n.textContent=`Logged in as ${o} (Role: ${t})`}else n.textContent="You are not logged in."} diff --git a/examples/YOLO_server_api/frontend/dist/assets/model_management-BkAfeMO-.js b/examples/YOLO_server_api/frontend/dist/assets/model_management-BkAfeMO-.js new file mode 100644 index 0000000..3f05af6 --- /dev/null +++ b/examples/YOLO_server_api/frontend/dist/assets/model_management-BkAfeMO-.js @@ -0,0 +1 @@ +import{checkAccess as r,authHeaders as a}from"./common-Ceipz8Vt.js";import"./headerFooterLoader-CkcITKwD.js";const i="/api";document.addEventListener("DOMContentLoaded",()=>{r(["admin","model_manager"]),m(),u()});function m(){const t=document.getElementById("model-file-update-form"),e=document.getElementById("model-update-error");t&&t.addEventListener("submit",async o=>{o.preventDefault(),e.textContent="";const n=s(e);if(n)try{await c(n,e)}catch{e.textContent="Error updating model file."}})}function s(t){const e=document.getElementById("model-name").value.trim(),n=document.getElementById("model-file").files[0];if(!n)return t.textContent="Please select a model file.",null;const d=new FormData;return d.append("model",e),d.append("file",n),d}async function c(t,e){const o=await fetch(`${i}/model_file_update`,{method:"POST",headers:a(),body:t});if(!o.ok){const n=await o.json();e.textContent=n.detail||"Failed to update model file.";return}alert("Model file updated successfully.")}function u(){const t=document.getElementById("get-new-model-form"),e=document.getElementById("get-new-model-error"),o=document.getElementById("model-file-content");t&&t.addEventListener("submit",async n=>{n.preventDefault(),f(e,o);const d=p();if(d)try{await g(d,e,o)}catch{e.textContent="Error retrieving model file."}})}function f(t,e){t.textContent="",e.textContent=""}function p(){return{model:document.getElementById("get-model-name").value.trim(),last_update_time:"1970-01-01"}}async function g(t,e,o){const n=await fetch(`${i}/get_new_model`,{method:"POST",headers:{"Content-Type":"application/json",...a()},body:JSON.stringify(t)});if(!n.ok){const l=await n.json();e.textContent=l.detail||"Failed to retrieve new model.";return}const d=await n.json();y(d,o)}function y(t,e){t.model_file?e.textContent=`Base64 Model File: ${t.model_file}`:e.textContent=t.message||"No model file available."} diff --git a/examples/YOLO_server_api/frontend/dist/assets/model_management-BkVGVr4y.js b/examples/YOLO_server_api/frontend/dist/assets/model_management-BkVGVr4y.js deleted file mode 100644 index 8b195bb..0000000 --- a/examples/YOLO_server_api/frontend/dist/assets/model_management-BkVGVr4y.js +++ /dev/null @@ -1 +0,0 @@ -import{checkAccess as p,authHeaders as i}from"./common-Ceipz8Vt.js";import"./headerFooterLoader-LYlhFStm.js";const s="/api";document.addEventListener("DOMContentLoaded",()=>{p(["admin","model_manager"]);const c=document.getElementById("model-file-update-form"),n=document.getElementById("model-update-error");c.addEventListener("submit",async a=>{a.preventDefault(),n.textContent="";const m=document.getElementById("model-name").value.trim(),e=document.getElementById("model-file").files[0];if(!e){n.textContent="Please select a model file.";return}const t=new FormData;t.append("model",m),t.append("file",e);try{const o=await fetch(`${s}/model_file_update`,{method:"POST",headers:i(),body:t});if(!o.ok){const u=await o.json();n.textContent=u.detail||"Failed to update model file.";return}alert("Model file updated successfully.")}catch{n.textContent="Error updating model file."}});const f=document.getElementById("get-new-model-form"),d=document.getElementById("get-new-model-error"),l=document.getElementById("model-file-content");f.addEventListener("submit",async a=>{a.preventDefault(),d.textContent="",l.textContent="";const m=document.getElementById("get-model-name").value.trim(),r="1970-01-01";try{const e=await fetch(`${s}/get_new_model`,{method:"POST",headers:{"Content-Type":"application/json",...i()},body:JSON.stringify({model:m,last_update_time:r})});if(!e.ok){const o=await e.json();d.textContent=o.detail||"Failed to retrieve new model.";return}const t=await e.json();t.model_file?l.textContent=`Base64 Model File: ${t.model_file}`:l.textContent=t.message}catch{d.textContent="Error retrieving model file."}})}); diff --git a/examples/YOLO_server_api/frontend/dist/assets/user_management-C9tlKTo9.js b/examples/YOLO_server_api/frontend/dist/assets/user_management-C9tlKTo9.js deleted file mode 100644 index cbbbd03..0000000 --- a/examples/YOLO_server_api/frontend/dist/assets/user_management-C9tlKTo9.js +++ /dev/null @@ -1 +0,0 @@ -import{checkAccess as w,clearToken as h,authHeaders as s}from"./common-Ceipz8Vt.js";import"./headerFooterLoader-LYlhFStm.js";const o="/api";document.addEventListener("DOMContentLoaded",()=>{w(["admin"]);const l=document.getElementById("logout-btn");l&&l.addEventListener("click",()=>{h(),window.location.href="/login.html"});const p=document.getElementById("add-user-form"),d=document.getElementById("add-user-error");p.addEventListener("submit",async n=>{n.preventDefault(),d.textContent="";const a=document.getElementById("add-username").value.trim(),t=document.getElementById("add-password").value.trim(),e=document.getElementById("add-role").value;try{const r=await fetch(`${o}/add_user`,{method:"POST",headers:{"Content-Type":"application/json",...s()},body:JSON.stringify({username:a,password:t,role:e})});if(!r.ok){const v=await r.json();d.textContent=v.detail||"Failed to add user.";return}alert("User added successfully.")}catch{d.textContent="Error adding user."}});const y=document.getElementById("delete-user-form"),u=document.getElementById("delete-user-error");y.addEventListener("submit",async n=>{n.preventDefault(),u.textContent="";const a=document.getElementById("delete-username").value.trim();try{const t=await fetch(`${o}/delete_user`,{method:"POST",headers:{"Content-Type":"application/json",...s()},body:JSON.stringify({username:a})});if(!t.ok){const e=await t.json();u.textContent=e.detail||"Failed to delete user.";return}alert("User deleted successfully.")}catch{u.textContent="Error deleting user."}});const E=document.getElementById("update-username-form"),c=document.getElementById("update-username-error");E.addEventListener("submit",async n=>{n.preventDefault(),c.textContent="";const a=document.getElementById("old-username").value.trim(),t=document.getElementById("new-username").value.trim();try{const e=await fetch(`${o}/update_username`,{method:"PUT",headers:{"Content-Type":"application/json",...s()},body:JSON.stringify({old_username:a,new_username:t})});if(!e.ok){const r=await e.json();c.textContent=r.detail||"Failed to update username.";return}alert("Username updated successfully.")}catch{c.textContent="Error updating username."}});const g=document.getElementById("update-password-form"),m=document.getElementById("update-password-error");g.addEventListener("submit",async n=>{n.preventDefault(),m.textContent="";const a=document.getElementById("update-password-username").value.trim(),t=document.getElementById("new-password").value.trim();try{const e=await fetch(`${o}/update_password`,{method:"PUT",headers:{"Content-Type":"application/json",...s()},body:JSON.stringify({username:a,new_password:t})});if(!e.ok){const r=await e.json();m.textContent=r.detail||"Failed to update password.";return}alert("Password updated successfully.")}catch{m.textContent="Error updating password."}});const f=document.getElementById("set-active-status-form"),i=document.getElementById("set-active-status-error");f.addEventListener("submit",async n=>{n.preventDefault(),i.textContent="";const a=document.getElementById("active-status-username").value.trim(),t=document.getElementById("is-active").checked;try{const e=await fetch(`${o}/set_user_active_status`,{method:"PUT",headers:{"Content-Type":"application/json",...s()},body:JSON.stringify({username:a,is_active:t})});if(!e.ok){const r=await e.json();i.textContent=r.detail||"Failed to update active status.";return}alert("User active status updated successfully.")}catch{i.textContent="Error updating active status."}})}); diff --git a/examples/YOLO_server_api/frontend/dist/assets/user_management-CzKH3sMr.js b/examples/YOLO_server_api/frontend/dist/assets/user_management-CzKH3sMr.js new file mode 100644 index 0000000..3569c77 --- /dev/null +++ b/examples/YOLO_server_api/frontend/dist/assets/user_management-CzKH3sMr.js @@ -0,0 +1 @@ +import{checkAccess as c,clearToken as m,authHeaders as l}from"./common-Ceipz8Vt.js";import"./headerFooterLoader-CkcITKwD.js";const r="/api";document.addEventListener("DOMContentLoaded",()=>{c(["admin"]),i(),p()});function i(){const e=document.getElementById("logout-btn");e&&e.addEventListener("click",()=>{m(),window.location.href="/login.html"})}function p(){a("add-user-form",f,"add-user-error"),a("delete-user-form",y,"delete-user-error"),a("update-username-form",w,"update-username-error"),a("update-password-form",v,"update-password-error"),a("set-active-status-form",g,"set-active-status-error")}function a(e,t,n){const s=document.getElementById(e),o=document.getElementById(n);s&&s.addEventListener("submit",async u=>{u.preventDefault(),o.textContent="";try{await t()}catch{o.textContent="An error occurred while processing the form."}})}async function f(){const e=document.getElementById("add-username").value.trim(),t=document.getElementById("add-password").value.trim(),n=document.getElementById("add-role").value;await d(`${r}/add_user`,"POST",{username:e,password:t,role:n}),alert("User added successfully.")}async function y(){const e=document.getElementById("delete-username").value.trim();await d(`${r}/delete_user`,"POST",{username:e}),alert("User deleted successfully.")}async function w(){const e=document.getElementById("old-username").value.trim(),t=document.getElementById("new-username").value.trim();await d(`${r}/update_username`,"PUT",{old_username:e,new_username:t}),alert("Username updated successfully.")}async function v(){const e=document.getElementById("update-password-username").value.trim(),t=document.getElementById("new-password").value.trim();await d(`${r}/update_password`,"PUT",{username:e,new_password:t}),alert("Password updated successfully.")}async function g(){const e=document.getElementById("active-status-username").value.trim(),t=document.getElementById("is-active").checked;await d(`${r}/set_user_active_status`,"PUT",{username:e,is_active:t}),alert("User active status updated successfully.")}async function d(e,t,n){const s=await fetch(e,{method:t,headers:{"Content-Type":"application/json",...l()},body:JSON.stringify(n)});if(!s.ok){const o=await s.json();throw new Error(o.detail||"Request failed.")}} diff --git a/examples/YOLO_server_api/frontend/dist/detection.html b/examples/YOLO_server_api/frontend/dist/detection.html index eff0d87..6591b5e 100644 --- a/examples/YOLO_server_api/frontend/dist/detection.html +++ b/examples/YOLO_server_api/frontend/dist/detection.html @@ -11,9 +11,9 @@ - + - + diff --git a/examples/YOLO_server_api/frontend/dist/index.html b/examples/YOLO_server_api/frontend/dist/index.html index 24826d8..ebcbf26 100644 --- a/examples/YOLO_server_api/frontend/dist/index.html +++ b/examples/YOLO_server_api/frontend/dist/index.html @@ -15,9 +15,9 @@ - + - + diff --git a/examples/YOLO_server_api/frontend/dist/login.html b/examples/YOLO_server_api/frontend/dist/login.html index 6793f18..e1f487b 100644 --- a/examples/YOLO_server_api/frontend/dist/login.html +++ b/examples/YOLO_server_api/frontend/dist/login.html @@ -11,7 +11,7 @@ - + diff --git a/examples/YOLO_server_api/frontend/dist/model_management.html b/examples/YOLO_server_api/frontend/dist/model_management.html index 0c7cea2..d596455 100644 --- a/examples/YOLO_server_api/frontend/dist/model_management.html +++ b/examples/YOLO_server_api/frontend/dist/model_management.html @@ -11,9 +11,9 @@ - + - + diff --git a/examples/YOLO_server_api/frontend/dist/user_management.html b/examples/YOLO_server_api/frontend/dist/user_management.html index c67d3d3..25bb95e 100644 --- a/examples/YOLO_server_api/frontend/dist/user_management.html +++ b/examples/YOLO_server_api/frontend/dist/user_management.html @@ -16,9 +16,9 @@ - + - + diff --git a/examples/YOLO_server_api/frontend/public/js/auth.js b/examples/YOLO_server_api/frontend/public/js/auth.js index f55f929..7038942 100644 --- a/examples/YOLO_server_api/frontend/public/js/auth.js +++ b/examples/YOLO_server_api/frontend/public/js/auth.js @@ -3,45 +3,84 @@ const API_URL = '/api'; // Base path for the backend API // Wait until the DOM content is fully loaded document.addEventListener('DOMContentLoaded', () => { - // Select the login form and error message elements const form = document.getElementById('login-form'); const errorMsg = document.getElementById('login-error'); - // Attach an event listener to handle form submission - form.addEventListener('submit', async (e) => { - e.preventDefault(); // Prevent the default form submission behaviour - - // Retrieve the username and password from the form inputs - const username = document.getElementById('username').value.trim(); - const password = document.getElementById('password').value.trim(); - - try { - // Clear any existing tokens before making a new login request - clearToken(); - - // Send a POST request to the API with the username and password - const response = await fetch(`${API_URL}/token`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, // Set the request header for JSON - body: JSON.stringify({ username, password }) // Convert credentials to a JSON string - }); - - // Check if the response indicates an unsuccessful login - if (!response.ok) { - const data = await response.json(); - errorMsg.textContent = data.detail || 'Login failed'; // Display the error message - return; // Exit the function - } - - // Parse the response JSON to retrieve the token - const data = await response.json(); - setToken(data.access_token); // Store the token in localStorage - - // Redirect to the homepage after successful login - window.location.href = './index.html'; - } catch (err) { - // Handle network or other unexpected errors - errorMsg.textContent = 'Error logging in.'; - } - }); + form.addEventListener('submit', (e) => handleLoginFormSubmit(e, errorMsg)); }); + +/** + * Handles the login form submission. + * + * @param {Event} e - The form submit event. + * @param {HTMLElement} errorMsg - The error message element. + */ +async function handleLoginFormSubmit(e, errorMsg) { + e.preventDefault(); // Prevent the default form submission behaviour + + const { username, password } = getFormCredentials(); + if (!username || !password) { + displayError(errorMsg, 'Username and password are required.'); + return; + } + + try { + await login(username, password); + redirectToHomePage(); + } catch (err) { + displayError(errorMsg, err.message || 'Error logging in.'); + } +} + +/** + * Retrieves the username and password from the form inputs. + * + * @returns {Object} An object containing username and password. + */ +function getFormCredentials() { + const username = document.getElementById('username').value.trim(); + const password = document.getElementById('password').value.trim(); + return { username, password }; +} + +/** + * Logs in the user by sending a POST request to the API. + * + * @param {string} username - The username. + * @param {string} password - The password. + * @throws Will throw an error if the login fails. + */ +async function login(username, password) { + clearToken(); // Clear any existing tokens before making a new login request + + const response = await fetch(`${API_URL}/token`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username, password }) + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.detail || 'Login failed.'); + } + + const data = await response.json(); + setToken(data.access_token); // Store the token in localStorage +} + +/** + * Redirects the user to the homepage. + */ +function redirectToHomePage() { + window.location.href = './index.html'; +} + +/** + * Displays an error message in the specified element. + * + * @param {HTMLElement} element - The element to display the error message in. + * @param {string} message - The error message to display. + */ +function displayError(element, message) { + element.textContent = message; +} diff --git a/examples/YOLO_server_api/frontend/public/js/detection.js b/examples/YOLO_server_api/frontend/public/js/detection.js index f3885e0..ceb8cdb 100644 --- a/examples/YOLO_server_api/frontend/public/js/detection.js +++ b/examples/YOLO_server_api/frontend/public/js/detection.js @@ -1,13 +1,13 @@ import { checkAccess, authHeaders, showAppropriateLinks } from './common.js'; + const API_URL = '/api'; // Base path for the backend API +let originalImageWidth = 0; +let originalImageHeight = 0; -// Wait until the DOM content is fully loaded document.addEventListener('DOMContentLoaded', () => { - // Check the user's access and show links based on their role checkAccess([]); showAppropriateLinks(); - // Select key DOM elements const logoutBtn = document.getElementById('logout-btn'); const form = document.getElementById('detection-form'); const detectionError = document.getElementById('detection-error'); @@ -15,200 +15,294 @@ document.addEventListener('DOMContentLoaded', () => { const fileDropArea = document.getElementById('file-drop-area'); const imageInput = document.getElementById('image-input'); const removeImageBtn = document.getElementById('remove-image-btn'); + const chooseFileBtn = document.querySelector('.choose-file-btn'); - // Variables to store the original dimensions of the uploaded image - let originalImageWidth = 0; - let originalImageHeight = 0; - - // Function to remove the currently uploaded image and clear related content - function removeImage() { - const canvas = document.getElementById('image-canvas'); - const ctx = canvas.getContext('2d'); - ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas - imageInput.value = ''; // Reset the file input - detectionResult.textContent = ''; // Clear the detection results - detectionError.textContent = ''; // Clear any error messages - removeImageBtn.style.display = 'none'; // Hide the "Remove Image" button - } + setupLogoutButton(logoutBtn); + setupRemoveImageButton(removeImageBtn); + setupChooseFileButton(chooseFileBtn, imageInput); + setupFileDrop(fileDropArea, imageInput); + setupFileInputChange(imageInput, fileDropArea, removeImageBtn); + setupFormSubmission(form, imageInput, detectionError, detectionResult); +}); - // Attach an event listener to the "Remove Image" button - removeImageBtn.addEventListener('click', () => { - removeImage(); +/** Set up the logout button event. */ +function setupLogoutButton(logoutBtn) { + if (!logoutBtn) return; + logoutBtn.addEventListener('click', () => { + window.location.href = '/login.html'; }); +} - // Allow users to click the "Choose File" button to open the file selector - const chooseFileBtn = document.querySelector('.choose-file-btn'); - chooseFileBtn.addEventListener('click', (e) => { - e.preventDefault(); // Prevent default behaviour - imageInput.click(); // Trigger the file input - }); +/** Set up the remove image button event. */ +function setupRemoveImageButton(removeImageBtn) { + removeImageBtn.addEventListener('click', removeImage); +} - // Handle drag-and-drop events for the file drop area - fileDropArea.addEventListener('dragover', (e) => { - e.preventDefault(); // Prevent the default drag behaviour - fileDropArea.classList.add('dragover'); // Highlight the drop area - }); - - fileDropArea.addEventListener('dragleave', () => { - fileDropArea.classList.remove('dragover'); // Remove the highlight +/** Set up the choose file button to trigger file input. */ +function setupChooseFileButton(chooseFileBtn, imageInput) { + if (!chooseFileBtn) return; + chooseFileBtn.addEventListener('click', (e) => { + e.preventDefault(); + imageInput.click(); }); +} - fileDropArea.addEventListener('drop', (e) => { - e.preventDefault(); // Prevent the default drop behaviour - fileDropArea.classList.remove('dragover'); // Remove the highlight - if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { - const file = e.dataTransfer.files[0]; - imageInput.files = e.dataTransfer.files; // Set the dropped file to the input - showImagePreview(file); // Display the preview - } - }); +/** Set up drag-and-drop file handling. */ +function setupFileDrop(fileDropArea, imageInput) { + fileDropArea.addEventListener('dragover', handleDragOver); + fileDropArea.addEventListener('dragleave', handleDragLeave); + fileDropArea.addEventListener('drop', (e) => handleFileDrop(e, fileDropArea, imageInput)); +} - // Handle file input changes +/** Set up file input change event. */ +function setupFileInputChange(imageInput, fileDropArea, removeImageBtn) { imageInput.addEventListener('change', (e) => { - if (e.target.files && e.target.files[0]) { - showImagePreview(e.target.files[0]); // Display the preview + const file = e.target.files && e.target.files[0]; + if (file) { + showImagePreview(file, fileDropArea, removeImageBtn); } }); +} - // Function to display a preview of the uploaded image on the canvas - function showImagePreview(file) { - const reader = new FileReader(); - reader.onload = () => { - const canvas = document.getElementById('image-canvas'); - const ctx = canvas.getContext('2d'); - const img = new Image(); - img.onload = () => { - originalImageWidth = img.width; - originalImageHeight = img.height; - - // Scale the image to fit within the drop area - const maxWidth = fileDropArea.clientWidth - 40; // Allow for padding - const maxHeight = fileDropArea.clientHeight - 40; - let { width, height } = img; - - // Adjust width and height to maintain the aspect ratio - if (width > maxWidth) { - const ratio = maxWidth / width; - width = maxWidth; - height *= ratio; - } - if (height > maxHeight) { - const ratio = maxHeight / height; - height = maxHeight; - width *= ratio; - } - - // Set canvas dimensions and draw the image - canvas.width = width; - canvas.height = height; - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(img, 0, 0, width, height); - removeImageBtn.style.display = 'inline-block'; // Show the "Remove Image" button - }; - img.src = reader.result; - }; - reader.readAsDataURL(file); // Read the file as a data URL - } - - // Handle form submission for detection requests +/** Set up form submission for detection. */ +function setupFormSubmission(form, imageInput, detectionError, detectionResult) { form.addEventListener('submit', async (e) => { - e.preventDefault(); // Prevent the default form submission - detectionError.textContent = ''; // Clear any previous errors - detectionResult.textContent = ''; // Clear any previous results + e.preventDefault(); + clearMessages(detectionError, detectionResult); + + const model = document.getElementById('model-select').value; + const file = imageInput.files[0]; - const model = document.getElementById('model-select').value; // Get the selected model - const file = imageInput.files[0]; // Get the uploaded file if (!file) { - detectionError.textContent = 'Please select an image.'; // Display an error if no file is selected + detectionError.textContent = 'Please select an image.'; return; } - const formData = new FormData(); // Create a FormData object - formData.append('image', file); // Append the image file - formData.append('model', model); // Append the selected model - - try { - const response = await fetch(`${API_URL}/detect`, { - method: 'POST', - headers: authHeaders(), // Add authentication headers - body: formData // Send the form data - }); - - if (!response.ok) { - const data = await response.json(); - detectionError.textContent = data.detail || 'Detection failed.'; // Display an error message - return; - } - - const results = await response.json(); - drawDetectionResults(results); // Draw the detection results on the image - displayObjectCounts(results); // Display the counts of detected objects - } catch (err) { - console.error(err); - detectionError.textContent = 'Error performing detection.'; // Display a generic error message - } + await performDetection(file, model, detectionError, detectionResult); }); +} - // Draw detection results on the canvas - function drawDetectionResults(results) { - const canvas = document.getElementById('image-canvas'); - const ctx = canvas.getContext('2d'); - - const names = ['Hardhat', 'Mask', 'NO-Hardhat', 'NO-Mask', 'NO-Safety Vest', 'Person', 'Safety Cone', 'Safety Vest', 'machinery', 'vehicle']; - const colors = { - 'Hardhat': 'green', - 'Safety Vest': 'green', - 'machinery': 'yellow', - 'vehicle': 'yellow', - 'NO-Hardhat': 'red', - 'NO-Safety Vest': 'red', - 'Person': 'orange', - 'Safety Cone': 'pink' - }; - - results.forEach(([x1, y1, x2, y2, confidence, classId]) => { - const label = names[classId]; - const color = colors[label] || 'blue'; - - // Scale the bounding box coordinates to fit the canvas - const scaleX = canvas.width / originalImageWidth; - const scaleY = canvas.height / originalImageHeight; - const scaledX1 = x1 * scaleX; - const scaledY1 = y1 * scaleY; - const scaledX2 = x2 * scaleX; - const scaledY2 = y2 * scaleY; - - // Draw the bounding box - ctx.strokeStyle = color; - ctx.lineWidth = 2; - ctx.strokeRect(scaledX1, scaledY1, scaledX2 - scaledX1, scaledY2 - scaledY1); - - // Draw the label background - ctx.fillStyle = color; - ctx.fillRect(scaledX1, scaledY1 - 20, ctx.measureText(label).width + 10, 20); - - // Draw the label text - ctx.fillStyle = 'black'; - ctx.font = '14px Arial'; - ctx.fillText(label, scaledX1 + 5, scaledY1 - 5); // Display the object label - }); +/** Handle drag over event. */ +function handleDragOver(e) { + e.preventDefault(); + e.currentTarget.classList.add('dragover'); +} + +/** Handle drag leave event. */ +function handleDragLeave(e) { + e.currentTarget.classList.remove('dragover'); +} + +/** Handle file drop event. */ +function handleFileDrop(e, fileDropArea, imageInput) { + e.preventDefault(); + fileDropArea.classList.remove('dragover'); + const file = e.dataTransfer.files && e.dataTransfer.files[0]; + if (file) { + imageInput.files = e.dataTransfer.files; + const removeImageBtn = document.getElementById('remove-image-btn'); + showImagePreview(file, fileDropArea, removeImageBtn); + } +} + +/** Clear error and result messages. */ +function clearMessages(detectionError, detectionResult) { + detectionError.textContent = ''; + detectionResult.textContent = ''; +} + +/** Remove the currently uploaded image and clear related content. */ +function removeImage() { + const canvas = document.getElementById('image-canvas'); + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + + const imageInput = document.getElementById('image-input'); + imageInput.value = ''; + + const detectionResult = document.getElementById('detection-result'); + const detectionError = document.getElementById('detection-error'); + detectionResult.textContent = ''; + detectionError.textContent = ''; + + const removeImageBtn = document.getElementById('remove-image-btn'); + removeImageBtn.style.display = 'none'; +} + +/** Display a preview of the uploaded image on the canvas. */ +function showImagePreview(file, fileDropArea, removeImageBtn) { + const reader = new FileReader(); + reader.onload = () => loadImagePreview(reader.result, fileDropArea, removeImageBtn); + reader.readAsDataURL(file); +} + +/** Load image preview once file is read. */ +function loadImagePreview(imageSrc, fileDropArea, removeImageBtn) { + const img = new Image(); + img.onload = () => { + originalImageWidth = img.width; + originalImageHeight = img.height; + drawScaledImage(img, fileDropArea); + removeImageBtn.style.display = 'inline-block'; + }; + img.src = imageSrc; +} + +/** Draw the scaled image on the canvas. */ +function drawScaledImage(img, fileDropArea) { + const canvas = document.getElementById('image-canvas'); + const ctx = canvas.getContext('2d'); + + const { width, height } = calculateScaledDimensions(img, fileDropArea); + canvas.width = width; + canvas.height = height; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(img, 0, 0, width, height); +} + +/** Calculate scaled dimensions for the image to fit the drop area. */ +function calculateScaledDimensions(img, fileDropArea) { + const maxWidth = fileDropArea.clientWidth - 40; + const maxHeight = fileDropArea.clientHeight - 40; + let { width, height } = img; + + ({ width, height } = scaleDimension(width, height, maxWidth, maxHeight)); + return { width, height }; +} + +/** Scale dimensions to maintain aspect ratio within maxWidth and maxHeight. */ +function scaleDimension(width, height, maxWidth, maxHeight) { + if (width > maxWidth) { + const ratio = maxWidth / width; + width = maxWidth; + height *= ratio; } + if (height > maxHeight) { + const ratio = maxHeight / height; + height = maxHeight; + width *= ratio; + } + return { width, height }; +} - // Display the counts of detected objects - function displayObjectCounts(results) { - const counts = {}; - const names = ['Hardhat', 'Mask', 'NO-Hardhat', 'NO-Mask', 'NO-Safety Vest', 'Person', 'Safety Cone', 'Safety Vest', 'machinery', 'vehicle']; - names.forEach(name => counts[name] = 0); // Initialise counts for all object names +/** Perform the detection request to the backend. */ +async function performDetection(file, model, detectionError, detectionResult) { + const formData = new FormData(); + formData.append('image', file); + formData.append('model', model); - results.forEach(([x1, y1, x2, y2, confidence, classId]) => { - const label = names[classId]; - if (label) counts[label] += 1; // Increment the count for the detected object + try { + const response = await fetch(`${API_URL}/detect`, { + method: 'POST', + headers: authHeaders(), + body: formData }); - // Display the object counts - detectionResult.textContent = Object.entries(counts) - .filter(([_, count]) => count > 0) // Only show objects that were detected - .map(([name, count]) => `${name}: ${count}`) - .join('\n'); + if (!response.ok) { + const data = await response.json(); + detectionError.textContent = data.detail || 'Detection failed.'; + return; + } + + const results = await response.json(); + drawDetectionResults(results); + displayObjectCounts(results, detectionResult); + } catch (err) { + console.error(err); + detectionError.textContent = 'Error performing detection.'; } -}); +} + +/** Draw detection results on the canvas. */ +function drawDetectionResults(results) { + const canvas = document.getElementById('image-canvas'); + const ctx = canvas.getContext('2d'); + + results.forEach((detection) => drawSingleDetection(ctx, detection, canvas)); +} + +/** Draw a single detection result. */ +function drawSingleDetection(ctx, detection, canvas) { + const names = ['Hardhat', 'Mask', 'NO-Hardhat', 'NO-Mask', 'NO-Safety Vest', 'Person', 'Safety Cone', 'Safety Vest', 'machinery', 'vehicle']; + const colors = { + 'Hardhat': 'green', + 'Safety Vest': 'green', + 'machinery': 'yellow', + 'vehicle': 'yellow', + 'NO-Hardhat': 'red', + 'NO-Safety Vest': 'red', + 'Person': 'orange', + 'Safety Cone': 'pink' + }; + + const [x1, y1, x2, y2, confidence, classId] = detection; + const label = names[classId]; + const color = colors[label] || 'blue'; + + const { scaledX1, scaledY1, scaledX2, scaledY2 } = scaleCoordinates(x1, y1, x2, y2, canvas); + + drawBoundingBox(ctx, scaledX1, scaledY1, scaledX2, scaledY2, color); + drawLabel(ctx, label, scaledX1, scaledY1, color); +} + +/** Scale coordinates to fit the canvas. */ +function scaleCoordinates(x1, y1, x2, y2, canvas) { + const scaleX = canvas.width / originalImageWidth; + const scaleY = canvas.height / originalImageHeight; + + return { + scaledX1: x1 * scaleX, + scaledY1: y1 * scaleY, + scaledX2: x2 * scaleX, + scaledY2: y2 * scaleY + }; +} + +/** Draw bounding box around the detected object. */ +function drawBoundingBox(ctx, x1, y1, x2, y2, color) { + ctx.strokeStyle = color; + ctx.lineWidth = 2; + ctx.strokeRect(x1, y1, x2 - x1, y2 - y1); +} + +/** Draw label above the bounding box. */ +function drawLabel(ctx, label, x1, y1, color) { + ctx.fillStyle = color; + ctx.fillRect(x1, y1 - 20, ctx.measureText(label).width + 10, 20); + + ctx.fillStyle = 'black'; + ctx.font = '14px Arial'; + ctx.fillText(label, x1 + 5, y1 - 5); +} + +/** Display counts of detected objects. */ +function displayObjectCounts(results, detectionResult) { + const names = ['Hardhat', 'Mask', 'NO-Hardhat', 'NO-Mask', 'NO-Safety Vest', 'Person', 'Safety Cone', 'Safety Vest', 'machinery', 'vehicle']; + const counts = initializeCounts(names); + countDetections(results, names, counts); + showCounts(detectionResult, counts); +} + +/** Initialize counts for each label. */ +function initializeCounts(names) { + const counts = {}; + names.forEach(name => counts[name] = 0); + return counts; +} + +/** Count detections per label. */ +function countDetections(results, names, counts) { + results.forEach(([, , , , , classId]) => { + const label = names[classId]; + if (label) counts[label] += 1; + }); +} + +/** Show counts in the detection result area. */ +function showCounts(detectionResult, counts) { + detectionResult.textContent = Object.entries(counts) + .filter(([_, count]) => count > 0) + .map(([name, count]) => `${name}: ${count}`) + .join('\n'); +} diff --git a/examples/YOLO_server_api/frontend/public/js/headerFooterLoader.js b/examples/YOLO_server_api/frontend/public/js/headerFooterLoader.js index f5428fc..9918a36 100644 --- a/examples/YOLO_server_api/frontend/public/js/headerFooterLoader.js +++ b/examples/YOLO_server_api/frontend/public/js/headerFooterLoader.js @@ -1,55 +1,62 @@ -// Wait for the DOM content to be fully loaded -document.addEventListener('DOMContentLoaded', () => { - // Select the containers for the header and footer +document.addEventListener('DOMContentLoaded', async () => { const headerContainer = document.getElementById('header-container'); const footerContainer = document.getElementById('footer-container'); - // Load the Header if (headerContainer) { - fetch('/header.html') // Fetch the header HTML file - .then(response => response.text()) // Convert the response to text - .then(html => { - headerContainer.innerHTML = html; // Insert the header content into the container + await loadHeader(headerContainer); + } - // Bind the Logout button event - const logoutBtn = document.getElementById('logout-btn'); - if (logoutBtn) { - // Dynamically import the common.js module to access clearToken - import('/js/common.js').then(module => { - const { clearToken } = module; - logoutBtn.addEventListener('click', () => { - clearToken(); // Clear the stored token - window.location.href = '/login.html'; // Redirect to the login page - }); - }); - } + if (footerContainer) { + await loadFooter(footerContainer); + } +}); - // Bind the Menu Toggle button for mobile navigation - const menuToggle = document.getElementById('menu-toggle'); - const navLinks = document.getElementById('nav-links'); - if (menuToggle && navLinks) { - // Toggle the visibility of navigation links when the button is clicked - menuToggle.addEventListener('click', () => { - navLinks.classList.toggle('expanded'); // Add or remove the 'expanded' class - }); - } - }) - .catch(err => console.error('Error loading header:', err)); // Log any errors that occur while fetching the header +async function loadHeader(headerContainer) { + try { + const response = await fetch('/header.html'); + const html = await response.text(); + headerContainer.innerHTML = html; + bindHeaderEvents(); + } catch (err) { + console.error('Error loading header:', err); } +} - // Load the Footer - if (footerContainer) { - fetch('/footer.html') // Fetch the footer HTML file - .then(response => response.text()) // Convert the response to text - .then(html => { - footerContainer.innerHTML = html; // Insert the footer content into the container +function bindHeaderEvents() { + const logoutBtn = document.getElementById('logout-btn'); + if (logoutBtn) { + import('/js/common.js').then(module => { + const { clearToken } = module; + logoutBtn.addEventListener('click', () => { + clearToken(); + window.location.href = '/login.html'; + }); + }); + } - // Dynamically set the current year in the footer - const yearSpan = document.getElementById('current-year'); - if (yearSpan) { - yearSpan.textContent = new Date().getFullYear(); // Get and display the current year - } - }) - .catch(err => console.error('Error loading footer:', err)); // Log any errors that occur while fetching the footer + const menuToggle = document.getElementById('menu-toggle'); + const navLinks = document.getElementById('nav-links'); + if (menuToggle && navLinks) { + menuToggle.addEventListener('click', () => { + navLinks.classList.toggle('expanded'); + }); } -}); +} + +async function loadFooter(footerContainer) { + try { + const response = await fetch('/footer.html'); + const html = await response.text(); + footerContainer.innerHTML = html; + updateFooterYear(); + } catch (err) { + console.error('Error loading footer:', err); + } +} + +function updateFooterYear() { + const yearSpan = document.getElementById('current-year'); + if (yearSpan) { + yearSpan.textContent = new Date().getFullYear(); + } +} diff --git a/examples/YOLO_server_api/frontend/public/js/main.js b/examples/YOLO_server_api/frontend/public/js/main.js index a55e43f..0d3d8a7 100644 --- a/examples/YOLO_server_api/frontend/public/js/main.js +++ b/examples/YOLO_server_api/frontend/public/js/main.js @@ -1,34 +1,50 @@ import { getUsernameFromToken, getUserRoleFromToken, getToken, clearToken, checkAccess } from './common.js'; -// Wait for the DOM content to be fully loaded document.addEventListener('DOMContentLoaded', () => { - // Check access permissions + // Initialize the page functionalities + initAccessCheck(); + initLogoutButton(); + displayUserInfo(); +}); + +/** + * Check user access permissions. + */ +function initAccessCheck() { // No specific role is required, meaning any logged-in user can access this page checkAccess([]); +} - // Handle the Logout button functionality +/** + * Set up the logout button functionality. + */ +function initLogoutButton() { const logoutBtn = document.getElementById('logout-btn'); if (logoutBtn) { - logoutBtn.addEventListener('click', () => { - clearToken(); // Clear the stored token - window.location.href = './login.html'; // Redirect the user to the login page - }); + logoutBtn.addEventListener('click', handleLogout); } +} - // Retrieve the user's token from localStorage - const token = getToken(); +/** + * Handle logout functionality. + */ +function handleLogout() { + clearToken(); // Clear the stored token + window.location.href = './login.html'; // Redirect the user to the login page +} - // Display user information or a message if the user is not logged in +/** + * Display the user's information or a message if the user is not logged in. + */ +function displayUserInfo() { + const token = getToken(); const userInfoDiv = document.getElementById('user-info'); + if (token) { - // If a token exists, decode it to retrieve the username and role const username = getUsernameFromToken(); const role = getUserRoleFromToken(); - - // Display the user's username and role userInfoDiv.textContent = `Logged in as ${username} (Role: ${role})`; } else { - // Display a message indicating the user is not logged in userInfoDiv.textContent = 'You are not logged in.'; } -}); +} diff --git a/examples/YOLO_server_api/frontend/public/js/model_management.js b/examples/YOLO_server_api/frontend/public/js/model_management.js index 9827a5f..e8e929c 100644 --- a/examples/YOLO_server_api/frontend/public/js/model_management.js +++ b/examples/YOLO_server_api/frontend/public/js/model_management.js @@ -1,96 +1,130 @@ import { checkAccess, authHeaders, clearToken } from './common.js'; const API_URL = '/api'; // Base path for the backend API -// Wait for the DOM content to be fully loaded document.addEventListener('DOMContentLoaded', () => { - // Restrict access to users with the roles 'admin' or 'model_manager' - checkAccess(['admin', 'model_manager']); + checkAccess(['admin', 'model_manager']); // Restrict access to specific roles + setupModelFileUpdate(); + setupGetNewModel(); +}); - // Handle the model file update form submission +/** Setup for model file update form submission */ +function setupModelFileUpdate() { const modelFileUpdateForm = document.getElementById('model-file-update-form'); const modelUpdateError = document.getElementById('model-update-error'); + + if (!modelFileUpdateForm) return; + modelFileUpdateForm.addEventListener('submit', async (e) => { - e.preventDefault(); // Prevent the default form submission behaviour + e.preventDefault(); modelUpdateError.textContent = ''; // Clear any previous error messages - // Retrieve the model name and file from the form - const model = document.getElementById('model-name').value.trim(); - const fileInput = document.getElementById('model-file'); - const file = fileInput.files[0]; - if (!file) { - modelUpdateError.textContent = 'Please select a model file.'; // Show an error if no file is selected - return; - } - - // Create a FormData object to hold the form data - const formData = new FormData(); - formData.append('model', model); - formData.append('file', file); + const formData = gatherModelFileUpdateData(modelUpdateError); + if (!formData) return; try { - // Send a POST request to the API to update the model file - const res = await fetch(`${API_URL}/model_file_update`, { - method: 'POST', - headers: authHeaders(), // Include the authentication headers - body: formData // Send the FormData object as the request body - }); - - if (!res.ok) { - const data = await res.json(); - modelUpdateError.textContent = data.detail || 'Failed to update model file.'; // Show an error message - return; - } - - // Show a success message upon successful model file update - alert('Model file updated successfully.'); + await sendModelFileUpdateRequest(formData, modelUpdateError); } catch (err) { - // Handle any unexpected errors modelUpdateError.textContent = 'Error updating model file.'; } }); +} + +/** Gather data for model file update */ +function gatherModelFileUpdateData(errorElement) { + const model = document.getElementById('model-name').value.trim(); + const fileInput = document.getElementById('model-file'); + const file = fileInput.files[0]; + + if (!file) { + errorElement.textContent = 'Please select a model file.'; + return null; + } + + const formData = new FormData(); + formData.append('model', model); + formData.append('file', file); + return formData; +} + +/** Send the model file update request */ +async function sendModelFileUpdateRequest(formData, errorElement) { + const response = await fetch(`${API_URL}/model_file_update`, { + method: 'POST', + headers: authHeaders(), + body: formData, + }); + + if (!response.ok) { + const data = await response.json(); + errorElement.textContent = data.detail || 'Failed to update model file.'; + return; + } - // Handle the form for retrieving a new model file + alert('Model file updated successfully.'); +} + +/** Setup for retrieving a new model file */ +function setupGetNewModel() { const getNewModelForm = document.getElementById('get-new-model-form'); const getNewModelError = document.getElementById('get-new-model-error'); const modelFileContent = document.getElementById('model-file-content'); + + if (!getNewModelForm) return; + getNewModelForm.addEventListener('submit', async (e) => { - e.preventDefault(); // Prevent the default form submission behaviour - getNewModelError.textContent = ''; // Clear any previous error messages - modelFileContent.textContent = ''; // Clear any previous content + e.preventDefault(); + clearGetNewModelMessages(getNewModelError, modelFileContent); - // Retrieve the model name from the form - const model = document.getElementById('get-model-name').value.trim(); - const lastUpdateTime = '1970-01-01'; // Automatically set the default last update time + const requestData = gatherGetNewModelData(); + if (!requestData) return; try { - // Send a POST request to the API to retrieve a new model file - const res = await fetch(`${API_URL}/get_new_model`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...authHeaders() // Include the authentication headers - }, - body: JSON.stringify({ model, last_update_time: lastUpdateTime }) // Send the model name and last update time as JSON - }); - - if (!res.ok) { - const data = await res.json(); - getNewModelError.textContent = data.detail || 'Failed to retrieve new model.'; // Show an error message - return; - } - - // Parse the response data - const data = await res.json(); - if (data.model_file) { - // Display the Base64-encoded model file content - modelFileContent.textContent = `Base64 Model File: ${data.model_file}`; - } else { - // Display any returned message if no file is included - modelFileContent.textContent = data.message; - } + await sendGetNewModelRequest(requestData, getNewModelError, modelFileContent); } catch (err) { - // Handle any unexpected errors getNewModelError.textContent = 'Error retrieving model file.'; } }); -}); +} + +/** Clear messages for the "Get New Model" form */ +function clearGetNewModelMessages(errorElement, contentElement) { + errorElement.textContent = ''; + contentElement.textContent = ''; +} + +/** Gather data for retrieving a new model file */ +function gatherGetNewModelData() { + const model = document.getElementById('get-model-name').value.trim(); + const lastUpdateTime = '1970-01-01'; // Default last update time + return { model, last_update_time: lastUpdateTime }; +} + +/** Send the request to retrieve a new model file */ +async function sendGetNewModelRequest(requestData, errorElement, contentElement) { + const response = await fetch(`${API_URL}/get_new_model`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...authHeaders(), + }, + body: JSON.stringify(requestData), + }); + + if (!response.ok) { + const data = await response.json(); + errorElement.textContent = data.detail || 'Failed to retrieve new model.'; + return; + } + + const data = await response.json(); + displayModelFileContent(data, contentElement); +} + +/** Display the retrieved model file content */ +function displayModelFileContent(data, contentElement) { + if (data.model_file) { + contentElement.textContent = `Base64 Model File: ${data.model_file}`; + } else { + contentElement.textContent = data.message || 'No model file available.'; + } +} diff --git a/examples/YOLO_server_api/frontend/public/js/user_management.js b/examples/YOLO_server_api/frontend/public/js/user_management.js index 79079ce..f06f77e 100644 --- a/examples/YOLO_server_api/frontend/public/js/user_management.js +++ b/examples/YOLO_server_api/frontend/public/js/user_management.js @@ -1,12 +1,17 @@ import { checkAccess, authHeaders, clearToken } from './common.js'; -const API_URL = '/api'; // Base path for the backend API, ensure the server has the corresponding routes -// Wait for the DOM content to be fully loaded +const API_URL = '/api'; // Base path for the backend API + document.addEventListener('DOMContentLoaded', () => { - // Restrict access to users with the 'admin' role - checkAccess(['admin']); + checkAccess(['admin']); // Restrict access to admin users + setupLogoutButton(); + setupFormHandlers(); +}); - // Bind the Logout button functionality +/** + * Bind the Logout button functionality. + */ +function setupLogoutButton() { const logoutBtn = document.getElementById('logout-btn'); if (logoutBtn) { logoutBtn.addEventListener('click', () => { @@ -14,174 +19,105 @@ document.addEventListener('DOMContentLoaded', () => { window.location.href = '/login.html'; // Redirect the user to the login page }); } - - // Handle the Add User form submission - const addUserForm = document.getElementById('add-user-form'); - const addUserError = document.getElementById('add-user-error'); - addUserForm.addEventListener('submit', async (e) => { - e.preventDefault(); // Prevent the default form submission behaviour - addUserError.textContent = ''; // Clear any previous error messages - - // Retrieve the input values from the form - const username = document.getElementById('add-username').value.trim(); - const password = document.getElementById('add-password').value.trim(); - const role = document.getElementById('add-role').value; - - try { - // Send a POST request to add a new user - const res = await fetch(`${API_URL}/add_user`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...authHeaders() // Include the authentication headers - }, - body: JSON.stringify({ username, password, role }) // Send the user data as JSON - }); - - if (!res.ok) { - const data = await res.json(); - addUserError.textContent = data.detail || 'Failed to add user.'; // Show an error message - return; - } - - alert('User added successfully.'); // Show a success message - } catch (err) { - addUserError.textContent = 'Error adding user.'; // Show a generic error message - } - }); - - // Handle the Delete User form submission - const deleteUserForm = document.getElementById('delete-user-form'); - const deleteUserError = document.getElementById('delete-user-error'); - deleteUserForm.addEventListener('submit', async (e) => { - e.preventDefault(); - deleteUserError.textContent = ''; // Clear any previous error messages - - // Retrieve the username to delete - const username = document.getElementById('delete-username').value.trim(); - - try { - // Send a POST request to delete the user - const res = await fetch(`${API_URL}/delete_user`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...authHeaders() // Include the authentication headers - }, - body: JSON.stringify({ username }) // Send the username as JSON - }); - - if (!res.ok) { - const data = await res.json(); - deleteUserError.textContent = data.detail || 'Failed to delete user.'; // Show an error message - return; - } - - alert('User deleted successfully.'); // Show a success message - } catch (err) { - deleteUserError.textContent = 'Error deleting user.'; // Show a generic error message - } - }); - - // Handle the Update Username form submission - const updateUsernameForm = document.getElementById('update-username-form'); - const updateUsernameError = document.getElementById('update-username-error'); - updateUsernameForm.addEventListener('submit', async (e) => { - e.preventDefault(); - updateUsernameError.textContent = ''; // Clear any previous error messages - - // Retrieve the old and new usernames - const old_username = document.getElementById('old-username').value.trim(); - const new_username = document.getElementById('new-username').value.trim(); - - try { - // Send a PUT request to update the username - const res = await fetch(`${API_URL}/update_username`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - ...authHeaders() // Include the authentication headers - }, - body: JSON.stringify({ old_username, new_username }) // Send the old and new usernames as JSON - }); - - if (!res.ok) { - const data = await res.json(); - updateUsernameError.textContent = data.detail || 'Failed to update username.'; // Show an error message - return; - } - - alert('Username updated successfully.'); // Show a success message - } catch (err) { - updateUsernameError.textContent = 'Error updating username.'; // Show a generic error message - } - }); - - // Handle the Update Password form submission - const updatePasswordForm = document.getElementById('update-password-form'); - const updatePasswordError = document.getElementById('update-password-error'); - updatePasswordForm.addEventListener('submit', async (e) => { - e.preventDefault(); - updatePasswordError.textContent = ''; // Clear any previous error messages - - // Retrieve the username and new password - const username = document.getElementById('update-password-username').value.trim(); - const new_password = document.getElementById('new-password').value.trim(); - - try { - // Send a PUT request to update the user's password - const res = await fetch(`${API_URL}/update_password`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - ...authHeaders() // Include the authentication headers - }, - body: JSON.stringify({ username, new_password }) // Send the username and new password as JSON - }); - - if (!res.ok) { - const data = await res.json(); - updatePasswordError.textContent = data.detail || 'Failed to update password.'; // Show an error message - return; +} + +/** + * Set up all form submission handlers. + */ +function setupFormHandlers() { + setupFormHandler('add-user-form', handleAddUser, 'add-user-error'); + setupFormHandler('delete-user-form', handleDeleteUser, 'delete-user-error'); + setupFormHandler('update-username-form', handleUpdateUsername, 'update-username-error'); + setupFormHandler('update-password-form', handleUpdatePassword, 'update-password-error'); + setupFormHandler('set-active-status-form', handleSetActiveStatus, 'set-active-status-error'); +} + +/** + * Generic function to set up form submission handlers. + */ +function setupFormHandler(formId, handlerFunction, errorElementId) { + const form = document.getElementById(formId); + const errorElement = document.getElementById(errorElementId); + + if (form) { + form.addEventListener('submit', async (e) => { + e.preventDefault(); + errorElement.textContent = ''; // Clear any previous error messages + try { + await handlerFunction(); + } catch (err) { + errorElement.textContent = 'An error occurred while processing the form.'; } - - alert('Password updated successfully.'); // Show a success message - } catch (err) { - updatePasswordError.textContent = 'Error updating password.'; // Show a generic error message - } + }); + } +} + +/** + * Handle the Add User form submission. + */ +async function handleAddUser() { + const username = document.getElementById('add-username').value.trim(); + const password = document.getElementById('add-password').value.trim(); + const role = document.getElementById('add-role').value; + + await sendRequest(`${API_URL}/add_user`, 'POST', { username, password, role }); + alert('User added successfully.'); +} + +/** + * Handle the Delete User form submission. + */ +async function handleDeleteUser() { + const username = document.getElementById('delete-username').value.trim(); + await sendRequest(`${API_URL}/delete_user`, 'POST', { username }); + alert('User deleted successfully.'); +} + +/** + * Handle the Update Username form submission. + */ +async function handleUpdateUsername() { + const old_username = document.getElementById('old-username').value.trim(); + const new_username = document.getElementById('new-username').value.trim(); + await sendRequest(`${API_URL}/update_username`, 'PUT', { old_username, new_username }); + alert('Username updated successfully.'); +} + +/** + * Handle the Update Password form submission. + */ +async function handleUpdatePassword() { + const username = document.getElementById('update-password-username').value.trim(); + const new_password = document.getElementById('new-password').value.trim(); + await sendRequest(`${API_URL}/update_password`, 'PUT', { username, new_password }); + alert('Password updated successfully.'); +} + +/** + * Handle the Set Active Status form submission. + */ +async function handleSetActiveStatus() { + const username = document.getElementById('active-status-username').value.trim(); + const is_active = document.getElementById('is-active').checked; + await sendRequest(`${API_URL}/set_user_active_status`, 'PUT', { username, is_active }); + alert('User active status updated successfully.'); +} + +/** + * Helper function to send API requests. + */ +async function sendRequest(url, method, body) { + const res = await fetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + ...authHeaders(), + }, + body: JSON.stringify(body), }); - // Handle the Set Active Status form submission - const setActiveStatusForm = document.getElementById('set-active-status-form'); - const setActiveStatusError = document.getElementById('set-active-status-error'); - setActiveStatusForm.addEventListener('submit', async (e) => { - e.preventDefault(); - setActiveStatusError.textContent = ''; // Clear any previous error messages - - // Retrieve the username and active status - const username = document.getElementById('active-status-username').value.trim(); - const is_active = document.getElementById('is-active').checked; - - try { - // Send a PUT request to update the user's active status - const res = await fetch(`${API_URL}/set_user_active_status`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - ...authHeaders() // Include the authentication headers - }, - body: JSON.stringify({ username, is_active }) // Send the username and active status as JSON - }); - - if (!res.ok) { - const data = await res.json(); - setActiveStatusError.textContent = data.detail || 'Failed to update active status.'; // Show an error message - return; - } - - alert('User active status updated successfully.'); // Show a success message - } catch (err) { - setActiveStatusError.textContent = 'Error updating active status.'; // Show a generic error message - } - }); -}); + if (!res.ok) { + const data = await res.json(); + throw new Error(data.detail || 'Request failed.'); + } +}