Add Veeam and NetBackup support to inventory and reporting
- Introduced Veeam and NetBackup version tracking in `Inventory.php` for backup validation. - Enhanced backup report in `Backups.php` with improved SQL queries and support for short hostname normalization. - Updated `navbar.html` and related navigation components for better usability and accessibility. - Refactored ActiveDirectory scripts for better modularity and event handling. - Added `AGENTS.md` and `modele.php` as templates for documentation and new page creation.
This commit is contained in:
@@ -1223,12 +1223,6 @@
|
||||
true
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
$table.bootstrapTable(
|
||||
'resetView'
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1236,8 +1230,9 @@
|
||||
|
||||
|
||||
/*
|
||||
* Laisse Bootstrap Table construire son DOM
|
||||
* avant de mesurer la hauteur disponible.
|
||||
* Laisse Bootstrap Table construire (ou reconstruire)
|
||||
* son DOM avant de mesurer la hauteur disponible.
|
||||
* updateTableHeight fait lui-même le resetView avec la hauteur.
|
||||
*/
|
||||
setTimeout(
|
||||
function () {
|
||||
@@ -1280,87 +1275,54 @@
|
||||
}
|
||||
|
||||
|
||||
const $table =
|
||||
$(target).find('table.report-table');
|
||||
|
||||
|
||||
/*
|
||||
* On laisse Bootstrap terminer complètement
|
||||
* l'affichage de l'onglet avant de toucher à Bootstrap Table.
|
||||
* IMPORTANT : on cible la table par son id.
|
||||
* En mode hauteur fixe, Bootstrap Table clone le thead dans une
|
||||
* seconde <table> à laquelle il recopie la classe d'origine
|
||||
* (report-table). Sélectionner par classe renverrait donc AUSSI ce
|
||||
* clone (placé avant l'originale dans le DOM), et .data('bt-initialized')
|
||||
* lirait le clone -> fausse "première init" -> double init -> tableau vidé.
|
||||
* Le clone ne reçoit jamais d'id : [id] ne matche que la vraie table.
|
||||
*/
|
||||
setTimeout(
|
||||
function () {
|
||||
|
||||
function initTable($table) {
|
||||
const $table =
|
||||
$(target).find('table.report-table[id]');
|
||||
|
||||
if ($table.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Important :
|
||||
* ne jamais initialiser Bootstrap Table
|
||||
* pendant que la table est cachée.
|
||||
*/
|
||||
if (!$table.is(':visible')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!$table.data('bt-initialized')) {
|
||||
|
||||
/*
|
||||
* Première ouverture de l'onglet :
|
||||
* Bootstrap Table construit son DOM.
|
||||
* on initialise et on dimensionne pendant que la table est visible.
|
||||
*/
|
||||
$table.bootstrapTable();
|
||||
|
||||
$table.data(
|
||||
'bt-initialized',
|
||||
true
|
||||
);
|
||||
initTable($table);
|
||||
|
||||
} else {
|
||||
|
||||
/*
|
||||
* Table déjà initialisée :
|
||||
* on force Bootstrap Table à recalculer son affichage.
|
||||
* Onglet déjà construit : display:none / show conserve tout le rendu
|
||||
* de Bootstrap Table. On ne refait PAS de resetView, qui viderait
|
||||
* le corps du tableau.
|
||||
*/
|
||||
$table.bootstrapTable(
|
||||
'resetView'
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
syncTheme($table);
|
||||
|
||||
|
||||
/*
|
||||
* Une fois le DOM Bootstrap Table construit,
|
||||
* calcul de la hauteur disponible.
|
||||
* Sauf si un resize a eu lieu pendant que l'onglet était masqué :
|
||||
* dans ce cas seulement, on recalcule la hauteur maintenant.
|
||||
*/
|
||||
setTimeout(
|
||||
function () {
|
||||
if ($table.data('needs-resize')) {
|
||||
|
||||
$table.bootstrapTable(
|
||||
'resetView'
|
||||
);
|
||||
$table.data('needs-resize', false);
|
||||
|
||||
updateTableHeight(
|
||||
$table
|
||||
);
|
||||
|
||||
},
|
||||
50
|
||||
);
|
||||
updateTableHeight($table);
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
10
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
@@ -1377,7 +1339,7 @@
|
||||
setTimeout(
|
||||
function () {
|
||||
|
||||
$('table.report-table').each(
|
||||
$('table.report-table[id]').each(
|
||||
function () {
|
||||
|
||||
syncTheme(
|
||||
@@ -1518,19 +1480,32 @@
|
||||
debounce(
|
||||
function () {
|
||||
|
||||
$('table.report-table:visible').each(
|
||||
$('table.report-table[id]').each(
|
||||
function () {
|
||||
|
||||
const $table =
|
||||
$(this);
|
||||
|
||||
|
||||
if ($table.data('bt-initialized')) {
|
||||
if (!$table.data('bt-initialized')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if ($table.is(':visible')) {
|
||||
|
||||
updateTableHeight(
|
||||
$table
|
||||
);
|
||||
|
||||
} else {
|
||||
|
||||
/*
|
||||
* On ne peut pas mesurer un onglet masqué :
|
||||
* on marque la table pour recalcul à sa prochaine ouverture.
|
||||
*/
|
||||
$table.data('needs-resize', true);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1550,7 +1525,7 @@
|
||||
setTimeout(
|
||||
function () {
|
||||
|
||||
$('table.report-table:visible').each(
|
||||
$('table.report-table[id]:visible').each(
|
||||
function () {
|
||||
|
||||
const $table =
|
||||
|
||||
222
AGENTS.md
Normal file
222
AGENTS.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# AGENTS.md — Instructions Codex
|
||||
|
||||
## Contexte
|
||||
|
||||
Ce projet est un environnement de développement hébergé sur une infrastructure
|
||||
professionnelle.
|
||||
|
||||
Le code présent dans ce workspace peut être modifié par Codex.
|
||||
|
||||
IMPORTANT :
|
||||
Les bases de données auxquelles l'application peut accéder contiennent des
|
||||
DONNÉES DE PRODUCTION.
|
||||
|
||||
La protection et l'intégrité de ces données sont prioritaires.
|
||||
|
||||
---
|
||||
|
||||
## 1. Périmètre autorisé
|
||||
|
||||
Codex peut :
|
||||
|
||||
- Lire tous les fichiers du workspace courant.
|
||||
- Rechercher librement dans les fichiers du workspace.
|
||||
- Créer, modifier, renommer ou supprimer des fichiers dans le workspace
|
||||
lorsque cela est nécessaire à la tâche demandée.
|
||||
- Exécuter des commandes locales nécessaires à l'analyse du projet.
|
||||
- Utiliser Git pour examiner :
|
||||
- status
|
||||
- diff
|
||||
- log
|
||||
- historique
|
||||
- Analyser et modifier du code PHP, JavaScript, HTML, CSS, SQL et PowerShell
|
||||
présent dans le workspace.
|
||||
|
||||
Codex doit rester dans le workspace courant :
|
||||
|
||||
E:\Sites\web-infra-reports
|
||||
|
||||
Ne jamais modifier un fichier situé en dehors de ce workspace sans demander
|
||||
explicitement l'autorisation.
|
||||
|
||||
---
|
||||
|
||||
## 2. Bases de données — RÈGLE CRITIQUE
|
||||
|
||||
Les bases de données accessibles depuis cette application doivent être
|
||||
considérées comme des bases de PRODUCTION.
|
||||
|
||||
### Opérations autorisées
|
||||
|
||||
Codex peut effectuer des opérations SQL strictement en lecture seule :
|
||||
|
||||
SELECT
|
||||
|
||||
Il peut également :
|
||||
|
||||
- analyser les requêtes SQL présentes dans le code ;
|
||||
- écrire ou modifier du SQL dans les fichiers source ;
|
||||
- proposer des requêtes SQL ;
|
||||
- examiner la structure des données lorsqu'elle est accessible en lecture.
|
||||
|
||||
### Opérations interdites
|
||||
|
||||
Codex ne doit JAMAIS exécuter sur une base de données une commande susceptible
|
||||
de modifier les données, la structure ou la configuration.
|
||||
|
||||
Cela comprend notamment :
|
||||
|
||||
INSERT
|
||||
UPDATE
|
||||
DELETE
|
||||
MERGE
|
||||
TRUNCATE
|
||||
DROP
|
||||
ALTER
|
||||
CREATE
|
||||
GRANT
|
||||
REVOKE
|
||||
|
||||
Ne jamais exécuter une procédure stockée ou une fonction SQL susceptible
|
||||
d'avoir des effets de bord.
|
||||
|
||||
En particulier :
|
||||
|
||||
EXEC
|
||||
EXECUTE
|
||||
|
||||
ne doivent être utilisés que s'il est certain que l'opération est strictement
|
||||
en lecture seule.
|
||||
|
||||
En cas de doute :
|
||||
|
||||
NE PAS EXÉCUTER LA REQUÊTE.
|
||||
|
||||
Demander confirmation à l'utilisateur.
|
||||
|
||||
---
|
||||
|
||||
## 3. Credentials et secrets
|
||||
|
||||
Ne jamais :
|
||||
|
||||
- afficher inutilement un mot de passe ;
|
||||
- recopier un mot de passe dans un nouveau fichier ;
|
||||
- ajouter un secret dans Git ;
|
||||
- modifier les credentials existants sans demande explicite ;
|
||||
- transmettre un credential ou un secret vers Internet.
|
||||
|
||||
Si des credentials sont découverts dans le code, les considérer comme
|
||||
confidentiels.
|
||||
|
||||
Ne pas les inclure dans les réponses sauf nécessité absolue.
|
||||
|
||||
---
|
||||
|
||||
## 4. Infrastructure externe
|
||||
|
||||
Ne jamais modifier directement :
|
||||
|
||||
- un autre site IIS ;
|
||||
- une configuration IIS externe au workspace ;
|
||||
- Active Directory ;
|
||||
- DNS ;
|
||||
- un serveur distant ;
|
||||
- une machine virtuelle ;
|
||||
- un partage réseau ;
|
||||
- une tâche planifiée ;
|
||||
- une configuration système Windows ;
|
||||
- une base de données ;
|
||||
- une ressource de production.
|
||||
|
||||
Codex peut analyser le code qui effectue ces opérations et le modifier dans
|
||||
le workspace.
|
||||
|
||||
L'exécution réelle d'une opération d'administration doit être laissée à
|
||||
l'utilisateur sauf demande explicite.
|
||||
|
||||
---
|
||||
|
||||
## 5. Commandes PowerShell et shell
|
||||
|
||||
Les commandes purement consultatives peuvent être exécutées librement.
|
||||
|
||||
Exemples :
|
||||
|
||||
rg
|
||||
Get-Content
|
||||
Select-String
|
||||
Get-ChildItem
|
||||
git status
|
||||
git diff
|
||||
git log
|
||||
|
||||
Avant d'exécuter une commande ayant potentiellement des effets de bord,
|
||||
vérifier qu'elle ne peut pas modifier une ressource de production.
|
||||
|
||||
En cas de doute, demander confirmation.
|
||||
|
||||
---
|
||||
|
||||
## 6. Modifications du code
|
||||
|
||||
Avant une modification importante :
|
||||
|
||||
1. comprendre le code existant ;
|
||||
2. rechercher les dépendances et les appels concernés ;
|
||||
3. conserver le comportement existant qui n'est pas concerné par la demande ;
|
||||
4. privilégier une modification minimale plutôt qu'une réécriture inutile.
|
||||
|
||||
Après modification :
|
||||
|
||||
1. vérifier les fichiers modifiés ;
|
||||
2. rechercher les éventuelles références impactées ;
|
||||
3. examiner le diff ;
|
||||
4. signaler clairement les modifications réalisées ;
|
||||
5. signaler les points qui n'ont pas pu être testés.
|
||||
|
||||
Ne jamais considérer qu'une modification est déployée en production.
|
||||
|
||||
---
|
||||
|
||||
## 7. Tests
|
||||
|
||||
Les tests locaux sans effet de bord sont autorisés.
|
||||
|
||||
Ne jamais utiliser une ressource de production pour tester une opération
|
||||
d'écriture.
|
||||
|
||||
Si une fonctionnalité nécessite normalement une écriture en base de données,
|
||||
modifier le code si demandé mais NE PAS effectuer l'écriture réelle.
|
||||
|
||||
Indiquer à l'utilisateur que cette partie doit être testée manuellement dans
|
||||
un environnement approprié.
|
||||
|
||||
---
|
||||
|
||||
## 8. Accès Internet
|
||||
|
||||
Ne pas envoyer vers un service externe :
|
||||
|
||||
- code source propriétaire ;
|
||||
- credentials ;
|
||||
- noms d'utilisateurs ;
|
||||
- données provenant des bases ;
|
||||
- informations internes à l'entreprise.
|
||||
|
||||
Si une recherche Internet est nécessaire, demander l'autorisation lorsque
|
||||
Codex le requiert et limiter la requête aux informations strictement
|
||||
nécessaires.
|
||||
|
||||
---
|
||||
|
||||
## Principe général
|
||||
|
||||
Codex dispose d'une autonomie importante sur le CODE contenu dans ce
|
||||
workspace.
|
||||
|
||||
Codex ne dispose d'aucune autonomie pour modifier les DONNÉES ou
|
||||
l'INFRASTRUCTURE de production.
|
||||
|
||||
Si une action peut avoir un effet de bord sur une ressource extérieure au
|
||||
workspace, demander confirmation avant de l'exécuter.
|
||||
@@ -68,10 +68,12 @@
|
||||
$UTDBES = end($versions);
|
||||
$UTDFI = (Invoke_Infra("select max(Ver) as utd from X_cmdb_Product where Name = 'fi'"))[0]['utd'];
|
||||
$UTDxs = (Invoke_Infra("select max(Ver) as utd from X_cmdb_Product where Name = 'xs'"))[0]['utd'];
|
||||
$UTDveeam = (Invoke_Infra("select max(Ver) as utd from X_cmdb_Product where Name = 'veeam'"))[0]['utd'];
|
||||
$UTDnetbackup = (Invoke_Infra("select max(Ver) as utd from X_cmdb_Product where Name = 'netbackup'"))[0]['utd'];
|
||||
?>
|
||||
|
||||
|
||||
<div class="container-fluid" id="content">
|
||||
<div class="container-fluid" id="content" style="zoom:80%">
|
||||
<div class="row flex-nowrap">
|
||||
<div class="col-auto col-md-2 col-xl-2 px-sm-2 px-0 bg-dark vh-100 position-sticky top-0" style="-ms-flex: 0 0 230px;flex: 0 0 230px;">
|
||||
<?php include $_SERVER['DOCUMENT_ROOT'] . "/navbar.html"; ?>
|
||||
@@ -111,6 +113,8 @@
|
||||
<th data-field="BES" data-sortable="true" data-visible="false">BES</th>
|
||||
<th data-field="FI" data-sortable="true" data-visible="false">Fusion Inv.</th>
|
||||
<th data-field="xs" data-sortable="true" data-visible="false">XenSam</th>
|
||||
<th data-field="veeam" data-sortable="true" data-visible="true">Veeam</th>
|
||||
<th data-field="netbackup" data-sortable="true" data-visible="true">Netbackup</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@@ -342,7 +346,46 @@
|
||||
} else {
|
||||
echo "<td></td>";
|
||||
}
|
||||
//Veeam
|
||||
$s1s = $s['VeeamState'];
|
||||
$s1v = $s['VeeamVersion'];
|
||||
if ($s1s == "Y") {
|
||||
if ($s1v == $UTDveeam) {
|
||||
echo "<td class='table-success'>$s1v $s1s</td>";
|
||||
} else {
|
||||
echo "<td class='table-warning'><b>$s1v</b> $s1s</td>";
|
||||
}
|
||||
}elseif($s1s == "E"){
|
||||
echo "<td class='table-secondary text-center'>Excluded</td>";
|
||||
}elseif($s1s == "N"){
|
||||
echo "<td class='table-primary text-center'>Nope :)</td>";
|
||||
} else {
|
||||
if ($s1v == "") {
|
||||
echo "<td class='table-danger text-center'>MISSING</td>";
|
||||
} else {
|
||||
echo "<td class='table-danger'>$s1v <b>$s1s</b></td>";
|
||||
}
|
||||
}
|
||||
|
||||
//Netbackup
|
||||
$s1s = $s['nbState'];
|
||||
$s1v = $s['nbVersion'];
|
||||
if ($s1s == "Y") {
|
||||
if ($s1v == $UTDnetbackup) {
|
||||
echo "<td class='table-success'>$s1v $s1s</td>";
|
||||
} else {
|
||||
echo "<td class='table-warning'><b>$s1v</b> $s1s</td>";
|
||||
}
|
||||
}elseif($s1s == "E"){
|
||||
echo "<td class='table-secondary text-center'>Excluded</td>";
|
||||
}elseif($s1s == "N"){
|
||||
} else {
|
||||
if ($s1v == "") {
|
||||
echo "<td class='table-danger text-center'>MISSING</td>";
|
||||
} else {
|
||||
echo "<td class='table-danger'>$s1v <b>$s1s</b></td>";
|
||||
}
|
||||
}
|
||||
echo "</tr>";
|
||||
}
|
||||
|
||||
|
||||
67
modele.php
Normal file
67
modele.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* Squelette de page : dupliquer ce fichier et adapter le titre et le contenu.
|
||||
* Les chemins depuis la racine restent valides dans les sous-dossiers du site.
|
||||
*/
|
||||
|
||||
// Initialiser la session, la langue et les fonctions communes avant tout affichage.
|
||||
// all.php produit aussi du HTML : le conserver pour l'insérer dans le body.
|
||||
ob_start();
|
||||
require $_SERVER['DOCUMENT_ROOT'] . '/include/all.php';
|
||||
$commonMarkup = ob_get_clean();
|
||||
|
||||
// À personnaliser pour chaque nouvelle page.
|
||||
$pageTitle = ($lang === 'fr') ? 'Titre de la page' : 'Page title';
|
||||
$escapedPageTitle = htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
// Ajouter ici les traitements PHP propres à la page.
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= htmlspecialchars($lang, ENT_QUOTES, 'UTF-8') ?>">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= $escapedPageTitle ?> - Web Infra Reports IT</title>
|
||||
<link rel="shortcut icon" type="image/png" href="/include/favicon-32x32.png">
|
||||
|
||||
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/css/bootstrap-icons-1.13.1/bootstrap-icons.css">
|
||||
<!-- jQuery est nécessaire à la recherche de serveurs du menu commun. -->
|
||||
<script src="/js/jquery-3.6.1.min.js"></script>
|
||||
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Ajouter ici les styles et dépendances propres à la page. -->
|
||||
<style>
|
||||
.page-sidebar {
|
||||
flex: 0 0 230px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light text-dark">
|
||||
<?= $commonMarkup ?>
|
||||
<div class="container-fluid">
|
||||
<div class="row flex-nowrap">
|
||||
<div class="col-auto col-md-2 col-xl-2 px-sm-2 px-0 bg-dark vh-100 position-sticky top-0 page-sidebar">
|
||||
<?php require $_SERVER['DOCUMENT_ROOT'] . '/navbar.html'; ?>
|
||||
</div>
|
||||
|
||||
<main class="col py-3 page-content" aria-labelledby="page-title">
|
||||
<h1 id="page-title"><span class="badge text-bg-secondary w-100"><?= $escapedPageTitle ?></span></h1>
|
||||
|
||||
<div class="mt-4">
|
||||
<!-- Ajouter ici le contenu de la page. -->
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Charger après le menu pour initialiser le bouton clair/sombre. -->
|
||||
<script src="/js/switch.js"></script>
|
||||
<!-- Ajouter ici les scripts propres à la page. -->
|
||||
</body>
|
||||
</html>
|
||||
278
modele_full.php
Normal file
278
modele_full.php
Normal file
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
/**
|
||||
* Catalogue de composants à copier dans les prochaines pages.
|
||||
* Sources : modele.php, index.php, AD/LocalAdmins.php,
|
||||
* Network/dhcp_servers.php, Hyper-V/Clusters.php et reports/Backups.php.
|
||||
* Toutes les données ci-dessous sont fictives. Aucun appel à une base.
|
||||
*/
|
||||
ob_start();
|
||||
require $_SERVER['DOCUMENT_ROOT'] . '/include/all.php';
|
||||
$commonMarkup = ob_get_clean();
|
||||
$pageTitle = 'Modèles de composants';
|
||||
$escape = static function ($value) {
|
||||
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
||||
};
|
||||
|
||||
// Remplacer ce jeu de démonstration par les données de votre future page.
|
||||
$demoRows = [
|
||||
['DEMO-SRV-01', 'Windows', 'Disponible', 'success', 24],
|
||||
['DEMO-SRV-02', 'Linux', 'Disponible', 'success', 42],
|
||||
['DEMO-SRV-03', 'Windows', 'À vérifier', 'warning', 76],
|
||||
['DEMO-SRV-04', 'Linux', 'Indisponible', 'danger', 0],
|
||||
['DEMO-SRV-05', 'Windows', 'Disponible', 'success', 31],
|
||||
['DEMO-SRV-06', 'Linux', 'Disponible', 'success', 58],
|
||||
];
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= $escape($lang) ?>">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= $escape($pageTitle) ?> - Web Infra Reports IT</title>
|
||||
<link rel="shortcut icon" type="image/png" href="/include/favicon-32x32.png">
|
||||
<link rel="stylesheet" href="/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/css/bootstrap-icons-1.13.1/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="/css/bootstrap-table.min.css">
|
||||
<script src="/js/jquery-3.6.1.min.js"></script>
|
||||
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||
<!-- Dépendances du tableau avec recherche, tri, pagination et export. -->
|
||||
<script src="/js/bootstrap-table.min.js"></script>
|
||||
<script src="/js/bootstrap-table-fr-FR.min.js"></script>
|
||||
<script src="/js/tableExport.min.js"></script>
|
||||
<script src="/js/bootstrap-table-export.min.js"></script>
|
||||
<script src="/js/chart.min.js"></script>
|
||||
<style>
|
||||
.page-sidebar { flex: 0 0 230px; overflow-y: auto; }
|
||||
.page-content { min-width: 0; }
|
||||
.demo-section { scroll-margin-top: 1rem; }
|
||||
.demo-kpi { border-radius: 14px; box-shadow: 0 4px 14px rgba(15, 23, 42, .06); }
|
||||
.demo-icon { width: 48px; height: 48px; display: grid; place-items: center; border-radius: 14px; font-size: 1.5rem; }
|
||||
.demo-chart { position: relative; height: 240px; }
|
||||
.page-content .card, .demo-modal .modal-content { color: inherit; }
|
||||
body.bg-dark .page-content .card,
|
||||
body.bg-dark .demo-modal .modal-content,
|
||||
body.bg-dark .page-content .list-group-item { background-color: #2b3035; color: #f8f9fa; border-color: #495057; }
|
||||
body.bg-dark .page-content .text-muted { color: #adb5bd !important; }
|
||||
body.bg-dark .page-content .form-control,
|
||||
body.bg-dark .page-content .form-select,
|
||||
body.bg-dark .page-content .page-link,
|
||||
body.bg-dark .page-content .dropdown-menu { background-color: #212529; color: #f8f9fa; border-color: #495057; }
|
||||
body.bg-dark .page-content .dropdown-item { color: #f8f9fa; }
|
||||
body.bg-dark .page-content .dropdown-item:hover { background-color: #495057; }
|
||||
body.bg-dark .page-content .nav-tabs .nav-link.active { background-color: #2b3035; color: #f8f9fa; border-color: #495057; }
|
||||
.page-content .accordion-item, .page-content .accordion-button { background-color: transparent; color: inherit; }
|
||||
.page-content .accordion-item { border-color: rgba(128, 128, 128, .35); }
|
||||
.page-content .accordion-button:not(.collapsed) { background-color: rgba(128, 128, 128, .12); }
|
||||
body.bg-dark .page-content .accordion-button::after,
|
||||
body.bg-dark .demo-modal .btn-close { filter: invert(1) grayscale(1); }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-light text-dark">
|
||||
<?= $commonMarkup ?>
|
||||
<div class="container-fluid">
|
||||
<div class="row flex-nowrap">
|
||||
<div class="col-auto col-md-2 col-xl-2 px-sm-2 px-0 bg-dark vh-100 position-sticky top-0 page-sidebar">
|
||||
<?php require $_SERVER['DOCUMENT_ROOT'] . '/navbar.html'; ?>
|
||||
</div>
|
||||
<main class="col py-3 page-content" aria-labelledby="page-title">
|
||||
<h1 id="page-title"><span class="badge text-bg-secondary w-100 text-wrap"><?= $escape($pageTitle) ?></span></h1>
|
||||
<p class="text-muted">Exemples réutilisables — données fictives et actions de démonstration.</p>
|
||||
<nav class="d-flex flex-wrap gap-2 mb-4" aria-label="Composants de la page">
|
||||
<a class="btn btn-sm btn-outline-primary" href="#synthese">Synthèse</a>
|
||||
<a class="btn btn-sm btn-outline-primary" href="#tableau">Tableau</a>
|
||||
<a class="btn btn-sm btn-outline-primary" href="#boutons">Boutons</a>
|
||||
<a class="btn btn-sm btn-outline-primary" href="#formulaire">Formulaire</a>
|
||||
<a class="btn btn-sm btn-outline-primary" href="#indicateurs">Indicateurs</a>
|
||||
<a class="btn btn-sm btn-outline-primary" href="#organisation">Onglets et accordéon</a>
|
||||
</nav>
|
||||
|
||||
<!-- 1. CARTES DE SYNTHÈSE : reprendre une colonne pour chaque indicateur. -->
|
||||
<section id="synthese" class="demo-section mb-4" aria-labelledby="synthese-title">
|
||||
<h2 id="synthese-title" class="h4">Cartes de synthèse</h2>
|
||||
<div class="row g-3">
|
||||
<?php foreach ([['Total', 6, 'primary', 'hdd-rack'], ['Disponibles', 4, 'success', 'check-circle'], ['À vérifier', 1, 'warning', 'exclamation-triangle'], ['Indisponibles', 1, 'danger', 'x-circle']] as $kpi): ?>
|
||||
<div class="col-sm-6 col-xl-3">
|
||||
<div class="card demo-kpi h-100"><div class="card-body d-flex align-items-center gap-3">
|
||||
<span class="demo-icon text-bg-<?= $escape($kpi[2]) ?>"><i class="bi bi-<?= $escape($kpi[3]) ?>" aria-hidden="true"></i></span>
|
||||
<div><div class="text-muted"><?= $escape($kpi[0]) ?></div><div class="fs-2 fw-bold"><?= $escape($kpi[1]) ?></div></div>
|
||||
</div></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 2. TABLEAU : identifiant unique requis pour chaque bootstrap-table. -->
|
||||
<section id="tableau" class="card demo-section mb-4" aria-labelledby="tableau-title">
|
||||
<div class="card-header"><h2 id="tableau-title" class="h5 mb-0"><i class="bi bi-table me-2" aria-hidden="true"></i>Tableau avec recherche, tri et export</h2></div>
|
||||
<div class="card-body">
|
||||
<table id="demo-table" class="table table-bordered table-hover table-sm"
|
||||
data-toggle="table" data-locale="<?= $lang === 'fr' ? 'fr-FR' : 'en-US' ?>"
|
||||
data-search="true" data-show-columns="true" data-pagination="true"
|
||||
data-page-size="5" data-page-list="[5, 10, 25]" data-show-export="true"
|
||||
data-export-types='["csv", "json"]' data-export-data-type="all">
|
||||
<caption>Inventaire fictif : six serveurs de démonstration.</caption>
|
||||
<thead><tr>
|
||||
<th scope="col" data-field="server" data-sortable="true">Serveur</th>
|
||||
<th scope="col" data-field="os" data-sortable="true">Système</th>
|
||||
<th scope="col" data-field="status" data-sortable="true">État</th>
|
||||
<th scope="col" data-field="usage" data-sortable="true">Utilisation (%)</th>
|
||||
<th scope="col" data-field="actions" data-force-hide="true">Action</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($demoRows as $row): ?>
|
||||
<tr>
|
||||
<td class="fw-bold"><?= $escape($row[0]) ?></td>
|
||||
<td><?= $escape($row[1]) ?></td>
|
||||
<td><span class="badge text-bg-<?= $escape($row[3]) ?>"><?= $escape($row[2]) ?></span></td>
|
||||
<td><?= $escape($row[4]) ?></td>
|
||||
<td><button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#demo-modal" data-demo-server="<?= $escape($row[0]) ?>"><i class="bi bi-eye me-1" aria-hidden="true"></i>Détails</button></td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 3. BOUTONS : les actions de cet exemple affichent seulement un message. -->
|
||||
<section id="boutons" class="card demo-section mb-4" aria-labelledby="boutons-title">
|
||||
<div class="card-header"><h2 id="boutons-title" class="h5 mb-0">Boutons et fenêtre modale</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap gap-2 mb-3">
|
||||
<button type="button" class="btn btn-primary" data-demo-action="Action principale"><i class="bi bi-plus-circle me-1" aria-hidden="true"></i>Principal</button>
|
||||
<button type="button" class="btn btn-secondary" data-demo-action="Action secondaire">Secondaire</button>
|
||||
<button type="button" class="btn btn-success" data-demo-action="Validation"><i class="bi bi-check-lg me-1" aria-hidden="true"></i>Valider</button>
|
||||
<button type="button" class="btn btn-warning" data-demo-action="Vérification">Vérifier</button>
|
||||
<button type="button" class="btn btn-danger" data-demo-action="Action sensible simulée">Action sensible</button>
|
||||
<button type="button" class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#demo-modal">Ouvrir la modale</button>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap align-items-center gap-2">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-demo-action="Petit bouton">Petit bouton</button>
|
||||
<button type="button" class="btn btn-primary" disabled>Désactivé</button>
|
||||
<button type="button" class="btn btn-primary" disabled><span class="spinner-border spinner-border-sm me-1" aria-hidden="true"></span>Chargement…</button>
|
||||
<div class="dropdown">
|
||||
<button type="button" class="btn btn-outline-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Autres actions</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><button type="button" class="dropdown-item" data-demo-action="Première option">Première option</button></li>
|
||||
<li><button type="button" class="dropdown-item" data-demo-action="Seconde option">Seconde option</button></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p id="demo-action-result" class="mt-3 mb-0" role="status" aria-live="polite"></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 4. FORMULAIRE : validation navigateur et aperçu local, sans envoi. -->
|
||||
<section id="formulaire" class="card demo-section mb-4" aria-labelledby="formulaire-title">
|
||||
<div class="card-header"><h2 id="formulaire-title" class="h5 mb-0">Formulaire</h2></div>
|
||||
<div class="card-body">
|
||||
<form id="demo-form" onsubmit="return false;">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6"><label for="demo-name" class="form-label">Nom <span class="text-danger">*</span></label><input id="demo-name" class="form-control" required maxlength="80" placeholder="Exemple : DEMO-SRV-07"></div>
|
||||
<div class="col-md-6"><label for="demo-system" class="form-label">Système</label><select id="demo-system" class="form-select"><option>Windows</option><option>Linux</option><option>Autre</option></select></div>
|
||||
<div class="col-md-6"><label for="demo-date" class="form-label">Date</label><input type="date" id="demo-date" class="form-control"></div>
|
||||
<div class="col-md-6"><label for="demo-capacity" class="form-label">Capacité</label><div class="input-group"><input type="number" id="demo-capacity" class="form-control" min="0" value="100"><span class="input-group-text">Go</span></div></div>
|
||||
<div class="col-12"><label for="demo-comment" class="form-label">Commentaire</label><textarea id="demo-comment" class="form-control" rows="2" maxlength="500"></textarea></div>
|
||||
<div class="col-md-6"><div class="form-check"><input type="checkbox" class="form-check-input" id="demo-notify"><label class="form-check-label" for="demo-notify">Inclure dans le rapport</label></div></div>
|
||||
<div class="col-md-6"><div class="form-check form-switch"><input type="checkbox" class="form-check-input" id="demo-enabled" checked><label class="form-check-label" for="demo-enabled">Actif</label></div></div>
|
||||
<div class="col-12 d-flex gap-2"><button type="submit" class="btn btn-primary">Prévisualiser</button><button type="reset" class="btn btn-outline-secondary">Réinitialiser</button></div>
|
||||
</div>
|
||||
<p id="demo-form-result" class="mt-3 mb-0" role="status" aria-live="polite"></p>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 5. ALERTES, BADGES, PROGRESSION ET GRAPHIQUE. -->
|
||||
<section id="indicateurs" class="demo-section mb-4" aria-labelledby="indicateurs-title">
|
||||
<h2 id="indicateurs-title" class="h4">Alertes et indicateurs</h2>
|
||||
<div class="row g-3">
|
||||
<div class="col-xl-7"><div class="card h-100"><div class="card-body">
|
||||
<div class="alert alert-info"><i class="bi bi-info-circle me-2" aria-hidden="true"></i>Information : exemple de message contextuel.</div>
|
||||
<div class="alert alert-success">Succès : vérification terminée.</div>
|
||||
<div class="alert alert-warning">Attention : un élément nécessite une vérification.</div>
|
||||
<div class="alert alert-danger">Erreur : exemple de message d’échec.</div>
|
||||
<div class="d-flex flex-wrap gap-2 mb-3"><span class="badge text-bg-success">Conforme</span><span class="badge text-bg-warning">À vérifier</span><span class="badge text-bg-danger">Erreur</span><span class="badge text-bg-secondary">Inconnu</span></div>
|
||||
<div id="demo-progress-label" class="mb-1">Capacité utilisée : 65 %</div>
|
||||
<div class="progress"><div class="progress-bar progress-bar-striped" role="progressbar" style="width:65%" aria-valuenow="65" aria-valuemin="0" aria-valuemax="100" aria-labelledby="demo-progress-label">65 %</div></div>
|
||||
</div></div></div>
|
||||
<div class="col-xl-5"><div class="card h-100"><div class="card-header"><h3 class="h5 mb-0">Répartition des serveurs</h3></div><div class="card-body">
|
||||
<div class="demo-chart"><canvas id="demo-chart" role="img" aria-label="4 serveurs disponibles, 1 à vérifier et 1 indisponible">4 disponibles, 1 à vérifier, 1 indisponible.</canvas></div>
|
||||
<ul class="list-unstyled mt-3 mb-0"><li><span class="badge text-bg-success me-2">4</span>Disponibles</li><li><span class="badge text-bg-warning me-2">1</span>À vérifier</li><li><span class="badge text-bg-danger me-2">1</span>Indisponibles</li></ul>
|
||||
</div></div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 6. ONGLETS ET ACCORDÉON : adapter ensemble les id et data-bs-target. -->
|
||||
<section id="organisation" class="card demo-section mb-4" aria-labelledby="organisation-title">
|
||||
<div class="card-header"><h2 id="organisation-title" class="h5 mb-0">Onglets et accordéon</h2></div>
|
||||
<div class="card-body">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li class="nav-item" role="presentation"><button id="demo-summary-tab" class="nav-link active" type="button" role="tab" data-bs-toggle="tab" data-bs-target="#demo-summary-panel" aria-controls="demo-summary-panel" aria-selected="true">Résumé</button></li>
|
||||
<li class="nav-item" role="presentation"><button id="demo-details-tab" class="nav-link" type="button" role="tab" data-bs-toggle="tab" data-bs-target="#demo-details-panel" aria-controls="demo-details-panel" aria-selected="false">Détails</button></li>
|
||||
</ul>
|
||||
<div class="tab-content pt-3">
|
||||
<div id="demo-summary-panel" class="tab-pane fade show active" role="tabpanel" aria-labelledby="demo-summary-tab" tabindex="0"><p>Zone de synthèse : placer ici les informations principales.</p></div>
|
||||
<div id="demo-details-panel" class="tab-pane fade" role="tabpanel" aria-labelledby="demo-details-tab" tabindex="0"><dl class="row"><dt class="col-sm-3">Environnement</dt><dd class="col-sm-9">Démonstration</dd><dt class="col-sm-3">Source</dt><dd class="col-sm-9">Données fictives intégrées à la page</dd></dl></div>
|
||||
</div>
|
||||
<div class="accordion" id="demo-accordion">
|
||||
<div class="accordion-item">
|
||||
<h3 class="accordion-header" id="demo-accordion-title"><button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#demo-accordion-panel" aria-expanded="false" aria-controls="demo-accordion-panel">Informations complémentaires</button></h3>
|
||||
<div id="demo-accordion-panel" class="accordion-collapse collapse" aria-labelledby="demo-accordion-title" data-bs-parent="#demo-accordion"><div class="accordion-body">Ce bloc peut accueillir un tableau secondaire, une liste ou un détail technique.</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MODALE : placée hors de la grille pour éviter les contraintes de positionnement. -->
|
||||
<div class="modal fade demo-modal" id="demo-modal" tabindex="-1" aria-labelledby="demo-modal-title" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable"><div class="modal-content">
|
||||
<div class="modal-header"><h2 class="modal-title fs-5" id="demo-modal-title">Détail de démonstration</h2><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Fermer"></button></div>
|
||||
<div class="modal-body"><p id="demo-modal-description" class="mb-0"></p></div>
|
||||
<div class="modal-footer"><button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Fermer</button></div>
|
||||
</div></div>
|
||||
</div>
|
||||
<script src="/js/switch.js"></script>
|
||||
<script>
|
||||
// Interactions de démonstration uniquement : aucun appel réseau ni enregistrement.
|
||||
document.querySelectorAll('[data-demo-action]').forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
document.getElementById('demo-action-result').textContent = button.dataset.demoAction + ' : démonstration exécutée.';
|
||||
});
|
||||
});
|
||||
document.getElementById('demo-modal').addEventListener('show.bs.modal', function (event) {
|
||||
var server = event.relatedTarget ? event.relatedTarget.getAttribute('data-demo-server') : null;
|
||||
document.getElementById('demo-modal-title').textContent = server || 'Détail de démonstration';
|
||||
document.getElementById('demo-modal-description').textContent = server
|
||||
? 'Informations fictives pour ' + server + '. Ajouter ici le contenu détaillé.'
|
||||
: 'Exemple de fenêtre modale avec un titre, un contenu et un bouton de fermeture.';
|
||||
});
|
||||
document.getElementById('demo-form').addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
document.getElementById('demo-form-result').textContent = 'Aperçu : '
|
||||
+ document.getElementById('demo-name').value + ' — '
|
||||
+ document.getElementById('demo-system').value + ' — '
|
||||
+ document.getElementById('demo-capacity').value + ' Go — '
|
||||
+ (document.getElementById('demo-enabled').checked ? 'Actif' : 'Inactif')
|
||||
+ '. Aucune donnée enregistrée.';
|
||||
});
|
||||
document.getElementById('demo-form').addEventListener('reset', function () {
|
||||
document.getElementById('demo-form-result').textContent = '';
|
||||
});
|
||||
// API Chart.js 2.x, correspondant au fichier local js/chart.min.js.
|
||||
new Chart(document.getElementById('demo-chart').getContext('2d'), {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['Disponibles', 'À vérifier', 'Indisponibles'],
|
||||
datasets: [{ data: [4, 1, 1], backgroundColor: ['#198754', '#ffc107', '#dc3545'], borderWidth: 0 }]
|
||||
},
|
||||
options: { responsive: true, maintainAspectRatio: false, legend: { display: false } }
|
||||
});
|
||||
// Conserver le thème du tableau après une recherche, un tri ou une pagination.
|
||||
$('#demo-table').on('post-body.bs.table', function () {
|
||||
$(this).toggleClass('table-dark', document.body.classList.contains('bg-dark'));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
10
navbar.html
10
navbar.html
@@ -60,15 +60,6 @@
|
||||
<a href="/crossover/GlobalCrossover.php" class="nav-link px-0 align-middle">
|
||||
<i class="fs-6 bi-table text-white"></i> <span class="ms-1 d-none d-sm-inline text-white h7"><?php echo $nav_2; ?></span></a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#AD" data-bs-toggle="collapse" class="nav-link px-0 align-middle">
|
||||
<i class="fs-6 bi bi-shield-lock me-1 text-white"></i> <span class="ms-1 d-none d-sm-inline text-white h7">AD</span><i class="bi bi-caret-down"></i> </a>
|
||||
<ul class="collapse nav flex-column ms-1" id="AD" data-bs-parent="#menu">
|
||||
<li class="w-100">
|
||||
<a href="/AD/LocalAdmins.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- Local Admins</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#SharedFolders" data-bs-toggle="collapse" class="nav-link px-0 align-middle">
|
||||
<i class="fs-6 bi bi-folder-symlink-fill text-white"></i> <span class="ms-1 d-none d-sm-inline text-white h7"><?php echo $nav_4; ?></span><i class="bi bi-caret-down"></i> </a>
|
||||
@@ -157,6 +148,7 @@
|
||||
<li class="w-100">
|
||||
<a href="/AD/GroupReview.php" class="nav-link px-0" > <span class="d-none d-sm-inline text-white h7">- <?php echo $nav_1_1; ?></span></a>
|
||||
<a href="/AD/GroupSearch.php" class="nav-link px-0" > <span class="d-none d-sm-inline text-white h7">- <?php echo $nav_1_2; ?></span></a>
|
||||
<a href="/AD/LocalAdmins.php" class="nav-link px-0"> <span class="d-none d-sm-inline text-white h7">- Local Admins</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<?php ob_start(); ?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
@@ -73,13 +74,22 @@
|
||||
|
||||
<?php
|
||||
/* ============================================================================
|
||||
Unified backup reporting : Veeam (target) + NetBackup (legacy)
|
||||
During the migration a VM can still exist in both SQL tables.
|
||||
Unified backup reporting : Veeam + NetBackupJobs (dun-bkp-01)
|
||||
During the migration a VM can still exist in both backup sources.
|
||||
It is displayed only on the side holding the most recent backup.
|
||||
========================================================================== */
|
||||
|
||||
/* ---------------------------------------------------------------- helpers */
|
||||
|
||||
// NetBackup can store a client as FQDN while CMDB/Veeam often use a short name.
|
||||
// Use the short hostname as the common comparison key.
|
||||
function normalize_host($name) {
|
||||
$name = strtoupper(trim((string) $name));
|
||||
if ($name === '') return '';
|
||||
$parts = explode('.', $name, 2);
|
||||
return $parts[0];
|
||||
}
|
||||
|
||||
// Whole days between a date and today. Returns null when the date is unusable.
|
||||
function days_since($dateStr) {
|
||||
if (empty($dateStr)) return null;
|
||||
@@ -110,14 +120,14 @@ function fmt_lastgood($dateStr) {
|
||||
$activeRows = Invoke_Infra("SELECT name FROM cmdb_vms
|
||||
WHERE CAST(lastinventory AS DATETIME) > DATEADD(DAY, -2, GETDATE())");
|
||||
$active = [];
|
||||
foreach ($activeRows as $r) { $active[strtoupper(trim($r['name']))] = true; }
|
||||
foreach ($activeRows as $r) { $active[normalize_host($r['name'])] = true; }
|
||||
|
||||
/* -------------------------------------------------------------- 1. Veeam */
|
||||
|
||||
$veeam = [];
|
||||
$rows = Invoke_Infra("SELECT * FROM Veeam_Reporting");
|
||||
foreach ($rows as $row) {
|
||||
$key = strtoupper(trim($row['ClientName']));
|
||||
$key = normalize_host($row['ClientName']);
|
||||
if (!isset($active[$key])) continue;
|
||||
|
||||
$ageGood = days_since($row['LastSuccess']);
|
||||
@@ -149,55 +159,187 @@ foreach ($rows as $row) {
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------- 2. NetBackup */
|
||||
/*
|
||||
Source principale : dbo.NetBackupJobs
|
||||
Master pris en compte : dun-bkp-01 uniquement.
|
||||
|
||||
Pour chaque client NetBackup :
|
||||
- le dernier FULL/INCREMENTAL donne l'état courant ;
|
||||
- le dernier FULL/INCREMENTAL avec status <= 1 donne "Last Good Backup" ;
|
||||
- dbo.NBRC fournit la description du code retour.
|
||||
|
||||
La page reste une synthèse VM : seuls les clients présents dans cmdb_vms
|
||||
avec un inventaire récent sont retenus.
|
||||
*/
|
||||
|
||||
$netbackup = [];
|
||||
$rows = Invoke_Infra("SELECT * FROM VMs_Backup
|
||||
WHERE (Owner LIKE 'DUN-VMH%' OR Owner LIKE 'MDK-VMH%')
|
||||
AND name NOT LIKE 'WS%'
|
||||
AND owner NOT LIKE '%WKG%'
|
||||
AND owner NOT LIKE '%VMH-WM%'
|
||||
ORDER BY name");
|
||||
|
||||
$rows = Invoke_Infra("
|
||||
WITH ScheduledJobs AS
|
||||
(
|
||||
SELECT
|
||||
j.master,
|
||||
j.job,
|
||||
j.client,
|
||||
j.policy,
|
||||
j.schedule_type,
|
||||
j.status,
|
||||
j.start_date,
|
||||
j.end_date,
|
||||
j.size_kb,
|
||||
|
||||
COALESCE(j.end_date, j.start_date) AS run_date,
|
||||
|
||||
MAX(
|
||||
CASE
|
||||
WHEN j.status <= 1
|
||||
THEN COALESCE(j.end_date, j.start_date)
|
||||
ELSE NULL
|
||||
END
|
||||
) OVER (
|
||||
PARTITION BY j.client
|
||||
) AS last_success,
|
||||
|
||||
ROW_NUMBER() OVER
|
||||
(
|
||||
PARTITION BY j.client
|
||||
ORDER BY
|
||||
COALESCE(j.end_date, j.start_date) DESC,
|
||||
j.job DESC
|
||||
) AS rn
|
||||
|
||||
FROM dbo.NetBackupJobs j
|
||||
|
||||
WHERE
|
||||
j.master like 'dun-bkp-01%'
|
||||
AND j.schedule_type IN ('full', 'incremental')
|
||||
),
|
||||
Latest AS
|
||||
(
|
||||
SELECT *
|
||||
FROM ScheduledJobs
|
||||
WHERE rn = 1
|
||||
)
|
||||
SELECT
|
||||
l.master,
|
||||
l.job,
|
||||
l.client,
|
||||
l.policy,
|
||||
l.schedule_type,
|
||||
l.status,
|
||||
l.start_date,
|
||||
l.end_date,
|
||||
l.run_date,
|
||||
l.last_success,
|
||||
l.size_kb,
|
||||
r.RC_DESC AS status_desc
|
||||
|
||||
FROM Latest l
|
||||
|
||||
LEFT JOIN dbo.NBRC r
|
||||
ON r.RC = l.status
|
||||
|
||||
ORDER BY l.client
|
||||
");
|
||||
|
||||
if (!is_array($rows)) $rows = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$key = strtoupper(trim($row['Name']));
|
||||
$excluded = trim((string) $row['Exclusion']) !== '';
|
||||
|
||||
// Excluded VMs are listed even when no longer inventoried, like on the old page.
|
||||
if (!$excluded && !isset($active[$key])) continue;
|
||||
$key = normalize_host($row['client']);
|
||||
|
||||
$result = trim((string) $row['LastResult']);
|
||||
$ageGood = days_since($row['LastKnownGood']);
|
||||
// This synthesis page is VM-oriented.
|
||||
if ($key === '' || !isset($active[$key])) continue;
|
||||
|
||||
if ($excluded) {
|
||||
$cat = 'excluded'; $cls = 'table-secondary';
|
||||
} elseif ($result !== 'OK') {
|
||||
// A failed job with a still fresh good copy is only a warning.
|
||||
$statusCode = (int) $row['status'];
|
||||
$ageGood = days_since($row['last_success']);
|
||||
|
||||
if ($statusCode > 1) {
|
||||
// Same policy as before:
|
||||
// a failed latest run remains only a warning while a recent good copy exists.
|
||||
if ($ageGood === null || $ageGood >= backup_tolerance_days()) {
|
||||
$cat = 'error'; $cls = 'table-danger';
|
||||
$cat = 'error';
|
||||
$cls = 'table-danger';
|
||||
} else {
|
||||
$cat = 'warning'; $cls = 'table-warning';
|
||||
$cat = 'warning';
|
||||
$cls = 'table-warning';
|
||||
}
|
||||
} elseif ($ageGood === null || $ageGood >= backup_tolerance_days()) {
|
||||
$cat = 'outdated'; $cls = 'table-info';
|
||||
$cat = 'outdated';
|
||||
$cls = 'table-info';
|
||||
} else {
|
||||
$cat = 'ok'; $cls = 'table-success';
|
||||
$cat = 'ok';
|
||||
$cls = 'table-success';
|
||||
}
|
||||
|
||||
$statusDesc = trim((string)($row['status_desc'] ?? ''));
|
||||
|
||||
if ($statusCode <= 1) {
|
||||
$statusText = 'OK';
|
||||
} else {
|
||||
$statusText = 'Error ' . $statusCode;
|
||||
if ($statusDesc !== '') {
|
||||
$statusText .= ' - ' . $statusDesc;
|
||||
}
|
||||
}
|
||||
|
||||
$runDate = $row['run_date'] ?? $row['start_date'] ?? null;
|
||||
|
||||
$netbackup[$key] = [
|
||||
'cat' => $cat,
|
||||
'row_class' => $cls,
|
||||
'excluded' => $excluded,
|
||||
'ts' => strtotime(trim($row['LastBackup'] . ' ' . $row['TimeStamp'])) ?: 0,
|
||||
'vm' => $row['Name'],
|
||||
'last_run' => $excluded ? 'Tag NoBackup' : fmt_datetime($row['LastBackup'] . ' ' . $row['TimeStamp']),
|
||||
'status' => $excluded ? 'Tag NoBackup' : ($result !== '' ? $result : 'Unknown'),
|
||||
'last_good' => $excluded ? 'Tag NoBackup' : fmt_lastgood($row['LastKnownGood']),
|
||||
'size' => $excluded ? '' : $row['LastSize'],
|
||||
'extra' => $row['Owner'],
|
||||
'policy' => $row['Policy'],
|
||||
'message' => '',
|
||||
'excluded' => false,
|
||||
'ts' => strtotime((string)$runDate) ?: 0,
|
||||
'vm' => $row['client'],
|
||||
'last_run' => fmt_datetime($runDate),
|
||||
'status' => $statusText,
|
||||
'last_good' => fmt_lastgood($row['last_success']),
|
||||
'size' => ($row['size_kb'] === null || $row['size_kb'] === '')
|
||||
? ''
|
||||
: number_format(((float)$row['size_kb']) / 1048576, 1, '.', ''),
|
||||
'extra' => $row['schedule_type'],
|
||||
'policy' => $row['policy'],
|
||||
'message' => ($statusCode > 1) ? $statusDesc : '',
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
Keep only the explicit NoBackup exclusions from the old VMs_Backup table.
|
||||
NetBackupJobs cannot represent a deliberate exclusion because no backup job
|
||||
exists for an intentionally excluded VM.
|
||||
*/
|
||||
$excludedRows = Invoke_Infra("
|
||||
SELECT name, Exclusion, Owner, Policy
|
||||
FROM VMs_Backup
|
||||
WHERE Exclusion IS NOT NULL
|
||||
AND LTRIM(RTRIM(Exclusion)) <> ''
|
||||
");
|
||||
|
||||
if (is_array($excludedRows)) {
|
||||
foreach ($excludedRows as $row) {
|
||||
|
||||
$key = normalize_host($row['name']);
|
||||
|
||||
// Do not overwrite a VM that has actual dun-bkp-01 job history.
|
||||
if ($key === '' || isset($netbackup[$key])) continue;
|
||||
|
||||
$netbackup[$key] = [
|
||||
'cat' => 'excluded',
|
||||
'row_class' => 'table-secondary',
|
||||
'excluded' => true,
|
||||
'ts' => 0,
|
||||
'vm' => $row['name'],
|
||||
'last_run' => 'Tag NoBackup',
|
||||
'status' => 'Tag NoBackup',
|
||||
'last_good' => 'Tag NoBackup',
|
||||
'size' => '',
|
||||
'extra' => '',
|
||||
'policy' => $row['Policy'],
|
||||
'message' => trim((string)$row['Exclusion']),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------- 3. Migration de-duplication */
|
||||
// A VM present on both sides is kept where the most recent backup lives.
|
||||
|
||||
@@ -345,7 +487,7 @@ function render_block($blockId, $meta, $rows, $extraLabel, $expanded = false) {
|
||||
<div class="accordion mb-4" id="acc_nb">
|
||||
<?php
|
||||
foreach (['error', 'warning', 'outdated', 'ok', 'excluded'] as $cat) {
|
||||
render_block('nb_' . $cat, $blockMeta[$cat], $buckets['nb'][$cat], $GLOBALS['w_host'], false);
|
||||
render_block('nb_' . $cat, $blockMeta[$cat], $buckets['nb'][$cat], 'Schedule', false);
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
@@ -408,3 +550,4 @@ function render_block($blockId, $meta, $rows, $extraLabel, $expanded = false) {
|
||||
|
||||
</body>
|
||||
</html>
|
||||
<?php ob_end_flush(); ?>
|
||||
|
||||
Reference in New Issue
Block a user