Report hotspot config
This commit is contained in:
parent
17d665e21a
commit
c443fce24f
8 changed files with 244 additions and 160 deletions
|
|
@ -149,43 +149,6 @@ async function disconnectWifi() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateHotspot() {
|
|
||||||
const ssid = document.getElementById('hotspotName').value;
|
|
||||||
const password = document.getElementById('hotspotPassword').value;
|
|
||||||
const channel = parseInt(document.getElementById('hotspotChannel').value);
|
|
||||||
|
|
||||||
if (!ssid || ssid.length > 32) {
|
|
||||||
showToast('warning', 'Attention', 'SSID invalide (1-32 caractères)');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!password || password.length < 8 || password.length > 63) {
|
|
||||||
showToast('warning', 'Attention', 'Mot de passe invalide (8-63 caractères)');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
showLoading(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/hotspot/config', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ ssid, password, channel })
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
showToast('success', 'Configuration', 'Hotspot configuré avec succès');
|
|
||||||
} else {
|
|
||||||
throw new Error('Configuration failed');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error configuring hotspot:', error);
|
|
||||||
showToast('error', 'Erreur', 'Échec de la configuration');
|
|
||||||
} finally {
|
|
||||||
showLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function toggleHotspot() {
|
async function toggleHotspot() {
|
||||||
const toggle = document.getElementById('hotspotToggle');
|
const toggle = document.getElementById('hotspotToggle');
|
||||||
const enabled = toggle.checked;
|
const enabled = toggle.checked;
|
||||||
|
|
@ -286,9 +249,12 @@ function updateStatusDisplay(status) {
|
||||||
const hotspotText = hotspotStatus.querySelector('.status-text');
|
const hotspotText = hotspotStatus.querySelector('.status-text');
|
||||||
const hotspotToggle = document.getElementById('hotspotToggle');
|
const hotspotToggle = document.getElementById('hotspotToggle');
|
||||||
|
|
||||||
if (status.hotspotEnabled) {
|
const isHotspotEnabled = status.hotspotStatus && status.hotspotStatus.state === 'ENABLED';
|
||||||
|
|
||||||
|
if (isHotspotEnabled) {
|
||||||
hotspotDot.className = 'status-dot active';
|
hotspotDot.className = 'status-dot active';
|
||||||
hotspotText.textContent = 'Hotspot actif';
|
const numStations = status.hotspotStatus.numStations || 0;
|
||||||
|
hotspotText.textContent = `Hotspot actif (${numStations} client${numStations > 1 ? 's' : ''})`;
|
||||||
hotspotToggle.checked = true;
|
hotspotToggle.checked = true;
|
||||||
} else {
|
} else {
|
||||||
hotspotDot.className = 'status-dot offline';
|
hotspotDot.className = 'status-dot offline';
|
||||||
|
|
@ -296,7 +262,10 @@ function updateStatusDisplay(status) {
|
||||||
hotspotToggle.checked = false;
|
hotspotToggle.checked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
appState.hotspotEnabled = status.hotspotEnabled;
|
appState.hotspotEnabled = isHotspotEnabled;
|
||||||
|
|
||||||
|
// Update hotspot details if available
|
||||||
|
updateHotspotDetails(status.hotspotStatus);
|
||||||
|
|
||||||
// Check if connectedSSID changed and refresh WiFi list if needed
|
// Check if connectedSSID changed and refresh WiFi list if needed
|
||||||
const connectedSSIDChanged = appState.connectedSSID !== status.connectedSSID;
|
const connectedSSIDChanged = appState.connectedSSID !== status.connectedSSID;
|
||||||
|
|
@ -409,11 +378,21 @@ function addLogEntry(log) {
|
||||||
|
|
||||||
const timestamp = new Date(log.timestamp).toLocaleTimeString('fr-FR');
|
const timestamp = new Date(log.timestamp).toLocaleTimeString('fr-FR');
|
||||||
|
|
||||||
logEntry.innerHTML = `
|
const timestampSpan = document.createElement('span');
|
||||||
<span class="log-timestamp">${timestamp}</span>
|
timestampSpan.className = 'log-timestamp';
|
||||||
<span class="log-source">[${escapeHtml(log.source)}]</span>
|
timestampSpan.textContent = timestamp;
|
||||||
<span class="log-message">${escapeHtml(log.message)}</span>
|
|
||||||
`;
|
const sourceSpan = document.createElement('span');
|
||||||
|
sourceSpan.className = 'log-source';
|
||||||
|
sourceSpan.textContent = `[${log.source}]`;
|
||||||
|
|
||||||
|
const messageSpan = document.createElement('span');
|
||||||
|
messageSpan.className = 'log-message';
|
||||||
|
messageSpan.textContent = log.message;
|
||||||
|
|
||||||
|
logEntry.appendChild(timestampSpan);
|
||||||
|
logEntry.appendChild(sourceSpan);
|
||||||
|
logEntry.appendChild(messageSpan);
|
||||||
|
|
||||||
logContainer.appendChild(logEntry);
|
logContainer.appendChild(logEntry);
|
||||||
|
|
||||||
|
|
@ -422,6 +401,55 @@ function addLogEntry(log) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createHotspotInfoItem(label, value) {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'hotspot-info-item';
|
||||||
|
|
||||||
|
const labelSpan = document.createElement('span');
|
||||||
|
labelSpan.className = 'info-label';
|
||||||
|
labelSpan.textContent = label + ':';
|
||||||
|
|
||||||
|
const valueSpan = document.createElement('span');
|
||||||
|
valueSpan.className = 'info-value';
|
||||||
|
valueSpan.textContent = value;
|
||||||
|
|
||||||
|
item.appendChild(labelSpan);
|
||||||
|
item.appendChild(valueSpan);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHotspotDetails(hotspotStatus) {
|
||||||
|
const detailsContainer = document.getElementById('hotspotDetails');
|
||||||
|
if (!detailsContainer) return;
|
||||||
|
|
||||||
|
// Clear existing content
|
||||||
|
detailsContainer.innerHTML = '';
|
||||||
|
|
||||||
|
if (!hotspotStatus || hotspotStatus.state !== 'ENABLED') {
|
||||||
|
detailsContainer.appendChild(createHotspotInfoItem('État', 'Inactif'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
detailsContainer.appendChild(createHotspotInfoItem('État', hotspotStatus.state));
|
||||||
|
|
||||||
|
if (hotspotStatus.ssid) {
|
||||||
|
detailsContainer.appendChild(createHotspotInfoItem('SSID', hotspotStatus.ssid));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hotspotStatus.channel) {
|
||||||
|
detailsContainer.appendChild(createHotspotInfoItem('Canal', `${hotspotStatus.channel} (${hotspotStatus.frequency} MHz)`));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hotspotStatus.numStations !== undefined) {
|
||||||
|
detailsContainer.appendChild(createHotspotInfoItem('Clients connectés', hotspotStatus.numStations.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hotspotStatus.bssid) {
|
||||||
|
detailsContainer.appendChild(createHotspotInfoItem('BSSID', hotspotStatus.bssid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ===== WebSocket Functions =====
|
// ===== WebSocket Functions =====
|
||||||
|
|
||||||
function connectWebSocket() {
|
function connectWebSocket() {
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@
|
||||||
<svg class="icon" viewBox="0 0 24 24" width="20" height="20">
|
<svg class="icon" viewBox="0 0 24 24" width="20" height="20">
|
||||||
<path fill="currentColor" d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4M12,6A6,6 0 0,0 6,12A6,6 0 0,0 12,18A6,6 0 0,0 18,12A6,6 0 0,0 12,6M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8Z"/>
|
<path fill="currentColor" d="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4M12,6A6,6 0 0,0 6,12A6,6 0 0,0 12,18A6,6 0 0,0 18,12A6,6 0 0,0 12,6M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
Configuration Hotspot
|
Hotspot Status
|
||||||
</h2>
|
</h2>
|
||||||
<div class="toggle-switch">
|
<div class="toggle-switch">
|
||||||
<input type="checkbox" id="hotspotToggle" checked onchange="toggleHotspot()">
|
<input type="checkbox" id="hotspotToggle" checked onchange="toggleHotspot()">
|
||||||
|
|
@ -141,54 +141,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="hotspot-details" id="hotspotDetails">
|
||||||
<label for="hotspotName">
|
<div class="hotspot-info-item">
|
||||||
<svg class="input-icon" viewBox="0 0 24 24" width="16" height="16">
|
<span class="info-label">État:</span>
|
||||||
<path fill="currentColor" d="M12,6A10,10 0 0,0 2,16C2,16.5 2.04,17 2.11,17.5L6,13.61C6,13.61 9.26,15.45 9.26,15.45L11.12,13C11.12,13 12.19,13.53 12.19,13.53C12.19,13.53 13.72,12 13.72,12L12,10.28C12,10.28 14.97,8.12 14.97,8.12L22,15.15C22,14.77 22,14.39 22,14A10,10 0 0,0 12,6Z"/>
|
<span class="info-value">Chargement...</span>
|
||||||
</svg>
|
|
||||||
Nom du réseau (SSID)
|
|
||||||
</label>
|
|
||||||
<input type="text" id="hotspotName" value="TravelRouter" placeholder="Nom du hotspot" maxlength="32">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="hotspotPassword">
|
|
||||||
<svg class="input-icon" viewBox="0 0 24 24" width="16" height="16">
|
|
||||||
<path fill="currentColor" d="M12,17A2,2 0 0,0 14,15C14,13.89 13.1,13 12,13A2,2 0 0,0 10,15A2,2 0 0,0 12,17M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6A2,2 0 0,1 4,20V10C4,8.89 4.9,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z"/>
|
|
||||||
</svg>
|
|
||||||
Mot de passe (8-63 caractères)
|
|
||||||
</label>
|
|
||||||
<input type="password" id="hotspotPassword" value="travel123" placeholder="Mot de passe du hotspot" minlength="8" maxlength="63">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="hotspotChannel">
|
|
||||||
<svg class="input-icon" viewBox="0 0 24 24" width="16" height="16">
|
|
||||||
<path fill="currentColor" d="M9,10H7V17H9V10M13,10H11V17H13V10M17,10H15V17H17V10M19,3H18V1H16V3H8V1H6V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M19,19H5V8H19V19Z"/>
|
|
||||||
</svg>
|
|
||||||
Canal WiFi (2.4GHz)
|
|
||||||
</label>
|
|
||||||
<select id="hotspotChannel">
|
|
||||||
<option value="1">Canal 1 (2412 MHz)</option>
|
|
||||||
<option value="2">Canal 2 (2417 MHz)</option>
|
|
||||||
<option value="3">Canal 3 (2422 MHz)</option>
|
|
||||||
<option value="4">Canal 4 (2427 MHz)</option>
|
|
||||||
<option value="5">Canal 5 (2432 MHz)</option>
|
|
||||||
<option value="6" selected>Canal 6 (2437 MHz)</option>
|
|
||||||
<option value="7">Canal 7 (2442 MHz)</option>
|
|
||||||
<option value="8">Canal 8 (2447 MHz)</option>
|
|
||||||
<option value="9">Canal 9 (2452 MHz)</option>
|
|
||||||
<option value="10">Canal 10 (2457 MHz)</option>
|
|
||||||
<option value="11">Canal 11 (2462 MHz)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn btn-primary" onclick="updateHotspot()">
|
|
||||||
<svg viewBox="0 0 24 24" width="18" height="18">
|
|
||||||
<path fill="currentColor" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"/>
|
|
||||||
</svg>
|
|
||||||
Mettre à jour la configuration
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -747,6 +747,39 @@ body {
|
||||||
background: var(--text-secondary);
|
background: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hotspot Details */
|
||||||
|
.hotspot-details {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: var(--background);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotspot-info-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hotspot-info-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive Design */
|
/* Responsive Design */
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.grid {
|
.grid {
|
||||||
|
|
|
||||||
|
|
@ -82,11 +82,11 @@ func ConfigureHotspot(c *gin.Context) {
|
||||||
|
|
||||||
// ToggleHotspot handles hotspot enable/disable
|
// ToggleHotspot handles hotspot enable/disable
|
||||||
func ToggleHotspot(c *gin.Context, status *models.SystemStatus) {
|
func ToggleHotspot(c *gin.Context, status *models.SystemStatus) {
|
||||||
status.HotspotEnabled = !status.HotspotEnabled
|
// Determine current state
|
||||||
enabled := status.HotspotEnabled
|
isEnabled := status.HotspotStatus != nil && status.HotspotStatus.State == "ENABLED"
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if enabled {
|
if !isEnabled {
|
||||||
err = hotspot.Start()
|
err = hotspot.Start()
|
||||||
logging.AddLog("Hotspot", "Hotspot activé")
|
logging.AddLog("Hotspot", "Hotspot activé")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -100,7 +100,10 @@ func ToggleHotspot(c *gin.Context, status *models.SystemStatus) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"enabled": enabled})
|
// Update status immediately
|
||||||
|
status.HotspotStatus = hotspot.GetDetailedStatus()
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"enabled": !isEnabled})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDevices returns connected devices
|
// GetDevices returns connected devices
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/nemunaire/repeater/internal/api"
|
"github.com/nemunaire/repeater/internal/api"
|
||||||
"github.com/nemunaire/repeater/internal/config"
|
"github.com/nemunaire/repeater/internal/config"
|
||||||
"github.com/nemunaire/repeater/internal/device"
|
"github.com/nemunaire/repeater/internal/device"
|
||||||
|
"github.com/nemunaire/repeater/internal/hotspot"
|
||||||
"github.com/nemunaire/repeater/internal/logging"
|
"github.com/nemunaire/repeater/internal/logging"
|
||||||
"github.com/nemunaire/repeater/internal/models"
|
"github.com/nemunaire/repeater/internal/models"
|
||||||
"github.com/nemunaire/repeater/internal/wifi"
|
"github.com/nemunaire/repeater/internal/wifi"
|
||||||
|
|
@ -32,7 +33,7 @@ func New(assets embed.FS) *App {
|
||||||
Status: models.SystemStatus{
|
Status: models.SystemStatus{
|
||||||
Connected: false,
|
Connected: false,
|
||||||
ConnectedSSID: "",
|
ConnectedSSID: "",
|
||||||
HotspotEnabled: true,
|
HotspotStatus: nil,
|
||||||
ConnectedCount: 0,
|
ConnectedCount: 0,
|
||||||
DataUsage: 0.0,
|
DataUsage: 0.0,
|
||||||
Uptime: 0,
|
Uptime: 0,
|
||||||
|
|
@ -131,6 +132,9 @@ func (a *App) periodicStatusUpdate() {
|
||||||
a.Status.ConnectedSSID = wifi.GetConnectedSSID()
|
a.Status.ConnectedSSID = wifi.GetConnectedSSID()
|
||||||
a.Status.Uptime = getSystemUptime()
|
a.Status.Uptime = getSystemUptime()
|
||||||
|
|
||||||
|
// Get detailed hotspot status
|
||||||
|
a.Status.HotspotStatus = hotspot.GetDetailedStatus()
|
||||||
|
|
||||||
// Get network data usage for WiFi interface
|
// Get network data usage for WiFi interface
|
||||||
if a.Config != nil {
|
if a.Config != nil {
|
||||||
rxBytes, txBytes := getInterfaceBytes(a.Config.WifiInterface)
|
rxBytes, txBytes := getInterfaceBytes(a.Config.WifiInterface)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/nemunaire/repeater/internal/models"
|
"github.com/nemunaire/repeater/internal/models"
|
||||||
)
|
)
|
||||||
|
|
@ -36,12 +38,74 @@ rsn_pairwise=CCMP
|
||||||
|
|
||||||
// Start starts the hotspot
|
// Start starts the hotspot
|
||||||
func Start() error {
|
func Start() error {
|
||||||
cmd := exec.Command("systemctl", "start", "hostapd")
|
cmd := exec.Command("/etc/init.d/hostapd", "start")
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop stops the hotspot
|
// Stop stops the hotspot
|
||||||
func Stop() error {
|
func Stop() error {
|
||||||
cmd := exec.Command("systemctl", "stop", "hostapd")
|
cmd := exec.Command("/etc/init.d/hostapd", "stop")
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Status checks if the hotspot is running.
|
||||||
|
// Returns nil if the service is running, or an error if it's stopped or crashed.
|
||||||
|
func Status() error {
|
||||||
|
cmd := exec.Command("/etc/init.d/hostapd", "status")
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDetailedStatus retrieves detailed status information from hostapd_cli.
|
||||||
|
// Returns nil if hostapd is not running or if there's an error.
|
||||||
|
func GetDetailedStatus() *models.HotspotStatus {
|
||||||
|
cmd := exec.Command("hostapd_cli", "status")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
status := &models.HotspotStatus{}
|
||||||
|
lines := strings.Split(string(output), "\n")
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" || strings.HasPrefix(line, "Selected interface") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(line, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := strings.TrimSpace(parts[0])
|
||||||
|
value := strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
|
switch key {
|
||||||
|
case "state":
|
||||||
|
status.State = value
|
||||||
|
case "channel":
|
||||||
|
if ch, err := strconv.Atoi(value); err == nil {
|
||||||
|
status.Channel = ch
|
||||||
|
}
|
||||||
|
case "freq":
|
||||||
|
if freq, err := strconv.Atoi(value); err == nil {
|
||||||
|
status.Frequency = freq
|
||||||
|
}
|
||||||
|
case "ssid[0]":
|
||||||
|
status.SSID = value
|
||||||
|
case "bssid[0]":
|
||||||
|
status.BSSID = value
|
||||||
|
case "num_sta[0]":
|
||||||
|
if num, err := strconv.Atoi(value); err == nil {
|
||||||
|
status.NumStations = num
|
||||||
|
}
|
||||||
|
case "hw_mode":
|
||||||
|
status.HWMode = value
|
||||||
|
case "country_code":
|
||||||
|
status.CountryCode = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return status
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,23 @@ type HotspotConfig struct {
|
||||||
Channel int `json:"channel"`
|
Channel int `json:"channel"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HotspotStatus represents detailed hotspot status
|
||||||
|
type HotspotStatus struct {
|
||||||
|
State string `json:"state"` // ENABLED, DISABLED, etc.
|
||||||
|
SSID string `json:"ssid"` // Current SSID being broadcast
|
||||||
|
BSSID string `json:"bssid"` // MAC address of the AP
|
||||||
|
Channel int `json:"channel"` // Current channel
|
||||||
|
Frequency int `json:"frequency"` // Frequency in MHz
|
||||||
|
NumStations int `json:"numStations"` // Number of connected stations
|
||||||
|
HWMode string `json:"hwMode"` // Hardware mode (g, a, n, ac, etc.)
|
||||||
|
CountryCode string `json:"countryCode"` // Country code
|
||||||
|
}
|
||||||
|
|
||||||
// SystemStatus represents overall system status
|
// SystemStatus represents overall system status
|
||||||
type SystemStatus struct {
|
type SystemStatus struct {
|
||||||
Connected bool `json:"connected"`
|
Connected bool `json:"connected"`
|
||||||
ConnectedSSID string `json:"connectedSSID"`
|
ConnectedSSID string `json:"connectedSSID"`
|
||||||
HotspotEnabled bool `json:"hotspotEnabled"`
|
HotspotStatus *HotspotStatus `json:"hotspotStatus,omitempty"` // Detailed hotspot status
|
||||||
ConnectedCount int `json:"connectedCount"`
|
ConnectedCount int `json:"connectedCount"`
|
||||||
DataUsage float64 `json:"dataUsage"`
|
DataUsage float64 `json:"dataUsage"`
|
||||||
Uptime int64 `json:"uptime"`
|
Uptime int64 `json:"uptime"`
|
||||||
|
|
|
||||||
100
openapi.yaml
100
openapi.yaml
|
|
@ -133,42 +133,6 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: '#/components/schemas/Error'
|
||||||
|
|
||||||
/api/hotspot/config:
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- Hotspot
|
|
||||||
summary: Configure hotspot settings
|
|
||||||
description: |
|
|
||||||
Updates the hotspot (access point) configuration including SSID, password,
|
|
||||||
and WiFi channel. Changes are written to hostapd configuration file.
|
|
||||||
The hotspot needs to be restarted for changes to take effect.
|
|
||||||
operationId: configureHotspot
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/HotspotConfig'
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Configuration updated successfully
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/SuccessResponse'
|
|
||||||
'400':
|
|
||||||
description: Invalid configuration data
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/Error'
|
|
||||||
'500':
|
|
||||||
description: Configuration update failed
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/Error'
|
|
||||||
|
|
||||||
/api/hotspot/toggle:
|
/api/hotspot/toggle:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
|
|
@ -176,7 +140,8 @@ paths:
|
||||||
summary: Toggle hotspot on/off
|
summary: Toggle hotspot on/off
|
||||||
description: |
|
description: |
|
||||||
Enables or disables the hotspot (access point) by starting/stopping
|
Enables or disables the hotspot (access point) by starting/stopping
|
||||||
the hostapd service. Returns the new enabled state.
|
the hostapd service. Returns the new enabled state and updates
|
||||||
|
the system status with current hostapd_cli information.
|
||||||
operationId: toggleHotspot
|
operationId: toggleHotspot
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
|
|
@ -242,7 +207,8 @@ paths:
|
||||||
summary: Get system status
|
summary: Get system status
|
||||||
description: |
|
description: |
|
||||||
Returns comprehensive system status including WiFi connection state,
|
Returns comprehensive system status including WiFi connection state,
|
||||||
hotspot status, connected device count, data usage, and uptime.
|
detailed hotspot status from hostapd_cli, connected device count,
|
||||||
|
data usage, and uptime.
|
||||||
operationId: getStatus
|
operationId: getStatus
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
|
|
@ -367,32 +333,48 @@ components:
|
||||||
- ssid
|
- ssid
|
||||||
- password
|
- password
|
||||||
|
|
||||||
HotspotConfig:
|
HotspotStatus:
|
||||||
type: object
|
type: object
|
||||||
description: Hotspot (access point) configuration
|
description: Detailed hotspot status from hostapd_cli
|
||||||
properties:
|
properties:
|
||||||
|
state:
|
||||||
|
type: string
|
||||||
|
description: Hotspot state (ENABLED, DISABLED, etc.)
|
||||||
|
example: "ENABLED"
|
||||||
ssid:
|
ssid:
|
||||||
type: string
|
type: string
|
||||||
description: Hotspot SSID (network name)
|
description: Current SSID being broadcast
|
||||||
minLength: 1
|
|
||||||
maxLength: 32
|
|
||||||
example: "TravelRouter"
|
example: "TravelRouter"
|
||||||
password:
|
bssid:
|
||||||
type: string
|
type: string
|
||||||
description: WPA2 password (minimum 8 characters)
|
description: MAC address of the access point
|
||||||
minLength: 8
|
pattern: '^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$'
|
||||||
maxLength: 63
|
example: "4a:e3:4e:09:57:f8"
|
||||||
example: "secure123"
|
|
||||||
channel:
|
channel:
|
||||||
type: integer
|
type: integer
|
||||||
description: WiFi channel (1-11 for 2.4GHz)
|
description: Current WiFi channel
|
||||||
minimum: 1
|
minimum: 1
|
||||||
maximum: 14
|
maximum: 14
|
||||||
example: 6
|
example: 11
|
||||||
|
frequency:
|
||||||
|
type: integer
|
||||||
|
description: Frequency in MHz
|
||||||
|
example: 2462
|
||||||
|
numStations:
|
||||||
|
type: integer
|
||||||
|
description: Number of connected stations
|
||||||
|
minimum: 0
|
||||||
|
example: 2
|
||||||
|
hwMode:
|
||||||
|
type: string
|
||||||
|
description: Hardware mode (g, a, n, ac, etc.)
|
||||||
|
example: "g"
|
||||||
|
countryCode:
|
||||||
|
type: string
|
||||||
|
description: Country code
|
||||||
|
example: "VN"
|
||||||
required:
|
required:
|
||||||
- ssid
|
- state
|
||||||
- password
|
|
||||||
- channel
|
|
||||||
|
|
||||||
ConnectedDevice:
|
ConnectedDevice:
|
||||||
type: object
|
type: object
|
||||||
|
|
@ -440,10 +422,11 @@ components:
|
||||||
type: string
|
type: string
|
||||||
description: SSID of connected upstream network (empty if not connected)
|
description: SSID of connected upstream network (empty if not connected)
|
||||||
example: "Hotel-Guest"
|
example: "Hotel-Guest"
|
||||||
hotspotEnabled:
|
hotspotStatus:
|
||||||
type: boolean
|
allOf:
|
||||||
description: Whether the hotspot is currently enabled
|
- $ref: '#/components/schemas/HotspotStatus'
|
||||||
example: true
|
nullable: true
|
||||||
|
description: Detailed hotspot status (null if hotspot is not running)
|
||||||
connectedCount:
|
connectedCount:
|
||||||
type: integer
|
type: integer
|
||||||
description: Number of devices connected to hotspot
|
description: Number of devices connected to hotspot
|
||||||
|
|
@ -452,7 +435,7 @@ components:
|
||||||
dataUsage:
|
dataUsage:
|
||||||
type: number
|
type: number
|
||||||
format: double
|
format: double
|
||||||
description: Total data usage in MB (placeholder for future implementation)
|
description: Total data usage in MB
|
||||||
example: 145.7
|
example: 145.7
|
||||||
uptime:
|
uptime:
|
||||||
type: integer
|
type: integer
|
||||||
|
|
@ -467,7 +450,6 @@ components:
|
||||||
required:
|
required:
|
||||||
- connected
|
- connected
|
||||||
- connectedSSID
|
- connectedSSID
|
||||||
- hotspotEnabled
|
|
||||||
- connectedCount
|
- connectedCount
|
||||||
- dataUsage
|
- dataUsage
|
||||||
- uptime
|
- uptime
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue