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
|
||||
|
||||
|
||||
# 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
|
||||
func (e *Exporter) ExportTXT(w io.Writer) error {
|
||||
fmt.Fprintln(w, "LOGPile Analysis Report")
|
||||
fmt.Fprintln(w, "========================")
|
||||
fmt.Fprintln(w, "LOGPile Analysis Report - mchus.pro")
|
||||
fmt.Fprintln(w, "====================================")
|
||||
fmt.Fprintln(w)
|
||||
|
||||
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, " 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
|
||||
}
|
||||
|
||||
@@ -353,6 +353,15 @@ func (s *Server) handleGetSerials(w http.ResponseWriter, r *http.Request) {
|
||||
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)
|
||||
@@ -364,7 +373,81 @@ func (s *Server) handleGetFirmware(w http.ResponseWriter, r *http.Request) {
|
||||
jsonResponse(w, []interface{}{})
|
||||
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) {
|
||||
|
||||
@@ -27,6 +27,12 @@ header p {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.header-domain {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 400;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1400px;
|
||||
margin: 2rem auto;
|
||||
@@ -248,6 +254,10 @@ code {
|
||||
background: #9b59b6;
|
||||
}
|
||||
|
||||
.category-badge.firmware {
|
||||
background: #34495e;
|
||||
}
|
||||
|
||||
/* Toolbar label */
|
||||
.toolbar-label {
|
||||
font-size: 0.875rem;
|
||||
@@ -365,6 +375,21 @@ footer {
|
||||
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 {
|
||||
background: #e74c3c;
|
||||
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="network">Network</button>
|
||||
<button class="config-tab" data-config-tab="pcie">Device Inventory</button>
|
||||
<button class="config-tab" data-config-tab="fw">Firmware</button>
|
||||
</div>`;
|
||||
|
||||
// Specification tab
|
||||
@@ -340,6 +341,9 @@ function renderConfig(data) {
|
||||
}
|
||||
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;
|
||||
|
||||
// Initialize config sub-tabs
|
||||
@@ -368,24 +372,47 @@ async function loadFirmware() {
|
||||
}
|
||||
}
|
||||
|
||||
let allFirmware = [];
|
||||
|
||||
function renderFirmware(firmware) {
|
||||
allFirmware = firmware || [];
|
||||
|
||||
// Render in Firmware tab
|
||||
const tbody = document.querySelector('#firmware-table tbody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
if (!firmware || firmware.length === 0) {
|
||||
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 => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${escapeHtml(fw.device_name)}</td>
|
||||
<td><code>${escapeHtml(fw.version)}</code></td>
|
||||
<td>${escapeHtml(fw.build_date || '-')}</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
// Render in Config -> Firmware tab
|
||||
const configFwContent = document.getElementById('config-fw-content');
|
||||
if (configFwContent) {
|
||||
if (!firmware || firmware.length === 0) {
|
||||
configFwContent.innerHTML = '<p class="no-data">Нет данных о прошивках</p>';
|
||||
} else {
|
||||
let html = '<h3>Прошивки компонентов</h3><table class="config-table"><thead><tr><th>Компонент</th><th>Модель</th><th>Версия</th></tr></thead><tbody>';
|
||||
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() {
|
||||
@@ -491,6 +518,7 @@ function renderSerials(serials) {
|
||||
'PCIe': 'PCIe',
|
||||
'Network': 'Сеть',
|
||||
'PSU': 'БП',
|
||||
'Firmware': 'Прошивка',
|
||||
'FRU': 'FRU'
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>LOGPile</h1>
|
||||
<h1>LOGPile <span class="header-domain">mchus.pro</span></h1>
|
||||
<p>Анализатор диагностических данных BMC/IPMI</p>
|
||||
</header>
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Компонент</th>
|
||||
<th>Модель</th>
|
||||
<th>Версия</th>
|
||||
<th>Дата сборки</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
@@ -80,6 +80,7 @@
|
||||
<option value="PCIe">PCIe устройства</option>
|
||||
<option value="Network">Сетевые адаптеры</option>
|
||||
<option value="PSU">Блоки питания</option>
|
||||
<option value="Firmware">Прошивки</option>
|
||||
<option value="FRU">FRU</option>
|
||||
</select>
|
||||
<button onclick="exportData('csv')">Экспорт CSV</button>
|
||||
@@ -124,6 +125,9 @@
|
||||
|
||||
<footer>
|
||||
<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>
|
||||
|
||||
<script src="/static/js/app.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user