mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2025-08-24 16:05:07 +03:00
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:
parent
8e44b57879
commit
a094c423de
4 changed files with 26 additions and 118 deletions
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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()"
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue