fix: copy button HTTP fallback, reset-admin-password in Docker, scripts volume
- CellNetwork.jsx CopyButton: use execCommand fallback when clipboard API is unavailable (HTTP non-localhost context) - Makefile reset-admin-password: run inside cell-api container via docker exec so bcrypt and all deps are available without host installation - docker-compose.yml: mount ./scripts:/app/scripts:ro in cell-api so the reset script is accessible inside the container - scripts/reset_admin_password.py: auto-detect API module path and data dir so the script works in both host (api/ sibling) and container (/app) layouts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -284,7 +284,7 @@ show-admin-password:
|
|||||||
@sudo python3 scripts/reset_admin_password.py --show
|
@sudo python3 scripts/reset_admin_password.py --show
|
||||||
|
|
||||||
reset-admin-password:
|
reset-admin-password:
|
||||||
@sudo python3 scripts/reset_admin_password.py --generate
|
@docker exec cell-api python3 /app/scripts/reset_admin_password.py --generate
|
||||||
|
|
||||||
|
|
||||||
# ── Network / peers ───────────────────────────────────────────────────────────
|
# ── Network / peers ───────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ services:
|
|||||||
- /var/run/docker.sock:/var/run/docker.sock
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
- ./.env:/app/.env.compose
|
- ./.env:/app/.env.compose
|
||||||
- ./docker-compose.yml:/app/docker-compose.yml:ro
|
- ./docker-compose.yml:/app/docker-compose.yml:ro
|
||||||
|
- ./scripts:/app/scripts:ro
|
||||||
pid: host
|
pid: host
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -11,11 +11,24 @@ import os
|
|||||||
import secrets
|
import secrets
|
||||||
import string
|
import string
|
||||||
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'api'))
|
_here = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
# Find auth_manager: host layout has api/ sibling, container has it one level up at /app
|
||||||
|
for _api_path in [os.path.join(_here, '..', 'api'), os.path.join(_here, '..'), '/app']:
|
||||||
|
if os.path.isfile(os.path.join(_api_path, 'auth_manager.py')):
|
||||||
|
sys.path.insert(0, os.path.normpath(_api_path))
|
||||||
|
break
|
||||||
|
|
||||||
ROOT = os.path.join(os.path.dirname(__file__), '..')
|
ROOT = os.path.normpath(os.path.join(_here, '..'))
|
||||||
INIT_PW_FILE = os.path.normpath(os.path.join(ROOT, 'data', 'api', '.admin_initial_password'))
|
# data dir: host uses ROOT/data/api, container mounts data/api at ROOT/data
|
||||||
TEST_PW_FILE = os.path.normpath(os.path.join(ROOT, 'data', 'api', '.test_admin_pass'))
|
for _data in [os.path.join(ROOT, 'data', 'api'), os.path.join(ROOT, 'data'), '/app/data']:
|
||||||
|
if os.path.isdir(_data):
|
||||||
|
_DATA_DIR = _data
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
_DATA_DIR = os.path.join(ROOT, 'data', 'api')
|
||||||
|
|
||||||
|
INIT_PW_FILE = os.path.join(_DATA_DIR, '.admin_initial_password')
|
||||||
|
TEST_PW_FILE = os.path.join(_DATA_DIR, '.test_admin_pass')
|
||||||
|
|
||||||
|
|
||||||
def _generate_password(length: int = 20) -> str:
|
def _generate_password(length: int = 20) -> str:
|
||||||
@@ -32,7 +45,7 @@ def _generate_password(length: int = 20) -> str:
|
|||||||
|
|
||||||
def _set_password(new_password: str) -> None:
|
def _set_password(new_password: str) -> None:
|
||||||
from auth_manager import AuthManager
|
from auth_manager import AuthManager
|
||||||
data_dir = os.path.normpath(os.path.join(ROOT, 'data', 'api'))
|
data_dir = _DATA_DIR
|
||||||
os.makedirs(data_dir, exist_ok=True)
|
os.makedirs(data_dir, exist_ok=True)
|
||||||
mgr = AuthManager(data_dir=data_dir, config_dir='/tmp')
|
mgr = AuthManager(data_dir=data_dir, config_dir='/tmp')
|
||||||
if mgr.set_password_admin('admin', new_password):
|
if mgr.set_password_admin('admin', new_password):
|
||||||
|
|||||||
@@ -23,7 +23,17 @@ const SERVICE_DEFS = [
|
|||||||
function CopyButton({ text, small }) {
|
function CopyButton({ text, small }) {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const copy = () => {
|
const copy = () => {
|
||||||
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
navigator.clipboard.writeText(text);
|
navigator.clipboard.writeText(text);
|
||||||
|
} else {
|
||||||
|
const el = document.createElement('textarea');
|
||||||
|
el.value = text;
|
||||||
|
el.style.cssText = 'position:fixed;opacity:0;top:0;left:0';
|
||||||
|
document.body.appendChild(el);
|
||||||
|
el.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(el);
|
||||||
|
}
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 1500);
|
setTimeout(() => setCopied(false), 1500);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user