Refactor configuration handling by removing writable flag from ConfigInfo and related UI components. Simplify config response structure in WebUIServer and update frontend to reflect these changes. Clean up unused CSS styles for improved layout.

This commit is contained in:
Andy Oknen 2025-08-16 05:34:20 +08:00
parent 8e44b57879
commit a094c423de
4 changed files with 26 additions and 118 deletions

View file

@ -286,10 +286,9 @@ func (k *KeyBytes) UnmarshalJSON(b []byte) error {
// ConfigInfo contains information about the configuration file // ConfigInfo contains information about the configuration file
type ConfigInfo struct { type ConfigInfo struct {
Path string `json:"path"` Path string `json:"path"`
Format string `json:"format"` Format string `json:"format"`
Data interface{} `json:"data"` Data interface{} `json:"data"`
Writable bool `json:"writable"`
} }
// Global variables to track the current configuration state // Global variables to track the current configuration state
@ -338,20 +337,6 @@ func validateConfigPath(path string) (string, error) {
} }
} }
// Basic sanity check on file extension for config files
ext := strings.ToLower(filepath.Ext(absPath))
allowedExts := []string{".json", ".hjson", ".conf", ".config", ".yml", ".yaml", ""}
validExt := false
for _, allowed := range allowedExts {
if ext == allowed {
validExt = true
break
}
}
if !validExt {
return "", fmt.Errorf("invalid file extension: %s", ext)
}
// Additional check: ensure the path doesn't escape intended directories // Additional check: ensure the path doesn't escape intended directories
if strings.Count(absPath, "/") > 10 { if strings.Count(absPath, "/") > 10 {
return "", fmt.Errorf("path too deep: potential security risk") return "", fmt.Errorf("path too deep: potential security risk")
@ -381,7 +366,6 @@ func GetCurrentConfig() (*ConfigInfo, error) {
var configPath string var configPath string
var configData *NodeConfig var configData *NodeConfig
var format string = "hjson" var format string = "hjson"
var writable bool = false
// Use current config if available, otherwise try to read from default location // Use current config if available, otherwise try to read from default location
if currentConfigPath != "" && currentConfigData != nil { if currentConfigPath != "" && currentConfigData != nil {
@ -402,72 +386,33 @@ func GetCurrentConfig() (*ConfigInfo, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid default config path: %v", err) return nil, fmt.Errorf("invalid default config path: %v", err)
} }
configPath = validatedDefaultPath
// Try to read existing config file configPath = validatedDefaultPath
if _, err := os.Stat(configPath); err == nil { // Path already validated above configData = GenerateConfig()
data, err := os.ReadFile(configPath) // Path already validated above
if err == nil {
cfg := GenerateConfig()
if err := hjson.Unmarshal(data, cfg); err == nil {
configData = cfg
// Detect format
var jsonTest interface{}
if json.Unmarshal(data, &jsonTest) == nil {
format = "json"
}
} else {
return nil, fmt.Errorf("failed to parse config file: %v", err)
}
}
} else {
// No config file exists, use default
configData = GenerateConfig()
}
} }
// Detect format from file if path is known // Try to read existing config file
if configPath != "" { if _, err := os.Stat(configPath); err == nil { // Path already validated above
// Config path is already validated at this point data, err := os.ReadFile(configPath) // Path already validated above
if _, err := os.Stat(configPath); err == nil { // Path already validated above if err == nil {
data, err := os.ReadFile(configPath) // Path already validated above cfg := GenerateConfig()
if err == nil { if err := hjson.Unmarshal(data, cfg); err == nil {
configData = cfg
// Detect format
var jsonTest interface{} var jsonTest interface{}
if json.Unmarshal(data, &jsonTest) == nil { if json.Unmarshal(data, &jsonTest) == nil {
format = "json" format = "json"
} }
} } else {
} return nil, fmt.Errorf("failed to parse config file: %v", err)
}
// Check if writable
if configPath != "" {
// Config path is already validated at this point
if _, err := os.Stat(configPath); err == nil { // Path already validated above
// File exists, check if writable
if file, err := os.OpenFile(configPath, os.O_WRONLY, 0); err == nil { // Path already validated above
writable = true
file.Close()
}
} else {
// File doesn't exist, check if directory is writable
dir := filepath.Clean(filepath.Dir(configPath))
if stat, err := os.Stat(dir); err == nil && stat.IsDir() {
testFile := filepath.Join(dir, ".yggdrasil_write_test")
if file, err := os.Create(testFile); err == nil {
file.Close()
os.Remove(testFile)
writable = true
}
} }
} }
} }
return &ConfigInfo{ return &ConfigInfo{
Path: configPath, Path: configPath,
Format: format, Format: format,
Data: configData, Data: configData,
Writable: writable,
}, nil }, nil
} }
@ -516,6 +461,7 @@ func SaveConfig(configData interface{}, configPath, format string) error {
} }
} }
} }
if targetFormat == "" { if targetFormat == "" {
targetFormat = "hjson" targetFormat = "hjson"
} }

View file

@ -388,7 +388,6 @@ type ConfigResponse struct {
ConfigPath string `json:"config_path"` ConfigPath string `json:"config_path"`
ConfigFormat string `json:"config_format"` ConfigFormat string `json:"config_format"`
ConfigJSON string `json:"config_json"` ConfigJSON string `json:"config_json"`
IsWritable bool `json:"is_writable"`
} }
type ConfigSetRequest struct { type ConfigSetRequest struct {
@ -432,7 +431,6 @@ func (w *WebUIServer) getConfigHandler(rw http.ResponseWriter, r *http.Request)
ConfigPath: configInfo.Path, ConfigPath: configInfo.Path,
ConfigFormat: configInfo.Format, ConfigFormat: configInfo.Format,
ConfigJSON: string(configBytes), ConfigJSON: string(configBytes),
IsWritable: configInfo.Writable,
} }
rw.Header().Set("Content-Type", "application/json") rw.Header().Set("Content-Type", "application/json")

View file

@ -30,8 +30,7 @@ async function loadConfiguration() {
currentConfigJSON = data.config_json; currentConfigJSON = data.config_json;
configMeta = { configMeta = {
path: data.config_path, path: data.config_path,
format: data.config_format, format: data.config_format
isWritable: data.is_writable
}; };
renderConfigEditor(); renderConfigEditor();
@ -54,9 +53,6 @@ function renderConfigEditor() {
<div class="config-meta"> <div class="config-meta">
<span class="config-path" title="${configMeta.path}">${configMeta.path}</span> <span class="config-path" title="${configMeta.path}">${configMeta.path}</span>
<span class="config-format ${configMeta.format}">${configMeta.format.toUpperCase()}</span> <span class="config-format ${configMeta.format}">${configMeta.format.toUpperCase()}</span>
<span class="config-status ${configMeta.isWritable ? 'writable' : 'readonly'}">
${configMeta.isWritable ? '✏️ <span data-key="editable">Редактируемый</span>' : '🔒 <span data-key="readonly">Только чтение</span>'}
</span>
</div> </div>
</div> </div>
</div> </div>
@ -76,14 +72,12 @@ function renderConfigEditor() {
<div onclick="validateJSON()" class="action-btn"> <div onclick="validateJSON()" class="action-btn">
<span data-key="validate">Проверить</span> <span data-key="validate">Проверить</span>
</div> </div>
${configMeta.isWritable ? ` <div onclick="saveConfiguration()" class="action-btn">
<div onclick="saveConfiguration()" class="action-btn"> <span data-key="save_config">Сохранить</span>
<span data-key="save_config">Сохранить</span> </div>
</div> <div onclick="saveAndRestartConfiguration()" class="action-btn">
<div onclick="saveAndRestartConfiguration()" class="action-btn"> <span data-key="save_and_restart">Сохранить и перезапустить</span>
<span data-key="save_and_restart">Сохранить и перезапустить</span> </div>
</div>
` : ''}
</div> </div>
</div> </div>
</div> </div>
@ -93,7 +87,6 @@ function renderConfigEditor() {
id="config-json-textarea" id="config-json-textarea"
class="json-editor" class="json-editor"
spellcheck="false" spellcheck="false"
${configMeta.isWritable ? '' : 'readonly'}
placeholder="Загрузка конфигурации..." placeholder="Загрузка конфигурации..."
oninput="onConfigChange()" oninput="onConfigChange()"
onscroll="syncLineNumbers()" onscroll="syncLineNumbers()"

View file

@ -1542,9 +1542,6 @@ button[onclick="copyNodeKey()"]:hover {
} }
.config-header { .config-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 30px; margin-bottom: 30px;
padding: 20px; padding: 20px;
background: var(--bg-info-card); background: var(--bg-info-card);
@ -1564,13 +1561,6 @@ button[onclick="copyNodeKey()"]:hover {
font-size: 1.5em; font-size: 1.5em;
} }
.config-meta {
display: flex;
gap: 15px;
flex-wrap: wrap;
align-items: center;
}
.config-path { .config-path {
font-family: 'Courier New', monospace; font-family: 'Courier New', monospace;
background: var(--bg-nav-item); background: var(--bg-nav-item);
@ -1602,25 +1592,6 @@ button[onclick="copyNodeKey()"]:hover {
color: #7b1fa2; color: #7b1fa2;
} }
.config-status {
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8em;
font-weight: bold;
}
.config-status.writable {
background: var(--bg-success);
color: var(--text-success);
}
.config-status.readonly {
background: var(--bg-warning);
color: var(--text-warning);
}
/* Configuration Groups */ /* Configuration Groups */
.config-groups { .config-groups {
display: flex; display: flex;