feat: unify 8081 dashboard design system and views
This commit is contained in:
@@ -21,7 +21,7 @@ import ezdxf
|
||||
from ezdxf import recover
|
||||
from fastapi import FastAPI, File, Form, Header, HTTPException, Request, UploadFile
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse, HTMLResponse
|
||||
from fastapi.responses import FileResponse, HTMLResponse, Response
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from openpyxl import load_workbook
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -42,6 +42,11 @@ app.add_middleware(
|
||||
|
||||
LEGACY_STATIC_DIR = LEGACY_DIR / "static"
|
||||
INCOMING_FILES_DIR = BASE_DIR / "incoming-files"
|
||||
INCOMING_SERVED_DIR = INCOMING_FILES_DIR / "served"
|
||||
INCOMING_REFERENCE_DIR = INCOMING_FILES_DIR / "reference"
|
||||
BUSINESS_DASHBOARD_DIR = INCOMING_FILES_DIR / "사업관리대장"
|
||||
BUSINESS_DASHBOARD_WRAPPER_PATH = BUSINESS_DASHBOARD_DIR / "MH 통합 대시보드_260320.html"
|
||||
BUSINESS_DASHBOARD_THEME_CSS = BUSINESS_DASHBOARD_DIR / "MH 통합 대시보드_260320.css"
|
||||
FIXED_OFFICE_SOURCE_KEY = "technical-development-center"
|
||||
FIXED_OFFICE_CONFIGS = {
|
||||
"technical-development-center": {
|
||||
@@ -61,6 +66,8 @@ FIXED_OFFICE_CONFIGS = {
|
||||
},
|
||||
}
|
||||
_fixed_office_cache: dict[str, dict[str, object]] = {}
|
||||
_business_ledger_html_cache: str | None = None
|
||||
BUSINESS_LEDGER_DEFAULT_SOURCE_KEY = "business_ledger_default"
|
||||
AUTH_DEFAULT_PASSWORD = "1111"
|
||||
AUTH_PASSWORD_ITERATIONS = 390000
|
||||
AUTH_SESSION_HOURS = 12
|
||||
@@ -83,6 +90,86 @@ MH_HEADER_ORDER = [
|
||||
]
|
||||
|
||||
|
||||
def build_business_ledger_html() -> str:
|
||||
global _business_ledger_html_cache
|
||||
if _business_ledger_html_cache is not None:
|
||||
return _business_ledger_html_cache
|
||||
if not BUSINESS_DASHBOARD_WRAPPER_PATH.exists():
|
||||
raise FileNotFoundError("Business dashboard wrapper file not found.")
|
||||
source = BUSINESS_DASHBOARD_WRAPPER_PATH.read_text(encoding="utf-8-sig")
|
||||
match = re.search(r"const BUSINESS_HTML_B64='([^']+)';", source)
|
||||
if not match:
|
||||
raise ValueError("Embedded business ledger source was not found.")
|
||||
decoded = base64.b64decode(match.group(1)).decode("utf-8")
|
||||
head_injection = (
|
||||
'<base href="/integrations/ledger-assets/">'
|
||||
'<link rel="stylesheet" href="/integrations/ledger-assets/MH%20통합%20대시보드_260320.css">'
|
||||
'<link rel="stylesheet" href="/integrations/ledger-assets/ledger-override.css?v=20260401-03">'
|
||||
)
|
||||
html = decoded.replace("</head>", f"{head_injection}</head>", 1)
|
||||
html = html.replace("<body>", '<body class="mh-business-theme">', 1)
|
||||
html = html.replace("</body>", '<script src="/integrations/ledger-assets/ledger-override.js?v=20260401-03"></script></body>', 1)
|
||||
_business_ledger_html_cache = html
|
||||
return html
|
||||
|
||||
|
||||
def sync_default_business_ledger_source(cur) -> None:
|
||||
if not BUSINESS_DASHBOARD_DIR.exists():
|
||||
return
|
||||
candidates = [
|
||||
BUSINESS_DASHBOARD_DIR / "사업관리대장-1.xlsx",
|
||||
BUSINESS_DASHBOARD_DIR / "사업관리 대장-1.xlsx",
|
||||
BUSINESS_DASHBOARD_DIR / "사업관리대장.xlsx",
|
||||
BUSINESS_DASHBOARD_DIR / "사업관리 대장.xlsx",
|
||||
]
|
||||
source_path = next((candidate for candidate in candidates if candidate.exists()), None)
|
||||
if source_path is None:
|
||||
return
|
||||
content = source_path.read_bytes()
|
||||
content_sha256 = hashlib.sha256(content).hexdigest()
|
||||
meta_json = {
|
||||
"byte_size": len(content),
|
||||
"source_path": str(source_path),
|
||||
"synced_from": "startup",
|
||||
}
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO integration_binary_sources (
|
||||
source_key, source_name, filename, mime_type, content, content_sha256, meta_json, imported_at
|
||||
)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s::jsonb, NOW())
|
||||
ON CONFLICT (source_key) DO UPDATE
|
||||
SET source_name = EXCLUDED.source_name,
|
||||
filename = EXCLUDED.filename,
|
||||
mime_type = EXCLUDED.mime_type,
|
||||
content = EXCLUDED.content,
|
||||
content_sha256 = EXCLUDED.content_sha256,
|
||||
meta_json = EXCLUDED.meta_json,
|
||||
imported_at = NOW()
|
||||
WHERE integration_binary_sources.content_sha256 IS DISTINCT FROM EXCLUDED.content_sha256
|
||||
OR integration_binary_sources.filename IS DISTINCT FROM EXCLUDED.filename
|
||||
OR integration_binary_sources.mime_type IS DISTINCT FROM EXCLUDED.mime_type
|
||||
OR integration_binary_sources.meta_json IS DISTINCT FROM EXCLUDED.meta_json
|
||||
""",
|
||||
(
|
||||
BUSINESS_LEDGER_DEFAULT_SOURCE_KEY,
|
||||
"사업관리대장 기본 원본",
|
||||
source_path.name,
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
content,
|
||||
content_sha256,
|
||||
json.dumps(meta_json, ensure_ascii=False),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
app.mount(
|
||||
"/integrations/ledger-assets",
|
||||
StaticFiles(directory=str(BUSINESS_DASHBOARD_DIR), check_dir=False),
|
||||
name="integration-ledger-assets",
|
||||
)
|
||||
|
||||
|
||||
class MemberPayload(BaseModel):
|
||||
id: int | None = None
|
||||
name: str = Field(min_length=1)
|
||||
@@ -3910,6 +3997,7 @@ def startup() -> None:
|
||||
init_db()
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
sync_default_business_ledger_source(cur)
|
||||
sync_auth_users_from_members(cur)
|
||||
conn.commit()
|
||||
|
||||
@@ -3939,6 +4027,37 @@ def health() -> dict[str, object]:
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/integration/business-ledger-default")
|
||||
def integration_business_ledger_default() -> Response:
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT filename, mime_type, content
|
||||
FROM integration_binary_sources
|
||||
WHERE source_key = %s
|
||||
ORDER BY imported_at DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(BUSINESS_LEDGER_DEFAULT_SOURCE_KEY,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Business ledger default source not found.")
|
||||
filename = str(row["filename"] or "사업관리대장-1.xlsx")
|
||||
headers = {
|
||||
"Content-Disposition": 'inline; filename="business-ledger-default.xlsx"',
|
||||
"X-Source-Filename": "business-ledger-default.xlsx",
|
||||
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
|
||||
"Pragma": "no-cache",
|
||||
}
|
||||
return Response(
|
||||
content=bytes(row["content"]),
|
||||
media_type=str(row["mime_type"] or "application/octet-stream"),
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
|
||||
@app.post("/api/auth/login")
|
||||
def auth_login(
|
||||
request: Request,
|
||||
@@ -4500,15 +4619,34 @@ def legacy_organization_backup() -> FileResponse:
|
||||
|
||||
@app.get("/integrations/payment")
|
||||
def integration_payment() -> FileResponse:
|
||||
target = INCOMING_FILES_DIR / "payment.html"
|
||||
# 8081 phase-1 cleanup: integration HTML is served only from incoming-files/served.
|
||||
target = INCOMING_SERVED_DIR / "payment.html"
|
||||
if not target.exists():
|
||||
raise HTTPException(status_code=404, detail="Payment integration file not found.")
|
||||
return FileResponse(target)
|
||||
|
||||
|
||||
@app.get("/integrations/ledger")
|
||||
def integration_ledger() -> HTMLResponse:
|
||||
try:
|
||||
html = build_business_ledger_html()
|
||||
except FileNotFoundError:
|
||||
raise HTTPException(status_code=404, detail="Business ledger integration file not found.")
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=500, detail="Business ledger integration source is invalid.")
|
||||
return HTMLResponse(
|
||||
html,
|
||||
headers={
|
||||
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
|
||||
"Pragma": "no-cache",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/integrations/mh")
|
||||
def integration_mh() -> FileResponse:
|
||||
target = INCOMING_FILES_DIR / "mh.html"
|
||||
# Keep the served path explicit so comparison/reference copies are never picked up by accident.
|
||||
target = INCOMING_SERVED_DIR / "mh.html"
|
||||
if not target.exists():
|
||||
raise HTTPException(status_code=404, detail="MH integration file not found.")
|
||||
return FileResponse(target)
|
||||
|
||||
Reference in New Issue
Block a user