(30-05-2025, 09:11 AM)grostoto a écrit : j'ai essayé de mette mon home assistant (192.168.1.XX:8123) dans la console, mais cela ne fonctionne pas..
je pense qu'il ne prend pas en compte le 8123.
alors homeassistant bloque les connexions iframe ....c est foutu pour HA pour l instant
re edition
pour HA : ajouter dans config.yam
http:
cors_allowed_origins:
- "*"
use_x_frame_options: false
pour l instant version html
Code :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard Multi-Interfaces</title>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
background: #222;
}
/* Styles pour le mode configuration */
.config-mode {
background-color: #f5f5f5 !important;
}
.config-container {
display: none;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.config-mode .config-container {
display: block;
}
.config-panel {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
.ip-list {
margin-bottom: 20px;
}
.ip-group {
margin-bottom: 15px;
padding: 15px;
background-color: #f9f9f9;
border-radius: 5px;
display: flex;
align-items: center;
gap: 10px;
}
.ip-number {
font-weight: bold;
color: #555;
min-width: 30px;
}
input[type="text"] {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
input[type="text"]:focus {
outline: none;
border-color: #4CAF50;
}
.remove-btn {
background-color: #f44336;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.remove-btn:hover {
background-color: #d32f2f;
}
.add-btn {
background-color: #2196F3;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin-bottom: 20px;
width: 100%;
}
.add-btn:hover {
background-color: #1976D2;
}
.button-group {
margin-top: 30px;
display: flex;
gap: 10px;
justify-content: center;
}
button {
padding: 12px 30px;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
.save-btn {
background-color: #4CAF50;
color: white;
}
.save-btn:hover {
background-color: #45a049;
}
.message {
margin-top: 20px;
padding: 10px;
border-radius: 5px;
text-align: center;
display: none;
}
.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.layout-preview {
margin-top: 20px;
padding: 15px;
background-color: #e3f2fd;
border-radius: 5px;
text-align: center;
font-size: 14px;
color: #1976D2;
}
.help-text {
font-size: 12px;
color: #666;
margin-top: 15px;
padding: 10px;
background-color: #f0f0f0;
border-radius: 5px;
line-height: 1.6;
}
/* Styles pour le mode dashboard */
.dashboard-container {
display: none;
height: 100vh;
gap: 2px;
background: #222;
}
.dashboard-mode .dashboard-container {
display: grid;
}
.frame-container {
position: relative;
overflow: hidden;
background: white;
}
.frame-wrapper {
width: 100%;
height: 100%;
transform-origin: top left;
transition: transform 0.3s ease;
}
iframe {
width: 100%;
height: 100%;
border: none;
background: white;
}
/* Contrôles de zoom */
.zoom-controls {
position: absolute;
top: 5px;
right: 5px;
background: rgba(0,0,0,0.8);
padding: 5px;
border-radius: 3px;
z-index: 10;
display: flex;
gap: 5px;
}
.zoom-btn {
width: 25px;
height: 25px;
border: none;
background: #444;
color: white;
cursor: pointer;
border-radius: 3px;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.zoom-btn:hover {
background: #666;
}
.zoom-value {
color: white;
font-family: Arial, sans-serif;
font-size: 12px;
min-width: 45px;
text-align: center;
display: flex;
align-items: center;
}
/* Label optionnel */
.frame-label {
position: absolute;
top: 5px;
left: 5px;
background: rgba(0,0,0,0.7);
color: white;
padding: 5px 10px;
font-family: Arial, sans-serif;
font-size: 12px;
border-radius: 3px;
z-index: 10;
pointer-events: none;
max-width: 70%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Panneau de contrôle global */
.global-controls {
display: none;
position: fixed;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.9);
padding: 10px 20px;
border-radius: 5px;
z-index: 100;
}
.dashboard-mode .global-controls {
display: block;
}
.global-btn {
background: #4CAF50;
color: white;
border: none;
padding: 8px 15px;
margin: 0 5px;
border-radius: 3px;
cursor: pointer;
}
.global-btn:hover {
background: #45a049;
}
.config-btn {
background: #FF9800;
}
.config-btn:hover {
background: #F57C00;
}
/* Message d'accueil */
.welcome-message {
text-align: center;
padding: 40px;
background-color: #e3f2fd;
border-radius: 10px;
margin-bottom: 30px;
}
.welcome-message h2 {
color: #1976D2;
margin-bottom: 10px;
}
/* Message d'erreur iframe */
.iframe-error {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #f8d7da;
color: #721c24;
padding: 20px;
border-radius: 8px;
text-align: center;
max-width: 80%;
z-index: 20;
display: none;
}
.iframe-error h3 {
margin: 0 0 10px 0;
font-size: 16px;
}
.iframe-error p {
margin: 5px 0;
font-size: 14px;
}
.iframe-error code {
background: rgba(0,0,0,0.1);
padding: 2px 5px;
border-radius: 3px;
font-size: 12px;
}
</style>
</head>
<body class="config-mode">
<!-- Mode Configuration -->
<div class="config-container">
<div class="config-panel">
<h1>Dashboard Multi-Interfaces</h1>
<div id="welcomeMessage" class="welcome-message">
<h2>Bienvenue !</h2>
<p>Configurez vos adresses ci-dessous pour commencer.</p>
</div>
<div id="ipList" class="ip-list"></div>
<button class="add-btn" onclick="addIP()">+ Ajouter une interface</button>
<div class="help-text">
<strong>Formats acceptés :</strong><br>
• IP : 192.168.1.100<br>
• Domaine : example.com<br>
• Sous-domaine : app.example.com<br>
• URL complète : https://example.com/dashboard<br>
• Port personnalisé : 192.168.1.100:8080 ou example.com:3000
</div>
<div id="layoutPreview" class="layout-preview"></div>
<div class="button-group">
<button class="save-btn" onclick="showDashboard()">Afficher le dashboard</button>
<button class="save-btn" style="background-color: #FF5722;" onclick="saveToFile()">? Sauvegarder config</button>
</div>
<div id="message" class="message"></div>
</div>
</div>
<!-- Mode Dashboard -->
<div id="dashboardContainer" class="dashboard-container"></div>
<!-- Contrôles globaux du dashboard -->
<div class="global-controls">
<button class="global-btn" onclick="resetAllZoom()">Réinitialiser tout</button>
<button class="global-btn" onclick="zoomAll(-0.1)">Tout réduire</button>
<button class="global-btn" onclick="zoomAll(0.1)">Tout agrandir</button>
<button class="global-btn config-btn" onclick="showConfig()">⚙️ Configuration</button>
</div>
<script>
// Configuration des adresses - MODIFIÉE AUTOMATIQUEMENT
let addresses = ['192.168.1.145'];
let zoomLevels = {};
let currentMode = 'config';
// Initialisation
window.onload = function() {
// Si des adresses sont déjà configurées dans le fichier
if (addresses.length > 0 && addresses[0] !== '') {
document.getElementById('welcomeMessage').style.display = 'none';
// Aller directement au dashboard si configuré
if (addresses.length > 1 || (addresses.length === 1 && addresses[0] !== '192.168.1.145')) {
showDashboard();
}
}
renderIPList();
};
// Afficher la liste des adresses
function renderIPList() {
const container = document.getElementById('ipList');
container.innerHTML = '';
addresses.forEach((address, index) => {
const div = document.createElement('div');
div.className = 'ip-group';
div.innerHTML = `
<span class="ip-number">${index + 1}.</span>
<input type="text" id="address${index}" value="${address}" placeholder="Ex: 192.168.1.100, example.com, https://app.example.com">
${addresses.length > 1 ? `<button class="remove-btn" onclick="removeIP(${index})">Supprimer</button>` : ''}
`;
container.appendChild(div);
});
updateLayoutPreview();
}
// Ajouter une adresse
function addIP() {
addresses.push('');
renderIPList();
}
// Supprimer une adresse
function removeIP(index) {
addresses.splice(index, 1);
renderIPList();
}
// Mettre à jour l'aperçu
function updateLayoutPreview() {
const preview = document.getElementById('layoutPreview');
const count = addresses.length;
let layout = '';
if (count === 1) {
layout = 'Plein écran';
} else if (count === 2) {
layout = '2 colonnes côte à côte';
} else if (count === 3) {
layout = '1 grande vue à gauche + 2 petites à droite';
} else if (count === 4) {
layout = 'Grille 2x2';
} else if (count <= 6) {
layout = 'Grille 2x3';
} else if (count <= 9) {
layout = 'Grille 3x3';
} else {
layout = `Grille ${Math.ceil(Math.sqrt(count))}x${Math.ceil(count / Math.ceil(Math.sqrt(count)))}`;
}
preview.innerHTML = `<strong>Disposition :</strong> ${count} interface${count > 1 ? 's' : ''} - ${layout}`;
}
// Valider et formater une adresse
function validateAndFormatAddress(address) {
// Supprimer les espaces
address = address.trim();
if (!address) {
return { valid: false, error: "L'adresse ne peut pas être vide" };
}
// Si c'est déjà une URL complète avec protocole
if (address.match(/^https?:\/\//)) {
return { valid: true, formatted: address, display: address };
}
// Vérifier si c'est une IP valide
const ipPattern = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?::\d{1,5})?$/;
if (ipPattern.test(address)) {
return {
valid: true,
formatted: `http://${address}`,
display: address
};
}
// Vérifier si c'est un domaine ou sous-domaine valide
const domainPattern = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}(:\d{1,5})?(\/.*)?$/;
const localhostPattern = /^localhost(:\d{1,5})?(\/.*)?$/;
if (domainPattern.test(address) || localhostPattern.test(address)) {
return {
valid: true,
formatted: `http://${address}`,
display: address
};
}
// Si c'est juste un nom sans extension (peut-être un hostname local)
if (/^[a-zA-Z0-9-]+(:\d{1,5})?(\/.*)?$/.test(address)) {
return {
valid: true,
formatted: `http://${address}`,
display: address
};
}
return { valid: false, error: "Format d'adresse non valide" };
}
// Afficher un message
function showMessage(text, type) {
const messageEl = document.getElementById('message');
messageEl.textContent = text;
messageEl.className = 'message ' + type;
messageEl.style.display = 'block';
setTimeout(() => {
messageEl.style.display = 'none';
}, 3000);
}
// Calculer la disposition
function calculateGridLayout(count) {
if (count === 1) return { cols: 1, rows: 1, special: 'single' };
if (count === 2) return { cols: 2, rows: 1, special: 'two-columns' };
if (count === 3) return { cols: 2, rows: 2, special: 'three-special' };
if (count === 4) return { cols: 2, rows: 2, special: 'grid' };
const cols = Math.ceil(Math.sqrt(count));
const rows = Math.ceil(count / cols);
return { cols, rows, special: 'grid' };
}
// Afficher le dashboard
function showDashboard() {
// Récupérer et valider les adresses
const validatedAddresses = [];
for (let i = 0; i < addresses.length; i++) {
const addressInput = document.getElementById(`address${i}`);
const addressValue = addressInput ? addressInput.value.trim() : addresses[i];
const validation = validateAndFormatAddress(addressValue);
if (!validation.valid) {
showMessage(`Adresse n°${i + 1} : ${validation.error}`, 'error');
return;
}
validatedAddresses.push(validation);
}
addresses = validatedAddresses.map(v => v.display);
// Construire le dashboard avec les adresses formatées
buildDashboard(validatedAddresses);
// Basculer en mode dashboard
document.body.className = 'dashboard-mode';
currentMode = 'dashboard';
}
// Sauvegarder la configuration dans le fichier actuel
function saveToFile() {
// Récupérer et valider les adresses
const newAddresses = [];
for (let i = 0; i < addresses.length; i++) {
const addressValue = document.getElementById(`address${i}`).value.trim();
const validation = validateAndFormatAddress(addressValue);
if (!validation.valid) {
showMessage(`Adresse n°${i + 1} : ${validation.error}`, 'error');
return;
}
newAddresses.push(addressValue);
}
// Demander le nom du fichier
let filename = prompt('Nom du fichier à sauvegarder :', 'dashboard.html');
if (!filename) return; // Annulé
// Ajouter .html si pas présent
if (!filename.endsWith('.html')) {
filename += '.html';
}
// Obtenir le HTML actuel
const currentHTML = document.documentElement.outerHTML;
// Remplacer la configuration actuelle
const modifiedHTML = currentHTML.replace(
/let addresses = \[[^\]]*\];/,
`let addresses = ${JSON.stringify(newAddresses)};`
);
// Créer et télécharger le fichier
const blob = new Blob([modifiedHTML], { type: 'text/html' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
showMessage(`Configuration sauvegardée dans ${filename} !`, 'success');
}
// Construire le dashboard
function buildDashboard(validatedAddresses) {
const container = document.getElementById('dashboardContainer');
const layout = calculateGridLayout(validatedAddresses.length);
// Appliquer les styles de grille
if (layout.special === 'single') {
container.style.gridTemplateColumns = '1fr';
container.style.gridTemplateRows = '1fr';
} else if (layout.special === 'two-columns') {
container.style.gridTemplateColumns = '1fr 1fr';
container.style.gridTemplateRows = '1fr';
} else if (layout.special === 'three-special') {
container.style.gridTemplateColumns = '1fr 1fr';
container.style.gridTemplateRows = '1fr 1fr';
} else {
container.style.gridTemplateColumns = `repeat(${layout.cols}, 1fr)`;
container.style.gridTemplateRows = `repeat(${layout.rows}, 1fr)`;
}
// Créer les conteneurs
container.innerHTML = '';
validatedAddresses.forEach((addressInfo, index) => {
const frameDiv = document.createElement('div');
frameDiv.className = 'frame-container';
// Style spécial pour 3 interfaces
if (layout.special === 'three-special' && index === 0) {
frameDiv.style.gridRow = 'span 2';
}
frameDiv.innerHTML = `
<span class="frame-label" title="${addressInfo.display}">${addressInfo.display}</span>
<div class="zoom-controls">
<button class="zoom-btn" onclick="zoom(${index + 1}, -0.1)">−</button>
<span class="zoom-value" id="zoom${index + 1}">100%</span>
<button class="zoom-btn" onclick="zoom(${index + 1}, 0.1)">+</button>
</div>
<div class="frame-wrapper" id="frame${index + 1}">
<iframe
src="${addressInfo.formatted}"
sandbox="allow-same-origin allow-scripts allow-popups allow-forms allow-modals allow-downloads allow-presentation allow-top-navigation"
referrerpolicy="no-referrer"
id="iframe${index + 1}"
onerror="handleIframeError(${index + 1}, '${addressInfo.display}')"
></iframe>
</div>
<div class="iframe-error" id="error${index + 1}">
<h3>⚠️ Impossible de charger ${addressInfo.display}</h3>
<p>Cette interface ne peut pas être affichée dans une iframe.</p>
<p><small>Pour Home Assistant, ajoutez dans configuration.yaml :</small></p>
<code>http:<br> use_x_frame_options: false</code>
<p style="margin-top: 15px;">
<a href="${addressInfo.formatted}" target="_blank" style="color: #0056b3;">Ouvrir dans un nouvel onglet →</a>
</p>
</div>
`;
container.appendChild(frameDiv);
});
// Initialiser les niveaux de zoom
zoomLevels = {};
for (let i = 1; i <= validatedAddresses.length; i++) {
zoomLevels[i] = 1;
}
}
// Retourner à la configuration
function showConfig() {
document.body.className = 'config-mode';
currentMode = 'config';
renderIPList();
}
// Fonctions de zoom
function zoom(frameNum, delta) {
zoomLevels[frameNum] = Math.max(0.2, Math.min(2, zoomLevels[frameNum] + delta));
updateZoom(frameNum);
}
function updateZoom(frameNum) {
const frame = document.getElementById(`frame${frameNum}`);
const zoomDisplay = document.getElementById(`zoom${frameNum}`);
const scale = zoomLevels[frameNum];
frame.style.transform = `scale(${scale})`;
frame.style.width = `${100 / scale}%`;
frame.style.height = `${100 / scale}%`;
zoomDisplay.textContent = `${Math.round(scale * 100)}%`;
}
function resetAllZoom() {
for (let i = 1; i <= addresses.length; i++) {
zoomLevels[i] = 1;
updateZoom(i);
}
}
function zoomAll(delta) {
for (let i = 1; i <= addresses.length; i++) {
zoom(i, delta);
}
}
// Gestion des erreurs d'iframe
function handleIframeError(frameNum, address) {
console.error(`Erreur de chargement pour l'iframe ${frameNum}: ${address}`);
const errorDiv = document.getElementById(`error${frameNum}`);
if (errorDiv) {
errorDiv.style.display = 'block';
}
}
// Détection des erreurs de chargement après un délai
function checkIframeLoading() {
if (currentMode === 'dashboard') {
for (let i = 1; i <= addresses.length; i++) {
const iframe = document.getElementById(`iframe${i}`);
if (iframe) {
iframe.addEventListener('load', function() {
try {
// Tenter d'accéder au document pour vérifier si c'est bloqué
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
// Si on arrive ici, l'iframe est chargée correctement
} catch (e) {
// Erreur de cross-origin, probablement bloqué
console.warn(`L'iframe ${i} pourrait être bloquée par des politiques de sécurité`);
}
});
}
}
}
}
// Appeler la vérification après le chargement du dashboard
setTimeout(checkIframeLoading, 1000);
</script>
</body>
</html>
j essaye de voir pour esp ....