templates/api/kelsync/device.html.twig line 1

Open in your IDE?
  1. {% extends 'base.html.twig' %}
  2. {% block body %}
  3.     <div class="container mt-4">
  4.         <div class="d-flex justify-content-between align-items-center mb-4">
  5.             <div>
  6.                 <h2>
  7.                     <span id="deviceName">{{ device.name }}</span>
  8.                     <button class="btn btn-sm btn-outline-secondary ml-2" id="editDeviceNameBtn" title="Renommer">
  9.                         <i class="fa fa-pencil"></i>
  10.                     </button>
  11.                 </h2>
  12.                 <small class="text-muted">ID : {{ device.deviceId }}{% if device.ip %} - IP : {{ device.ip }}{% endif %} - {{ device.updateDate|date("d/m/Y H:i") }}</small>
  13.             </div>
  14.             <div>
  15.                 <button class="btn btn-primary" data-toggle="modal" data-target="#addLinkModal">
  16.                     <i class="fa fa-plus"></i> Ajouter un lien
  17.                 </button>
  18.                 {% if device.linkItems|length == 0 %}
  19.                     <button class="btn btn-danger ml-2" id="deleteDeviceBtn">
  20.                         <i class="fa fa-trash"></i> Supprimer le device
  21.                     </button>
  22.                 {% endif %}
  23.             </div>
  24.         </div>
  25.         <div id="linksList" class="list-group" style="gap: 0;">
  26.             {% for link in links %}
  27.                 <div class="list-group-item sortable-item {{ link.enabled ? '' : 'disabled-link' }}"
  28.                      data-link-id="{{ link.id }}"
  29.                      data-order="{{ link.sortOrder }}"
  30.                      data-enabled="{{ link.enabled ? '1' : '0' }}"
  31.                      style="cursor: move;user-select: none; margin-bottom: 8px;padding: 5px 15px;">
  32.                     <div class="d-flex align-items-center">
  33.                         <!-- Icône SVG -->
  34.                         {% if link.svgIcon %}
  35.                             <div class="mr-3" style="width: 35px; height: 35px; flex-shrink: 0;">
  36.                                 {{ link.svgIcon|raw }}
  37.                             </div>
  38.                         {% endif %}
  39.                         <!-- Contenu principal -->
  40.                         <div class="flex-grow-1">
  41.                             <h6>
  42.                                 {{ link.title }}
  43.                                 {% if not link.enabled %}
  44.                                     <span class="badge badge-secondary ml-2">Désactivé</span>
  45.                                 {% endif %}
  46.                             </h6>
  47.                             <small class="text-muted">
  48.                                 {{ link.link|slice(0, 30) }}
  49.                             </small>
  50.                         </div>
  51.                         <!-- Actions -->
  52.                         <div class="ml-3">
  53.                             <div class="btn-group">
  54.                                 <button class="btn btn-sm btn-outline-{{ link.enabled ? 'warning' : 'success' }} btn-toggle-enabled"
  55.                                         data-link-id="{{ link.id }}"
  56.                                         data-enabled="{{ link.enabled ? '1' : '0' }}"
  57.                                         title="{{ link.enabled ? 'Désactiver' : 'Activer' }}">
  58.                                     <i class="fa fa-{{ link.enabled ? 'eye-slash' : 'eye' }}" aria-hidden="true"></i>
  59.                                 </button>&nbsp;
  60.                                 <button class="btn btn-sm btn-outline-primary btn-edit"
  61.                                         data-link-id="{{ link.id }}"
  62.                                         data-title="{{ link.title }}"
  63.                                         data-link="{{ link.link }}"
  64.                                         data-svg="{{ link.svgIcon }}"
  65.                                         title="Modifier">
  66.                                     <i class="fa fa-pencil" aria-hidden="true"></i>
  67.                                 </button>&nbsp;
  68.                                 <button class="btn btn-sm btn-outline-danger btn-delete"
  69.                                         data-link-id="{{ link.id }}"
  70.                                         title="Supprimer">
  71.                                     <i class="fa fa-trash" aria-hidden="true"></i>
  72.                                 </button>
  73.                             </div>
  74.                         </div>
  75.                     </div>
  76.                 </div>
  77.             {% else %}
  78.                 <div class="alert alert-info">
  79.                     Aucun lien pour le moment. Cliquez sur "Ajouter un lien" pour commencer.
  80.                 </div>
  81.             {% endfor %}
  82.         </div>
  83.     </div>
  84.     <!-- Modal Ajouter/Éditer Lien -->
  85.     <div class="modal fade" id="addLinkModal" tabindex="-1" role="dialog" aria-labelledby="modalTitle" aria-hidden="true">
  86.         <div class="modal-dialog modal-lg" role="document">
  87.             <div class="modal-content">
  88.                 <div class="modal-header">
  89.                     <h5 class="modal-title" id="modalTitle">Ajouter un lien</h5>
  90.                     <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  91.                         <span aria-hidden="true">&times;</span>
  92.                     </button>
  93.                 </div>
  94.                 <div class="modal-body">
  95.                     <form id="linkForm">
  96.                         <input type="hidden" id="linkId" value="">
  97.                         <div class="form-group">
  98.                             <label for="linkTitle">Titre *</label>
  99.                             <input type="text" class="form-control" id="linkTitle" required>
  100.                         </div>
  101.                         <div class="form-group">
  102.                             <label for="linkUrl">URL *</label>
  103.                             <input type="url" class="form-control" id="linkUrl" required>
  104.                         </div>
  105.                         <div class="form-group">
  106.                             <label for="linkSvg">Code SVG</label>
  107.                             <textarea class="form-control" id="linkSvg" rows="4"></textarea>
  108.                         </div>
  109.                     </form>
  110.                 </div>
  111.                 <div class="modal-footer">
  112.                     <button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
  113.                     <button type="button" class="btn btn-primary" id="saveLinkBtn">Enregistrer</button>
  114.                 </div>
  115.             </div>
  116.         </div>
  117.     </div>
  118.     <!-- Modal Renommer Device -->
  119.     <div class="modal fade" id="editDeviceModal" tabindex="-1" role="dialog" aria-labelledby="editDeviceModalTitle" aria-hidden="true">
  120.         <div class="modal-dialog" role="document">
  121.             <div class="modal-content">
  122.                 <div class="modal-header">
  123.                     <h5 class="modal-title" id="editDeviceModalTitle">Renommer le device</h5>
  124.                     <button type="button" class="close" data-dismiss="modal" aria-label="Close">
  125.                         <span aria-hidden="true">&times;</span>
  126.                     </button>
  127.                 </div>
  128.                 <div class="modal-body">
  129.                     <form id="deviceNameForm">
  130.                         <div class="form-group">
  131.                             <label for="deviceNameInput">Nouveau nom *</label>
  132.                             <input type="text" class="form-control" id="deviceNameInput" required value="{{ device.name }}">
  133.                         </div>
  134.                     </form>
  135.                 </div>
  136.                 <div class="modal-footer">
  137.                     <button type="button" class="btn btn-secondary" data-dismiss="modal">Annuler</button>
  138.                     <button type="button" class="btn btn-primary" id="saveDeviceNameBtn">Enregistrer</button>
  139.                 </div>
  140.             </div>
  141.         </div>
  142.     </div>
  143.     <style>
  144.         .list-group-item {
  145.             transition: all 0.2s ease;
  146.         }
  147.         .list-group-item:hover {
  148.             background-color: #555555;
  149.             box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  150.         }
  151.         .list-group-item.disabled-link {
  152.             opacity: .5;
  153.         }
  154.         .list-group-item.disabled-link:hover {
  155.             background-color: #e9ecef;
  156.         }
  157.         .sortable-ghost {
  158.             opacity: 0.3 !important;
  159.             background: #e3f2fd !important;
  160.             border: 2px dashed #007bff !important;
  161.         }
  162.         .sortable-chosen {
  163.             background-color: #f8f9fa;
  164.         }
  165.         .sortable-drag {
  166.             opacity: 0.8 !important;
  167.             cursor: grabbing !important;
  168.         }
  169.         .drag-handle:hover {
  170.             color: #007bff !important;
  171.         }
  172.         .sortable-item {
  173.             position: relative;
  174.         }
  175.         #linksList {
  176.             min-height: 100px;
  177.         }
  178.     </style>
  179. {% endblock %}
  180. {% block javascripts %}
  181.     {{ parent() }}
  182.     <script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
  183.     <script>
  184.         const deviceId = {{ device.id }};
  185.         let editingLinkId = null;
  186.         // SVG par défaut
  187.         const defaultSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  188.     <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path>
  189.     <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path>
  190. </svg>`;
  191.         // Initialiser Sortable pour le drag & drop
  192.         const linksList = document.getElementById('linksList');
  193.         if (linksList) {
  194.             const sortable = new Sortable(linksList, {
  195.                 animation: 200,
  196.                 ghostClass: 'sortable-ghost',
  197.                 chosenClass: 'sortable-chosen',
  198.                 dragClass: 'sortable-drag',
  199.                 forceFallback: false,
  200.                 fallbackOnBody: true,
  201.                 swapThreshold: 0.65,
  202.                 draggable: '.sortable-item',
  203.                 filter: '.btn, a, input, textarea, select',
  204.                 preventOnFilter: true,
  205.                 onStart: function(evt) {
  206.                     evt.item.style.opacity = '0.5';
  207.                 },
  208.                 onEnd: function(evt) {
  209.                     evt.item.style.opacity = '1';
  210.                     if (evt.oldIndex !== evt.newIndex) {
  211.                         saveOrder();
  212.                     }
  213.                 }
  214.             });
  215.         }
  216.         // Fonction pour sauvegarder l'ordre
  217.         function saveOrder() {
  218.             const orders = {};
  219.             document.querySelectorAll('[data-link-id]').forEach((el, index) => {
  220.                 orders[el.dataset.linkId] = index;
  221.             });
  222.             fetch(`/kelsync/device/${deviceId}/link/reorder`, {
  223.                 method: 'POST',
  224.                 headers: { 'Content-Type': 'application/json' },
  225.                 body: JSON.stringify({ orders })
  226.             })
  227.                 .then(response => response.json())
  228.                 .then(data => {
  229.                     if (data.success) {
  230.                         console.log('Ordre sauvegardé');
  231.                     }
  232.                 })
  233.                 .catch(error => {
  234.                     console.error('Erreur lors de la sauvegarde:', error);
  235.                     alert('Erreur lors de la sauvegarde de l\'ordre');
  236.                 });
  237.         }
  238.         // Fonction pour fermer la modal
  239.         function closeModal(modalId) {
  240.             var modal = document.getElementById(modalId);
  241.             modal.classList.remove('show');
  242.             modal.style.display = 'none';
  243.             modal.setAttribute('aria-hidden', 'true');
  244.             modal.removeAttribute('aria-modal');
  245.             var backdrop = document.querySelector('.modal-backdrop');
  246.             if (backdrop) {
  247.                 backdrop.remove();
  248.             }
  249.             document.body.classList.remove('modal-open');
  250.         }
  251.         // Fonction pour ouvrir la modal
  252.         function openModal(modalId) {
  253.             var modal = document.getElementById(modalId);
  254.             modal.classList.add('show');
  255.             modal.style.display = 'block';
  256.             modal.setAttribute('aria-modal', 'true');
  257.             modal.removeAttribute('aria-hidden');
  258.             var backdrop = document.createElement('div');
  259.             backdrop.className = 'modal-backdrop fade show';
  260.             document.body.appendChild(backdrop);
  261.             document.body.classList.add('modal-open');
  262.         }
  263.         // Gérer les boutons de fermeture
  264.         document.querySelectorAll('[data-dismiss="modal"]').forEach(function(btn) {
  265.             btn.addEventListener('click', function() {
  266.                 const modal = btn.closest('.modal');
  267.                 closeModal(modal.id);
  268.             });
  269.         });
  270.         // Fermer en cliquant sur le backdrop
  271.         document.addEventListener('click', function(e) {
  272.             if (e.target.classList.contains('modal-backdrop')) {
  273.                 const openModal = document.querySelector('.modal.show');
  274.                 if (openModal) {
  275.                     closeModal(openModal.id);
  276.                 }
  277.             }
  278.         });
  279.         // Ouvrir modal pour ajouter un lien
  280.         document.querySelector('[data-target="#addLinkModal"]').addEventListener('click', function() {
  281.             editingLinkId = null;
  282.             document.getElementById('modalTitle').textContent = 'Ajouter un lien';
  283.             document.getElementById('linkForm').reset();
  284.             document.getElementById('linkId').value = '';
  285.             document.getElementById('linkSvg').value = defaultSvg;
  286.             openModal('addLinkModal');
  287.         });
  288.         // Ouvrir modal pour éditer un lien
  289.         document.addEventListener('click', function(e) {
  290.             if (e.target.closest('.btn-edit')) {
  291.                 const btn = e.target.closest('.btn-edit');
  292.                 const linkId = btn.dataset.linkId;
  293.                 editingLinkId = linkId;
  294.                 document.getElementById('modalTitle').textContent = 'Modifier le lien';
  295.                 document.getElementById('linkId').value = linkId;
  296.                 document.getElementById('linkTitle').value = btn.dataset.title || '';
  297.                 document.getElementById('linkUrl').value = btn.dataset.link || '';
  298.                 document.getElementById('linkSvg').value = btn.dataset.svg || '';
  299.                 openModal('addLinkModal');
  300.             }
  301.         });
  302.         // Activer/Désactiver un lien
  303.         document.addEventListener('click', function(e) {
  304.             if (e.target.closest('.btn-toggle-enabled')) {
  305.                 const btn = e.target.closest('.btn-toggle-enabled');
  306.                 const linkId = btn.dataset.linkId;
  307.                 const currentEnabled = btn.dataset.enabled === '1';
  308.                 const newEnabled = !currentEnabled;
  309.                 fetch(`/kelsync/link/${linkId}/toggle`, {
  310.                     method: 'POST',
  311.                     headers: { 'Content-Type': 'application/json' },
  312.                     body: JSON.stringify({ enabled: newEnabled })
  313.                 })
  314.                     .then(response => response.json())
  315.                     .then(data => {
  316.                         if (data.success) {
  317.                             location.reload();
  318.                         }
  319.                     })
  320.                     .catch(error => {
  321.                         console.error('Erreur:', error);
  322.                         alert('Erreur lors de la modification du statut');
  323.                     });
  324.             }
  325.         });
  326.         // Supprimer un lien
  327.         document.addEventListener('click', function(e) {
  328.             if (e.target.closest('.btn-delete')) {
  329.                 if (confirm('Voulez-vous vraiment supprimer ce lien ?')) {
  330.                     const linkId = e.target.closest('.btn-delete').dataset.linkId;
  331.                     fetch(`/kelsync/link/${linkId}/delete`, {
  332.                         method: 'DELETE'
  333.                     })
  334.                         .then(response => response.json())
  335.                         .then(data => {
  336.                             if (data.success) {
  337.                                 location.reload();
  338.                             }
  339.                         })
  340.                         .catch(error => {
  341.                             console.error('Erreur:', error);
  342.                             alert('Erreur lors de la suppression');
  343.                         });
  344.                 }
  345.             }
  346.         });
  347.         // Enregistrer le lien
  348.         document.getElementById('saveLinkBtn').addEventListener('click', function() {
  349.             const form = document.getElementById('linkForm');
  350.             if (!form.checkValidity()) {
  351.                 form.reportValidity();
  352.                 return;
  353.             }
  354.             const linkId = document.getElementById('linkId').value;
  355.             const data = {
  356.                 title: document.getElementById('linkTitle').value,
  357.                 link: document.getElementById('linkUrl').value,
  358.                 svgIcon: document.getElementById('linkSvg').value
  359.             };
  360.             const url = linkId
  361.                 ? `/kelsync/link/${linkId}/edit`
  362.                 : `/kelsync/device/${deviceId}/link/add`;
  363.             fetch(url, {
  364.                 method: 'POST',
  365.                 headers: { 'Content-Type': 'application/json' },
  366.                 body: JSON.stringify(data)
  367.             })
  368.                 .then(response => response.json())
  369.                 .then(data => {
  370.                     if (data.success) {
  371.                         closeModal('addLinkModal');
  372.                         location.reload();
  373.                     }
  374.                 })
  375.                 .catch(error => {
  376.                     alert('Erreur lors de l\'enregistrement');
  377.                 });
  378.         });
  379.         // ========== GESTION DU DEVICE ==========
  380.         // Ouvrir modal pour renommer le device
  381.         document.getElementById('editDeviceNameBtn').addEventListener('click', function() {
  382.             openModal('editDeviceModal');
  383.         });
  384.         // Enregistrer le nouveau nom du device
  385.         document.getElementById('saveDeviceNameBtn').addEventListener('click', function() {
  386.             const form = document.getElementById('deviceNameForm');
  387.             if (!form.checkValidity()) {
  388.                 form.reportValidity();
  389.                 return;
  390.             }
  391.             const newName = document.getElementById('deviceNameInput').value.trim();
  392.             if (!newName) {
  393.                 alert('Le nom ne peut pas être vide');
  394.                 return;
  395.             }
  396.             fetch(`/kelsync/device/${deviceId}/edit`, {
  397.                 method: 'POST',
  398.                 headers: { 'Content-Type': 'application/json' },
  399.                 body: JSON.stringify({ name: newName })
  400.             })
  401.                 .then(response => response.json())
  402.                 .then(data => {
  403.                     if (data.success) {
  404.                         document.getElementById('deviceName').textContent = data.device.name;
  405.                         closeModal('editDeviceModal');
  406.                     } else {
  407.                         alert(data.message || 'Erreur lors de la modification');
  408.                     }
  409.                 })
  410.                 .catch(error => {
  411.                     console.error('Erreur:', error);
  412.                     alert('Erreur lors de la modification du nom');
  413.                 });
  414.         });
  415.         // Supprimer le device
  416.         const deleteBtn = document.getElementById('deleteDeviceBtn');
  417.         if (deleteBtn) {
  418.             deleteBtn.addEventListener('click', function() {
  419.                 if (confirm('Voulez-vous vraiment supprimer ce device ?\n\nNote : Le device ne peut être supprimé que s\'il ne contient aucun lien.')) {
  420.                     fetch(`/kelsync/device/${deviceId}/delete`, {
  421.                         method: 'DELETE'
  422.                     })
  423.                         .then(response => response.json())
  424.                         .then(data => {
  425.                             if (data.success) {
  426.                                 window.location.href = '/kelsync/';
  427.                             } else {
  428.                                 alert(data.message || 'Erreur lors de la suppression');
  429.                             }
  430.                         })
  431.                         .catch(error => {
  432.                             console.error('Erreur:', error);
  433.                             alert('Erreur lors de la suppression du device');
  434.                         });
  435.                 }
  436.             });
  437.         }
  438.     </script>
  439. {% endblock %}