Add branding, license, and firmware table improvements
- Add mchus.pro branding to header and footer - Add author info and git repository link - Add proprietary license (Mike Chus) - Firmware table: replace count column with separate component/model columns - Add branding to TXT export reports Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -59,3 +59,6 @@ go.work.sum
|
|||||||
# env file
|
# env file
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
|
||||||
|
# Distribution binaries
|
||||||
|
dist/
|
||||||
|
|||||||
44
LICENSE
Normal file
44
LICENSE
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
Proprietary Software License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Mike Chus (mchus.pro)
|
||||||
|
All Rights Reserved.
|
||||||
|
|
||||||
|
This software and associated documentation files (the "Software") are the
|
||||||
|
exclusive property of Mike Chus (mchus.pro).
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS:
|
||||||
|
|
||||||
|
1. GRANT OF LICENSE
|
||||||
|
No license is granted to use, copy, modify, merge, publish, distribute,
|
||||||
|
sublicense, and/or sell copies of the Software without explicit written
|
||||||
|
permission from the copyright holder.
|
||||||
|
|
||||||
|
2. RESTRICTIONS
|
||||||
|
You may not:
|
||||||
|
- Copy, modify, or distribute the Software
|
||||||
|
- Reverse engineer, decompile, or disassemble the Software
|
||||||
|
- Remove or alter any proprietary notices or labels on the Software
|
||||||
|
- Use the Software for commercial purposes without authorization
|
||||||
|
- Sublicense, lease, or lend the Software to third parties
|
||||||
|
|
||||||
|
3. OWNERSHIP
|
||||||
|
The Software is protected by copyright laws and international treaty
|
||||||
|
provisions. The copyright holder retains all rights, title, and interest
|
||||||
|
in and to the Software, including all intellectual property rights.
|
||||||
|
|
||||||
|
4. TERMINATION
|
||||||
|
This license is effective until terminated. It will terminate automatically
|
||||||
|
without notice if you fail to comply with any provision of this license.
|
||||||
|
|
||||||
|
5. DISCLAIMER OF WARRANTIES
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
|
||||||
|
6. LIMITATION OF LIABILITY
|
||||||
|
IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For licensing inquiries, please contact: https://mchus.pro
|
||||||
@@ -112,8 +112,8 @@ func (e *Exporter) ExportJSON(w io.Writer) error {
|
|||||||
|
|
||||||
// ExportTXT exports a human-readable text report
|
// ExportTXT exports a human-readable text report
|
||||||
func (e *Exporter) ExportTXT(w io.Writer) error {
|
func (e *Exporter) ExportTXT(w io.Writer) error {
|
||||||
fmt.Fprintln(w, "LOGPile Analysis Report")
|
fmt.Fprintln(w, "LOGPile Analysis Report - mchus.pro")
|
||||||
fmt.Fprintln(w, "========================")
|
fmt.Fprintln(w, "====================================")
|
||||||
fmt.Fprintln(w)
|
fmt.Fprintln(w)
|
||||||
|
|
||||||
if e.result == nil {
|
if e.result == nil {
|
||||||
@@ -254,5 +254,11 @@ func (e *Exporter) ExportTXT(w io.Writer) error {
|
|||||||
fmt.Fprintf(w, " Warning: %d\n", warning)
|
fmt.Fprintf(w, " Warning: %d\n", warning)
|
||||||
fmt.Fprintf(w, " Info: %d\n", info)
|
fmt.Fprintf(w, " Info: %d\n", info)
|
||||||
|
|
||||||
|
// Footer
|
||||||
|
fmt.Fprintln(w)
|
||||||
|
fmt.Fprintln(w, "------------------------------------")
|
||||||
|
fmt.Fprintln(w, "Generated by LOGPile - mchus.pro")
|
||||||
|
fmt.Fprintln(w, "https://git.mchus.pro/mchus/logpile")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -353,6 +353,15 @@ func (s *Server) handleGetSerials(w http.ResponseWriter, r *http.Request) {
|
|||||||
Category: "PSU",
|
Category: "PSU",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Firmware (using version as "serial number" for display)
|
||||||
|
for _, fw := range result.Hardware.Firmware {
|
||||||
|
serials = append(serials, SerialEntry{
|
||||||
|
Component: fw.DeviceName,
|
||||||
|
SerialNumber: fw.Version,
|
||||||
|
Category: "Firmware",
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonResponse(w, serials)
|
jsonResponse(w, serials)
|
||||||
@@ -364,7 +373,81 @@ func (s *Server) handleGetFirmware(w http.ResponseWriter, r *http.Request) {
|
|||||||
jsonResponse(w, []interface{}{})
|
jsonResponse(w, []interface{}{})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
jsonResponse(w, result.Hardware.Firmware)
|
|
||||||
|
// Deduplicate firmware by extracting model name and version
|
||||||
|
// E.g., "PSU0 (AP-CR3000F12BY)" and "PSU1 (AP-CR3000F12BY)" with same version -> one entry
|
||||||
|
type FirmwareEntry struct {
|
||||||
|
Component string `json:"component"`
|
||||||
|
Model string `json:"model"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
var deduplicated []FirmwareEntry
|
||||||
|
|
||||||
|
for _, fw := range result.Hardware.Firmware {
|
||||||
|
// Extract component type and model from device name
|
||||||
|
component, model := extractFirmwareComponentAndModel(fw.DeviceName)
|
||||||
|
key := component + "|" + model + "|" + fw.Version
|
||||||
|
|
||||||
|
if !seen[key] {
|
||||||
|
seen[key] = true
|
||||||
|
deduplicated = append(deduplicated, FirmwareEntry{
|
||||||
|
Component: component,
|
||||||
|
Model: model,
|
||||||
|
Version: fw.Version,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonResponse(w, deduplicated)
|
||||||
|
}
|
||||||
|
|
||||||
|
// extractFirmwareComponentAndModel extracts the component type and model from firmware device name
|
||||||
|
func extractFirmwareComponentAndModel(deviceName string) (component, model string) {
|
||||||
|
// Parse different firmware name formats and extract component + model
|
||||||
|
|
||||||
|
// For "PSU0 (AP-CR3000F12BY)" -> component: "PSU", model: "AP-CR3000F12BY"
|
||||||
|
if strings.HasPrefix(deviceName, "PSU") {
|
||||||
|
if idx := strings.Index(deviceName, "("); idx != -1 {
|
||||||
|
model = strings.Trim(deviceName[idx:], "()")
|
||||||
|
return "PSU", model
|
||||||
|
}
|
||||||
|
return "PSU", "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
// For "CPU0 Microcode" -> component: "CPU Microcode", model: "-"
|
||||||
|
if strings.HasPrefix(deviceName, "CPU") && strings.Contains(deviceName, "Microcode") {
|
||||||
|
return "CPU Microcode", "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
// For "NIC #CPU1_PCIE9 (MCX512A-ACAT)" -> component: "NIC", model: "MCX512A-ACAT"
|
||||||
|
if strings.HasPrefix(deviceName, "NIC ") {
|
||||||
|
if idx := strings.Index(deviceName, "("); idx != -1 {
|
||||||
|
model = strings.Trim(deviceName[idx:], "()")
|
||||||
|
return "NIC", model
|
||||||
|
}
|
||||||
|
return "NIC", "-"
|
||||||
|
}
|
||||||
|
|
||||||
|
// For "HDD Samsung MZ7L33T8HBNA-00A07" -> component: "HDD", model: "Samsung MZ7L33T8HBNA-00A07"
|
||||||
|
if strings.HasPrefix(deviceName, "HDD ") {
|
||||||
|
return "HDD", strings.TrimPrefix(deviceName, "HDD ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// For "SSD Samsung MZ7..." -> component: "SSD", model: "Samsung MZ7..."
|
||||||
|
if strings.HasPrefix(deviceName, "SSD ") {
|
||||||
|
return "SSD", strings.TrimPrefix(deviceName, "SSD ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// For "NVMe KIOXIA..." -> component: "NVMe", model: "KIOXIA..."
|
||||||
|
if strings.HasPrefix(deviceName, "NVMe ") {
|
||||||
|
return "NVMe", strings.TrimPrefix(deviceName, "NVMe ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// For simple names like "BIOS", "ME", "BKC", "Virtual MicroCo"
|
||||||
|
// component = name, model = "-"
|
||||||
|
return deviceName, "-"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) handleGetStatus(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) handleGetStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ header p {
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-domain {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 400;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
max-width: 1400px;
|
max-width: 1400px;
|
||||||
margin: 2rem auto;
|
margin: 2rem auto;
|
||||||
@@ -248,6 +254,10 @@ code {
|
|||||||
background: #9b59b6;
|
background: #9b59b6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.category-badge.firmware {
|
||||||
|
background: #34495e;
|
||||||
|
}
|
||||||
|
|
||||||
/* Toolbar label */
|
/* Toolbar label */
|
||||||
.toolbar-label {
|
.toolbar-label {
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
@@ -365,6 +375,21 @@ footer {
|
|||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer-info {
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-info a {
|
||||||
|
color: #3498db;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer-info a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
#clear-btn {
|
#clear-btn {
|
||||||
background: #e74c3c;
|
background: #e74c3c;
|
||||||
color: white;
|
color: white;
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ function renderConfig(data) {
|
|||||||
<button class="config-tab" data-config-tab="gpu">GPU</button>
|
<button class="config-tab" data-config-tab="gpu">GPU</button>
|
||||||
<button class="config-tab" data-config-tab="network">Network</button>
|
<button class="config-tab" data-config-tab="network">Network</button>
|
||||||
<button class="config-tab" data-config-tab="pcie">Device Inventory</button>
|
<button class="config-tab" data-config-tab="pcie">Device Inventory</button>
|
||||||
|
<button class="config-tab" data-config-tab="fw">Firmware</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
// Specification tab
|
// Specification tab
|
||||||
@@ -340,6 +341,9 @@ function renderConfig(data) {
|
|||||||
}
|
}
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
|
// Firmware tab (content will be populated after firmware loads)
|
||||||
|
html += '<div class="config-tab-content" id="config-fw"><div id="config-fw-content"><p class="no-data">Загрузка...</p></div></div>';
|
||||||
|
|
||||||
container.innerHTML = html;
|
container.innerHTML = html;
|
||||||
|
|
||||||
// Initialize config sub-tabs
|
// Initialize config sub-tabs
|
||||||
@@ -368,24 +372,47 @@ async function loadFirmware() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let allFirmware = [];
|
||||||
|
|
||||||
function renderFirmware(firmware) {
|
function renderFirmware(firmware) {
|
||||||
|
allFirmware = firmware || [];
|
||||||
|
|
||||||
|
// Render in Firmware tab
|
||||||
const tbody = document.querySelector('#firmware-table tbody');
|
const tbody = document.querySelector('#firmware-table tbody');
|
||||||
tbody.innerHTML = '';
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
if (!firmware || firmware.length === 0) {
|
if (!firmware || firmware.length === 0) {
|
||||||
tbody.innerHTML = '<tr><td colspan="3" class="no-data">Нет данных о прошивках</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="3" class="no-data">Нет данных о прошивках</td></tr>';
|
||||||
return;
|
} else {
|
||||||
|
firmware.forEach(fw => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${escapeHtml(fw.component)}</td>
|
||||||
|
<td>${escapeHtml(fw.model)}</td>
|
||||||
|
<td><code>${escapeHtml(fw.version)}</code></td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
firmware.forEach(fw => {
|
// Render in Config -> Firmware tab
|
||||||
const row = document.createElement('tr');
|
const configFwContent = document.getElementById('config-fw-content');
|
||||||
row.innerHTML = `
|
if (configFwContent) {
|
||||||
<td>${escapeHtml(fw.device_name)}</td>
|
if (!firmware || firmware.length === 0) {
|
||||||
<td><code>${escapeHtml(fw.version)}</code></td>
|
configFwContent.innerHTML = '<p class="no-data">Нет данных о прошивках</p>';
|
||||||
<td>${escapeHtml(fw.build_date || '-')}</td>
|
} else {
|
||||||
`;
|
let html = '<h3>Прошивки компонентов</h3><table class="config-table"><thead><tr><th>Компонент</th><th>Модель</th><th>Версия</th></tr></thead><tbody>';
|
||||||
tbody.appendChild(row);
|
firmware.forEach(fw => {
|
||||||
});
|
html += `<tr>
|
||||||
|
<td>${escapeHtml(fw.component)}</td>
|
||||||
|
<td>${escapeHtml(fw.model)}</td>
|
||||||
|
<td><code>${escapeHtml(fw.version)}</code></td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
html += '</tbody></table>';
|
||||||
|
configFwContent.innerHTML = html;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSensors() {
|
async function loadSensors() {
|
||||||
@@ -491,6 +518,7 @@ function renderSerials(serials) {
|
|||||||
'PCIe': 'PCIe',
|
'PCIe': 'PCIe',
|
||||||
'Network': 'Сеть',
|
'Network': 'Сеть',
|
||||||
'PSU': 'БП',
|
'PSU': 'БП',
|
||||||
|
'Firmware': 'Прошивка',
|
||||||
'FRU': 'FRU'
|
'FRU': 'FRU'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1>LOGPile</h1>
|
<h1>LOGPile <span class="header-domain">mchus.pro</span></h1>
|
||||||
<p>Анализатор диагностических данных BMC/IPMI</p>
|
<p>Анализатор диагностических данных BMC/IPMI</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -48,8 +48,8 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Компонент</th>
|
<th>Компонент</th>
|
||||||
|
<th>Модель</th>
|
||||||
<th>Версия</th>
|
<th>Версия</th>
|
||||||
<th>Дата сборки</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody></tbody>
|
<tbody></tbody>
|
||||||
@@ -80,6 +80,7 @@
|
|||||||
<option value="PCIe">PCIe устройства</option>
|
<option value="PCIe">PCIe устройства</option>
|
||||||
<option value="Network">Сетевые адаптеры</option>
|
<option value="Network">Сетевые адаптеры</option>
|
||||||
<option value="PSU">Блоки питания</option>
|
<option value="PSU">Блоки питания</option>
|
||||||
|
<option value="Firmware">Прошивки</option>
|
||||||
<option value="FRU">FRU</option>
|
<option value="FRU">FRU</option>
|
||||||
</select>
|
</select>
|
||||||
<button onclick="exportData('csv')">Экспорт CSV</button>
|
<button onclick="exportData('csv')">Экспорт CSV</button>
|
||||||
@@ -124,6 +125,9 @@
|
|||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<button id="clear-btn" class="hidden" onclick="clearData()">Очистить данные</button>
|
<button id="clear-btn" class="hidden" onclick="clearData()">Очистить данные</button>
|
||||||
|
<div class="footer-info">
|
||||||
|
<p>Автор: <a href="https://mchus.pro" target="_blank">mchus.pro</a> | <a href="https://git.mchus.pro/mchus/logpile" target="_blank">Git Repository</a></p>
|
||||||
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="/static/js/app.js"></script>
|
<script src="/static/js/app.js"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user