diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/admin/api_map.go | 48 | ||||
| -rw-r--r-- | internal/admin/api_rooms.go | 70 | ||||
| -rw-r--r-- | internal/admin/server.go | 39 | ||||
| -rw-r--r-- | internal/admin/static/map.js | 184 | ||||
| -rw-r--r-- | internal/admin/undo.go | 2 | ||||
| -rw-r--r-- | internal/config/config.go | 10 |
6 files changed, 175 insertions, 178 deletions
diff --git a/internal/admin/api_map.go b/internal/admin/api_map.go index 5de41a7..7298f1e 100644 --- a/internal/admin/api_map.go +++ b/internal/admin/api_map.go @@ -44,7 +44,6 @@ func (s *AdminServer) handleMap(w http.ResponseWriter, r *http.Request) { Y int `json:"y"` Name string `json:"name"` Color string `json:"color"` - Symbol string `json:"symbol"` } type LinkEntry struct { @@ -249,47 +248,22 @@ func findDisconnectedRooms(s *AdminServer, dir string, seed int, g world.RoomGri func listRoomIDsInDir(s *AdminServer, dir string) []int { base := filepath.Join(s.dataDir, "rooms") - var dirs []string - if dir == "" { - dirs = append(dirs, base) - entries, err := os.ReadDir(base) - if err != nil { - return nil - } - for _, e := range entries { - if e.IsDir() { - dirs = append(dirs, filepath.Join(base, e.Name())) - subE, err := os.ReadDir(filepath.Join(base, e.Name())) - if err != nil { - continue - } - for _, se := range subE { - if se.IsDir() { - dirs = append(dirs, filepath.Join(base, e.Name(), se.Name())) - } - } - } - } - } else { - dirs = append(dirs, filepath.Join(base, dir)) + walkRoot := base + if dir != "" { + walkRoot = filepath.Join(base, dir) } var ids []int - for _, d := range dirs { - entries, err := os.ReadDir(d) - if err != nil { - continue + filepath.WalkDir(walkRoot, func(path string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() || filepath.Ext(d.Name()) != ".yaml" { + return nil } - for _, e := range entries { - if e.IsDir() || filepath.Ext(e.Name()) != ".yaml" { - continue - } - id, err := strconv.Atoi(e.Name()[:len(e.Name())-5]) - if err != nil { - continue - } + name := d.Name() + id, convErr := strconv.Atoi(name[:len(name)-5]) + if convErr == nil && id > 0 { ids = append(ids, id) } - } + return nil + }) return ids } diff --git a/internal/admin/api_rooms.go b/internal/admin/api_rooms.go index b60aefc..a477cc3 100644 --- a/internal/admin/api_rooms.go +++ b/internal/admin/api_rooms.go @@ -106,7 +106,7 @@ func (s *AdminServer) handleRooms(w http.ResponseWriter, r *http.Request) { }) } - exits[string(opp)] = float64(*linkFrom) + exits[string(opp)] = *linkFrom } } @@ -161,7 +161,7 @@ func (s *AdminServer) handleRoomByID(w http.ResponseWriter, r *http.Request) { return } m["id"] = id - writeJSON(w, map[string]any{"room": m, "path": path, "file": path, "raw": string(data)}) + writeJSON(w, map[string]any{"room": m, "path": path, "file": path}) case http.MethodPut: path, ok := s.world.GetRoomPath(id) @@ -298,45 +298,27 @@ func (s *AdminServer) handleRoomDirs(w http.ResponseWriter, r *http.Request) { return } base := filepath.Join(s.dataDir, "rooms") - entries, err := os.ReadDir(base) - if err != nil { - writeJSON(w, []string{""}) - return - } dirs := []string{""} - for _, e := range entries { - if !e.IsDir() { - continue - } - if hasYAMLFiles(filepath.Join(base, e.Name())) { - dirs = append(dirs, e.Name()) - } - subEntries, err := os.ReadDir(filepath.Join(base, e.Name())) - if err != nil { - continue - } - for _, se := range subEntries { - if se.IsDir() && hasYAMLFiles(filepath.Join(base, e.Name(), se.Name())) { - dirs = append(dirs, e.Name()+"/"+se.Name()) + filepath.WalkDir(base, func(path string, d os.DirEntry, err error) error { + if err != nil || !d.IsDir() || path == base { + return nil + } + entries, rdErr := os.ReadDir(path) + if rdErr != nil { + return nil + } + for _, e := range entries { + if !e.IsDir() && filepath.Ext(e.Name()) == ".yaml" { + rel, _ := filepath.Rel(base, path) + dirs = append(dirs, rel) + return nil } } - } + return nil + }) writeJSON(w, dirs) } -func hasYAMLFiles(dir string) bool { - entries, err := os.ReadDir(dir) - if err != nil { - return false - } - for _, e := range entries { - if !e.IsDir() && filepath.Ext(e.Name()) == ".yaml" { - return true - } - } - return false -} - func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPost: @@ -377,10 +359,14 @@ func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) { cA, okA := grid.Coord[body.From] cB, okB := grid.Coord[body.To] - if !okA || !okB || cA[2] != cB[2] { - writeJSON(w, map[string]any{"error": "rooms must be on same z-level"}) - return - } + if !okA || !okB { + writeJSON(w, map[string]any{"error": "one or both rooms are not reachable from the seed room"}) + return + } + if cA[2] != cB[2] { + writeJSON(w, map[string]any{"error": "rooms must be on same z-level"}) + return + } dx, dy := cB[0]-cA[0], cB[1]-cA[1] for d, delta := range world.DirectionDeltas3D { if delta[0] == dx && delta[1] == dy && delta[2] == 0 { @@ -510,12 +496,6 @@ func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) { writeJSON(w, map[string]any{"error": "both rooms not found"}) return } - if (errA != nil && roomA == nil) { - roomA = nil - } - if (errB != nil && roomB == nil) { - roomB = nil - } var dir, oppDir world.ExitDir if roomA != nil { diff --git a/internal/admin/server.go b/internal/admin/server.go index 8facc4a..823d7ba 100644 --- a/internal/admin/server.go +++ b/internal/admin/server.go @@ -34,6 +34,7 @@ var embedded embed.FS type AdminServer struct { cfg *config.Config + useTLS bool accountStore *player.AccountStore world *world.World itemStore *item.ItemStore @@ -46,7 +47,7 @@ type AdminServer struct { cookieSecret []byte } -func NewServer(cfg *config.Config, accountStore *player.AccountStore, w *world.World, is *item.ItemStore, os *object.ObjectStore, ms *world.MobStore, dataDir string) (*AdminServer, error) { +func NewServer(cfg *config.Config, useTLS bool, accountStore *player.AccountStore, w *world.World, is *item.ItemStore, os *object.ObjectStore, ms *world.MobStore, dataDir string) (*AdminServer, error) { secret := make([]byte, 32) if _, err := rand.Read(secret); err != nil { return nil, fmt.Errorf("cookie secret: %w", err) @@ -62,13 +63,24 @@ func NewServer(cfg *config.Config, accountStore *player.AccountStore, w *world.W return nil, fmt.Errorf("parse templates: %w", err) } - cert, err := loadOrGenerateCert(cfg) - if err != nil { - return nil, fmt.Errorf("admin cert: %w", err) + var port int + if useTLS { + port = cfg.AdminHTTPS.Port + } else { + port = cfg.AdminHTTP.Port + } + + var cert tls.Certificate + if useTLS { + cert, err = loadOrGenerateCert(cfg) + if err != nil { + return nil, fmt.Errorf("admin cert: %w", err) + } } s := &AdminServer{ cfg: cfg, + useTLS: useTLS, accountStore: accountStore, world: w, itemStore: is, @@ -159,12 +171,14 @@ func NewServer(cfg *config.Config, accountStore *player.AccountStore, w *world.W }) s.httpServer = &http.Server{ - Addr: fmt.Sprintf(":%d", cfg.AdminHTTPS.Port), + Addr: fmt.Sprintf(":%d", port), Handler: mux, - TLSConfig: &tls.Config{ + } + if useTLS { + s.httpServer.TLSConfig = &tls.Config{ Certificates: []tls.Certificate{cert}, MinVersion: tls.VersionTLS12, - }, + } } return s, nil @@ -175,8 +189,11 @@ func (s *AdminServer) ListenAndServe() error { if err != nil { return fmt.Errorf("admin listen: %w", err) } - tlsLn := tls.NewListener(ln, s.httpServer.TLSConfig) - return s.httpServer.Serve(tlsLn) + if s.useTLS { + tlsLn := tls.NewListener(ln, s.httpServer.TLSConfig) + return s.httpServer.Serve(tlsLn) + } + return s.httpServer.Serve(ln) } func (s *AdminServer) authMiddleware(next http.Handler) http.Handler { @@ -228,7 +245,7 @@ func (s *AdminServer) setAuthCookie(w http.ResponseWriter, account string) { Value: account + ":" + sig, Path: "/", HttpOnly: true, - Secure: true, + Secure: s.useTLS, SameSite: http.SameSiteStrictMode, MaxAge: 86400, }) @@ -270,7 +287,7 @@ func (s *AdminServer) handleLogout(w http.ResponseWriter, r *http.Request) { Path: "/", MaxAge: -1, HttpOnly: true, - Secure: true, + Secure: s.useTLS, }) http.Redirect(w, r, "/login", http.StatusFound) } diff --git a/internal/admin/static/map.js b/internal/admin/static/map.js index cd53bed..85364d5 100644 --- a/internal/admin/static/map.js +++ b/internal/admin/static/map.js @@ -47,7 +47,7 @@ function loadMap() { if (currentDir) url += '&dir=' + encodeURIComponent(currentDir); var wasDirChange = dirChangedByUser; dirChangedByUser = false; - API.get(url).then(function(data) { + return API.get(url).then(function(data) { mapData = data; if (!currentDir && data.dir) { currentDir = data.dir; @@ -64,11 +64,11 @@ function loadMap() { } function blendColors(colorA, colorB) { + if (!colorA && !colorB) return '#666666'; + if (!colorB) return xtermToCss(parseXtermIdx(colorA)); + if (!colorA) return xtermToCss(parseXtermIdx(colorB)); var idxA = parseXtermIdx(colorA); var idxB = parseXtermIdx(colorB); - if (colorA && !colorB) return xtermToCss(idxA); - if (!colorA && colorB) return xtermToCss(idxB); - if (!colorA && !colorB) return '#666666'; var rgbA = xtermToRGB(idxA); var rgbB = xtermToRGB(idxB); var r = Math.round((rgbA[0] + rgbB[0]) / 2); @@ -90,17 +90,21 @@ function renderMap(data) { var W = container.clientWidth; var H = container.clientHeight; - roomMap = {}; - if (data.rooms) data.rooms.forEach(function(r) { roomMap[r.id] = r; }); - - occupied = {}; - if (data.rooms) data.rooms.forEach(function(r) { occupied[r.x+','+r.y] = r.id; }); - + roomMap = {}; occupied = {}; upTarget = {}; downTarget = {}; - if (data.upLinks) data.upLinks.forEach(function(l) { upTarget[l.from] = l.to; }); - if (data.downLinks) data.downLinks.forEach(function(l) { downTarget[l.from] = l.to; }); + var hasUp = {}, hasDown = {}; + + if (data.rooms) data.rooms.forEach(function(r) { + roomMap[r.id] = r; + occupied[r.x+','+r.y] = r.id; + }); + if (data.upLinks) data.upLinks.forEach(function(l) { + upTarget[l.from] = l.to; hasUp[l.from] = true; + }); + if (data.downLinks) data.downLinks.forEach(function(l) { + downTarget[l.from] = l.to; hasDown[l.from] = true; + }); - var svgNS = 'http://www.w3.org/2000/svg'; var g = ''; if (data.links) { @@ -109,16 +113,11 @@ function renderMap(data) { if (!fr || !tr) return; var x1 = fr.x * CELL + CELL/2, y1 = fr.y * CELL + CELL/2; var x2 = tr.x * CELL + CELL/2, y2 = tr.y * CELL + CELL/2; - var c = blendColors(fr.color, tr.color); - c = c.replace('#',''); + var c = blendColors(fr.color, tr.color).replace('#',''); g += '<line x1="'+x1+'" y1="'+y1+'" x2="'+x2+'" y2="'+y2+'" stroke="#'+c+'" stroke-width="6" opacity="0.55"'+ (l.bidirectional ? '' : ' stroke-dasharray="4,3"')+' vector-effect="non-scaling-stroke"/>'; }); } - var hasUp = {}, hasDown = {}; - if (data.upLinks) data.upLinks.forEach(function(l) { hasUp[l.from] = true; }); - if (data.downLinks) data.downLinks.forEach(function(l) { hasDown[l.from] = true; }); - var roomHTML = ''; if (data.rooms) { data.rooms.forEach(function(r) { @@ -126,7 +125,7 @@ function renderMap(data) { var fill = resolveColor(r.color); var txtColor = hexLuminance(fill) > 0.35 ? '#111' : '#fff'; var sel = selectedRoom === r.id; - roomHTML += '<rect class="rm" x="'+x+'" y="'+y+'" width="'+w+'" height="'+h+'" rx="5" fill="'+fill+'" stroke="'+(sel?'#fff':fill)+'" stroke-width="'+(sel?3:1.5)+'" data-id="'+r.id+'" data-name="'+esc(r.name)+'"/>'; + roomHTML += '<rect class="rm" x="'+x+'" y="'+y+'" width="'+w+'" height="'+h+'" rx="5" fill="'+fill+'" stroke="'+(sel?'#fff':fill)+'" stroke-width="'+(sel?3:1.5)+'" data-id="'+r.id+'"/>'; var dimColor = hexLuminance(fill) > 0.35 ? 'rgba(0,0,0,0.45)' : 'rgba(255,255,255,0.4)'; roomHTML += '<text x="'+(x+w/2)+'" y="'+(y+14)+'" text-anchor="middle" font-size="8" fill="'+dimColor+'" font-family="monospace" pointer-events="none">#'+r.id+'</text>'; @@ -167,7 +166,7 @@ function renderMap(data) { svg.setAttribute('height', H); svg.innerHTML = '<g>' + innerHTML + '</g>'; - svg.addEventListener('contextmenu', function(e) { e.preventDefault(); }); + svg.oncontextmenu = function(e) { e.preventDefault(); }; svg.onmousedown = function(e) { var onRoom = e.target.classList.contains('rm'); @@ -180,9 +179,7 @@ function renderMap(data) { var dz = parseInt(e.target.getAttribute('data-dz')); var tid = parseInt(e.target.getAttribute('data-target')); currentZ += dz; - selectedRoom = tid; - loadMap(); - selectRoom(tid); + loadMap().then(function() { selectRoom(tid); }); return; } @@ -292,7 +289,6 @@ function renderMap(data) { if (scale > 5) scale = 5; updateView(); }; - } function wrapText(name, cx, cy, maxWidth, color) { @@ -338,8 +334,24 @@ function updateView() { function selectRoom(id) { if (saveTimer) { clearTimeout(saveTimer); saveTimer = null; } + var prev = selectedRoom; selectedRoom = id; - loadMap(); + + if (mapData && roomMap) { + if (prev && prev !== id) { + var prevEl = document.querySelector('.rm[data-id="'+prev+'"]'); + if (prevEl) { + prevEl.setAttribute('stroke', prevEl.getAttribute('fill') || '#3a3a6a'); + prevEl.setAttribute('stroke-width', '1.5'); + } + } + var el = document.querySelector('.rm[data-id="'+id+'"]'); + if (el) { + el.setAttribute('stroke', '#fff'); + el.setAttribute('stroke-width', '3'); + } + } + API.get('/api/rooms/' + id).then(function(data) { renderSidePanel(data); }).catch(function(e) { @@ -443,9 +455,7 @@ function renderHazardTab(r) { } function searchHazards(input) { - searchAndShow(input, 'hazards', function(r) { - addHazard(r.id); - }); + searchAndShow(input, 'hazards', function(r) { addHazard(r.id); }); } function addHazard(id) { if (!currentRoomData || !currentRoomData.room) return; @@ -597,7 +607,6 @@ function renderItemsTab(spawns) { function renderExitsSection(r) { var exits = r.exits || {}; var exitDirs = ['north','south','east','west','northeast','northwest','southeast','southwest','up','down']; - var dirAliases = {n:'north',s:'south',e:'east',w:'west',ne:'northeast',nw:'northwest',se:'southeast',sw:'southwest',u:'up',d:'down'}; var h = '<div class="exit-section"><h3>Exits</h3>'; exitDirs.forEach(function(d) { var exit = exits[d]; @@ -676,7 +685,6 @@ function editExitCondition(dir) { menu._condClauses = parsed.clauses; menu._condMode = parsed.mode; - // Fix: mode is always 'all' for our editor, but store what was parsed renderConditionClauses(menu); } @@ -801,7 +809,6 @@ function saveExitCondition() { exit.set_flags = parseFlagInput(flagsStr ? flagsStr.value : ''); exit.set_player_flags = parseFlagInput(pflagsStr ? pflagsStr.value : ''); - // Update clause data from DOM before saving var clauseEls = menu.querySelectorAll('.cond-clause'); clauseEls.forEach(function(el) { updateClauseData(el); }); @@ -868,8 +875,7 @@ function addExit() { API.post('/api/rooms/link', {from: selectedRoom, to: target, dir: dir}).then(function() { notify('Exit added: ' + dir + ' #' + target, 'success'); updateUndoBar(); - loadMap(); - selectRoom(selectedRoom); + loadMap().then(function() { selectRoom(selectedRoom); }); }).catch(function(e) { notify('Add exit failed: ' + extractError(e), 'error'); }); } @@ -887,8 +893,7 @@ function deleteExit(dir, target) { }).then(function() { notify('Removed exit ' + dir + ' to #' + target, 'success'); updateUndoBar(); - loadMap(); - selectRoom(selectedRoom); + loadMap().then(function() { selectRoom(selectedRoom); }); }).catch(function(e) { notify('Delete exit failed: ' + extractError(e), 'error'); }); } @@ -997,9 +1002,7 @@ function addRefObject(id) { } function searchRefObjects(input) { - searchAndShow(input, 'objects', function(r) { - addRefObject(r.id); - }); + searchAndShow(input, 'objects', function(r) { addRefObject(r.id); }); } function removeSpawn(i) { @@ -1029,9 +1032,7 @@ function addSpawn(id) { } function searchItemSpawns(input) { - searchAndShow(input, 'items', function(r) { - addSpawn(r.id); - }); + searchAndShow(input, 'items', function(r) { addSpawn(r.id); }); } function removeMob(i) { @@ -1049,9 +1050,7 @@ function addMob(id) { } function searchMobs(input) { - searchAndShow(input, 'mobs', function(r) { - addMob(r.id); - }); + searchAndShow(input, 'mobs', function(r) { addMob(r.id); }); } function searchAndShow(input, type, onSelect) { @@ -1112,20 +1111,14 @@ function saveNow() { doSavePanel(); } -function doSavePanel() { +async function doSavePanel() { var id = selectedRoom; if (!id) return; - var save = function(r) { - API.put('/api/rooms/' + id, r).then(function() { - notify('Room #' + id + ' saved', 'success'); - updateUndoBar(); - loadMap(); - }).catch(function(e) { notify('Save failed: '+e.message, 'error'); }); - }; - - API.get('/api/rooms/' + id).then(function(data) { + try { + var data = await API.get('/api/rooms/' + id); var r = data.room || {}; r.id = id; + var el = $('#roomName'); if (el) r.name = el.value; el = $('#roomColor'); if (el) r.color = el.value; el = $('#roomDesc'); if (el && el.value) r.description = [{text: el.value}]; @@ -1140,29 +1133,29 @@ function doSavePanel() { if (r.block_transport === undefined) r.block_transport = currentRoomData.room.block_transport; } - var el = $('#roomID'); var newID = el ? parseInt(el.value) || id : id; + el = $('#roomID'); + var newID = el ? parseInt(el.value) || id : id; var dirEl = $('#roomDir'); var newDir = dirEl ? dirEl.value : null; var currentDir = (data.file || '').replace(/^data\/rooms\/?/, '').split('/')[0] || ''; if (dirEl && newDir !== currentDir) { - API.post('/api/rooms/move', {id: id, dir: newDir}).then(function() { - if (newID !== id) { - API.post('/api/rooms/rename', {id: id, new_id: newID}).then(function() { - selectRoom(newID); - }).catch(function(e) { notify('Rename failed: '+e.message, 'error'); save(r); }); - } else { save(r); } - }).catch(function(e) { notify('Move failed: '+e.message, 'error'); save(r); }); - } else if (newID !== id) { - API.post('/api/rooms/rename', {id: id, new_id: newID}).then(function() { - selectRoom(newID); - }).catch(function(e) { - notify('Rename failed: '+e.message, 'error'); - var rid = $('#roomID'); if (rid) rid.value = id; - save(r); - }); - } else { save(r); } - }).catch(function(e) { notify('Save failed: '+e.message, 'error'); }); + await API.post('/api/rooms/move', {id: id, dir: newDir}); + } + if (newID !== id) { + await API.post('/api/rooms/rename', {id: id, new_id: newID}); + selectedRoom = newID; + id = newID; + } + + await API.put('/api/rooms/' + id, r); + notify('Room #' + id + ' saved', 'success'); + updateUndoBar(); + await loadMap(); + selectRoom(id); + } catch (e) { + notify('Save failed: ' + extractError(e), 'error'); + } } function deleteRoom(id) { @@ -1175,6 +1168,18 @@ function deleteRoom(id) { if (l.to === id && l.from !== id) neighbors.push(l.from); }); } + if (mapData.upLinks) { + mapData.upLinks.forEach(function(l) { + if (l.from === id && l.to !== id) neighbors.push(l.to); + if (l.to === id && l.from !== id) neighbors.push(l.from); + }); + } + if (mapData.downLinks) { + mapData.downLinks.forEach(function(l) { + if (l.from === id && l.to !== id) neighbors.push(l.to); + if (l.to === id && l.from !== id) neighbors.push(l.from); + }); + } if (neighbors.length > 1) { var remaining = mapData.rooms.filter(function(r) { return r.id !== id; }); var adj = {}; @@ -1186,6 +1191,20 @@ function deleteRoom(id) { if (adj[l.to]) adj[l.to].push(l.from); }); } + if (mapData.upLinks) { + mapData.upLinks.forEach(function(l) { + if (l.from === id || l.to === id) return; + if (adj[l.from]) adj[l.from].push(l.to); + if (adj[l.to]) adj[l.to].push(l.from); + }); + } + if (mapData.downLinks) { + mapData.downLinks.forEach(function(l) { + if (l.from === id || l.to === id) return; + if (adj[l.from]) adj[l.from].push(l.to); + if (adj[l.to]) adj[l.to].push(l.from); + }); + } if (remaining.length > 0) { var visited = {}, queue = [remaining[0].id]; visited[remaining[0].id] = true; @@ -1219,7 +1238,7 @@ function createRoom(fromID, dir) { var createdId = (resp && resp.room && resp.room.id) || newID; notify('Room #' + createdId + ' created', 'success'); updateUndoBar(); - selectRoom(createdId); + loadMap().then(function() { selectRoom(createdId); }); }).catch(function(e) { notify('Create failed: ' + e.message, 'error'); }); }); } @@ -1323,7 +1342,7 @@ function applyGhostLinkHighlight(fromRoom, gx, gy) { fromEl.setAttribute('stroke', '#0ff'); fromEl.setAttribute('stroke-width', '3'); - var g = document.querySelector('#mapSvg g'); + var g = document.querySelector('#mapSvg g'); if (!g) return; var svgNS = 'http://www.w3.org/2000/svg'; var line = document.createElementNS(svgNS, 'line'); @@ -1353,7 +1372,7 @@ function applyLinkHighlight(fromId, toId) { toEl.setAttribute('stroke', '#f0f'); toEl.setAttribute('stroke-width', '3'); - var g = document.querySelector('#mapSvg g'); + var g = document.querySelector('#mapSvg g'); if (!g) return; var svgNS = 'http://www.w3.org/2000/svg'; var line = document.createElementNS(svgNS, 'line'); @@ -1393,12 +1412,9 @@ function createLink(fromId, toId) { API.post('/api/rooms/link', {from: fromId, to: toId}).then(function() { notify('Linked rooms #'+fromId+' '+toId, 'success'); updateUndoBar(); - loadMap(); - if (selectedRoom) { - API.get('/api/rooms/' + selectedRoom).then(function(data) { - renderSidePanel(data); - }); - } + loadMap().then(function() { + if (selectedRoom) selectRoom(selectedRoom); + }); }).catch(function(e) { notify('Link failed: ' + e.message, 'error'); }); @@ -1441,7 +1457,7 @@ function applyDelHighlight(fromId, toId) { toEl.setAttribute('stroke', '#f55'); toEl.setAttribute('stroke-width', '3'); - var g = document.querySelector('#mapSvg g'); + var g = document.querySelector('#mapSvg g'); if (!g) return; var svgNS = 'http://www.w3.org/2000/svg'; var line = document.createElementNS(svgNS, 'line'); @@ -1587,7 +1603,7 @@ function escAttr(s) { return String(s).replace(/&/g,'&').replace(/"/g,'"'); } -window.refreshPage = function() { loadMap(); if (selectedRoom) selectRoom(selectedRoom); }; +window.refreshPage = function() { loadMap().then(function() { if (selectedRoom) selectRoom(selectedRoom); }); }; function loadRoomDirs(cb) { API.get('/api/room-dirs').then(function(dirs) { diff --git a/internal/admin/undo.go b/internal/admin/undo.go index 39e8c4e..bdd16e6 100644 --- a/internal/admin/undo.go +++ b/internal/admin/undo.go @@ -42,7 +42,7 @@ func NewUndoStack(dataDir string) *UndoStack { us := &UndoStack{ dataDir: dataDir, maxSize: 100, - filePath: filepath.Join(dataDir, ".admin_history.yaml"), + filePath: filepath.Join(dataDir, ".admin_history.json"), } us.load() return us diff --git a/internal/config/config.go b/internal/config/config.go index 40f4891..9505cbb 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -16,6 +16,7 @@ type Config struct { TelnetTLS TelnetTLSConfig `yaml:"telnet_tls"` HTTP HTTPConfig `yaml:"http"` HTTPS HTTPSConfig `yaml:"https"` + AdminHTTP AdminHTTPConfig `yaml:"admin_http"` AdminHTTPS AdminHTTPSConfig `yaml:"admin_https"` } @@ -99,6 +100,11 @@ type HTTPSConfig struct { Port int `yaml:"port"` } +type AdminHTTPConfig struct { + Enabled bool `yaml:"enabled"` + Port int `yaml:"port"` +} + type AdminHTTPSConfig struct { Enabled bool `yaml:"enabled"` Port int `yaml:"port"` @@ -133,6 +139,10 @@ func Default() *Config { Enabled: false, Port: 8443, }, + AdminHTTP: AdminHTTPConfig{ + Enabled: false, + Port: 9091, + }, AdminHTTPS: AdminHTTPSConfig{ Enabled: false, Port: 9090, |
