guardia-docs/gen_export_docs.py
DESKTOP-TKLFCPRython 2bd7d876cc refactor: 101.79.17.164 → zioinfo.co.kr 전체 도메인 변환 + Manager UI 배포
- 37개 파일 IP → zioinfo.co.kr 치환 (소스/매뉴얼/설정/하네스)
- Manager DrConsole/NetworkConsole/CsapConsole 빌드 + /var/www/manager/ 배포
- 테스트: Manager HTTP 200, ITSM 신규 API 7개 전체 200

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 10:09:17 +09:00

231 lines
14 KiB
Python

#!/usr/bin/env python3
"""Export/Import 가이드 PDF + PPTX 생성"""
from pathlib import Path
OUT_DIR = Path(__file__).parent
def gen_pdf(out):
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm
from reportlab.lib.enums import TA_CENTER
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable, PageBreak
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import os
FONT_DIRS = ["C:/Windows/Fonts","/usr/share/fonts/truetype/noto","/usr/share/fonts/truetype/dejavu"]
font = "Helvetica"
for fn,alias in [("malgun.ttf","Malgun"),("NanumGothic.ttf","NanumGothic"),("DejaVuSans.ttf","DejaVuSans")]:
for d in FONT_DIRS:
fp = os.path.join(d,fn)
if os.path.exists(fp):
try: pdfmetrics.registerFont(TTFont(alias,fp)); font=alias; break
except: pass
if font != "Helvetica": break
BRAND=colors.HexColor("#1a3a6b"); ACCENT=colors.HexColor("#4f6ef7")
GREEN=colors.HexColor("#22c55e"); GRAY=colors.HexColor("#f0f2f5")
MUTED=colors.HexColor("#64748b")
doc = SimpleDocTemplate(out,pagesize=A4,leftMargin=20*mm,rightMargin=20*mm,topMargin=20*mm,bottomMargin=20*mm)
W = A4[0]-40*mm
styles = getSampleStyleSheet()
def sty(name,**kw):
kw.setdefault("fontName",font)
return ParagraphStyle(name,parent=styles["Normal"],**kw)
def hr(c=None): return HRFlowable(width="100%",thickness=1,color=c or ACCENT,spaceAfter=4,spaceBefore=4)
def tbl(data,cws=None,hdr=True):
t=Table(data,colWidths=cws)
base=[("FONTNAME",(0,0),(-1,-1),font),("FONTSIZE",(0,0),(-1,-1),9),
("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white,GRAY]),
("GRID",(0,0),(-1,-1),.5,colors.HexColor("#e2e8f0")),
("VALIGN",(0,0),(-1,-1),"MIDDLE"),
("LEFTPADDING",(0,0),(-1,-1),6),("RIGHTPADDING",(0,0),(-1,-1),6),
("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5)]
if hdr:base+=[("BACKGROUND",(0,0),(-1,0),BRAND),("TEXTCOLOR",(0,0),(-1,0),colors.white),
("FONTNAME",(0,0),(-1,0),font)]
t.setStyle(TableStyle(base)); return t
S={"h1":sty("h1",fontSize=18,textColor=BRAND,spaceBefore=16,spaceAfter=8,leading=24),
"h2":sty("h2",fontSize=13,textColor=ACCENT,spaceBefore=10,spaceAfter=5,leading=18),
"body":sty("body",fontSize=10,textColor=colors.HexColor("#1e293b"),leading=16,spaceAfter=4),
"code":sty("code",fontName="Courier",fontSize=9,backColor=GRAY,
textColor=colors.HexColor("#1d4ed8"),leading=14,leftIndent=10,spaceBefore=3,spaceAfter=3),
"note":sty("note",fontSize=9,textColor=MUTED,leftIndent=10,leading=14,spaceAfter=3)}
story=[]
# 표지
cover=Table([[Paragraph("GUARDiA 폐쇄망 ↔ 개방망",sty("ct",fontSize=26,textColor=colors.white,alignment=TA_CENTER,leading=34))],
[Paragraph("데이터 Export / Import 가이드",sty("cs",fontSize=16,textColor=colors.HexColor("#aac4e8"),alignment=TA_CENTER))],
[Paragraph("v1.0.0 | 2026-05-30 | (주)지오정보기술",sty("cm",fontSize=10,textColor=colors.HexColor("#7c85a8"),alignment=TA_CENTER))]],
colWidths=[W])
cover.setStyle(TableStyle([("BACKGROUND",(0,0),(-1,-1),BRAND),
("TOPPADDING",(0,0),(-1,-1),35),("BOTTOMPADDING",(0,0),(-1,-1),35),
("LEFTPADDING",(0,0),(-1,-1),20),("RIGHTPADDING",(0,0),(-1,-1),20)]))
story+=[Spacer(1,30*mm),cover,PageBreak()]
story+=[Paragraph("1. 개요",S["h1"]),hr(),
Paragraph("폐쇄망 GUARDiA ITSM의 데이터를 개방망 GUARDiA Manager로 안전하게 이관합니다. "
"번들 파일에 HMAC-SHA256 서명을 포함하여 위변조를 방지하며, 민감 정보는 자동 마스킹됩니다.",S["body"]),
Spacer(1,6),Paragraph("1-1. 보안 특징",S["h2"]),
tbl([["특징","내용"],["HMAC-SHA256 서명","번들 ZIP 위변조 방지"],
["민감 정보 마스킹","IP 주소, SSH 비밀번호 → '****' 처리"],
["Dry Run 모드","실제 저장 전 사전 검증"],
["중복 방지","sr_id 기준 중복 SKIP"]],cws=[55*mm,110*mm]),
Spacer(1,10),Paragraph("2. API 엔드포인트",S["h1"]),hr(),
tbl([["메서드","경로","설명"],
["GET","/api/export-import/export/bundle","전체 번들 ZIP (권장)"],
["GET","/api/export-import/export/sr","SR 목록 JSON"],
["GET","/api/export-import/export/cmdb","CMDB 서버 자산 JSON"],
["GET","/api/export-import/export/institutions","기관 목록 JSON"],
["GET","/api/export-import/export/audit","감사 로그 JSON"],
["POST","/api/export-import/import/bundle","번들 ZIP Import"],
["POST","/api/export-import/import/sr","SR JSON Import"]],
cws=[18*mm,75*mm,72*mm]),
PageBreak(),
Paragraph("3. 연동 흐름",S["h1"]),hr(),
tbl([["단계","작업","비고"],
["1","폐쇄망 서버에서 번들 Export","GET /export/bundle → ZIP 다운로드"],
["2","USB/보안매체로 ZIP 이동","Air Gap 환경 고려"],
["3","Manager UI에서 파일 업로드","http://zioinfo.co.kr:8090/export-import"],
["4","Dry Run 검증 실행","HMAC 서명 + 데이터 카운트 확인"],
["5","Import 실행 (dry_run=false)","중복 sr_id 자동 SKIP"]],cws=[12*mm,75*mm,78*mm]),
Spacer(1,10),Paragraph("4. 테스트 결과 (7/7 PASS)",S["h1"]),hr(),
tbl([["#","테스트 항목","결과"],
["T1","SR Export (5건)","PASS"],
["T2","CMDB Export (6건)","PASS"],
["T3","기관 Export (3건)","PASS"],
["T4","감사 로그 Export (5건)","PASS"],
["T5","번들 ZIP Export (HMAC 서명)","PASS"],
["T6","SR Import dry_run","PASS"],
["T7","Manager UI /export-import","PASS"]],cws=[12*mm,90*mm,63*mm]),
Spacer(1,6),
Paragraph("버그 수정: date 타입 JSON 직렬화 오류 → isoformat() 처리 완료",
sty("fix",fontSize=10,textColor=GREEN,fontName=font)),
Spacer(1,12),
hr(colors.HexColor("#e2e8f0")),
Paragraph("GUARDiA ITSM v2.0.0 | (주)지오정보기술 | 2026-05-30",
sty("foot",fontSize=8,textColor=MUTED,alignment=TA_CENTER,fontName=font))]
doc.build(story)
print(f"PDF: {out}")
def gen_pptx(out):
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
W,H=Inches(13.33),Inches(7.5)
prs=Presentation(); prs.slide_width=W; prs.slide_height=H
BRAND=RGBColor(0x1a,0x3a,0x6b); ACCENT=RGBColor(0x4f,0x6e,0xf7)
WHITE=RGBColor(0xff,0xff,0xff); GRAY=RGBColor(0xf0,0xf2,0xf5)
GREEN=RGBColor(0x22,0xc5,0x5e); MUTED=RGBColor(0x64,0x74,0x8b)
DARK=RGBColor(0x1e,0x29,0x3b)
def blank(): return prs.slides.add_slide(prs.slide_layouts[6])
def rect(sl,x,y,w,h,fill=None):
s=sl.shapes.add_shape(1,x,y,w,h)
if fill: s.fill.solid(); s.fill.fore_color.rgb=fill
else: s.fill.background()
s.line.fill.background(); return s
def text(sl,t,x,y,w,h,sz=14,bold=False,color=DARK,align=PP_ALIGN.LEFT):
tb=sl.shapes.add_textbox(x,y,w,h); tf=tb.text_frame; tf.word_wrap=True
p=tf.paragraphs[0]; p.alignment=align; r=p.add_run(); r.text=t
r.font.size=Pt(sz); r.font.bold=bold; r.font.color.rgb=color
def tbl_sl(sl,hdrs,rows,x,y,w,h,cws=None):
t=sl.shapes.add_table(len(rows)+1,len(hdrs),x,y,w,h).table
if cws:
for i,cw in enumerate(cws): t.columns[i].width=cw
def cell(c,v,bg=None,clr=DARK,bold=False):
c.text=str(v); c.text_frame.paragraphs[0].font.size=Pt(9)
c.text_frame.paragraphs[0].font.bold=bold
c.text_frame.paragraphs[0].font.color.rgb=clr
if bg: c.fill.solid(); c.fill.fore_color.rgb=bg
for j,h in enumerate(hdrs): cell(t.cell(0,j),h,bg=BRAND,clr=WHITE,bold=True)
for i,row in enumerate(rows):
bg=GRAY if i%2==0 else WHITE
for j,v in enumerate(row): cell(t.cell(i+1,j),v,bg=bg)
# S1 표지
s=blank()
rect(s,0,0,W,H,fill=BRAND)
rect(s,Inches(.5),Inches(1.2),Inches(12.33),Inches(4.0),fill=RGBColor(0x25,0x4a,0x80))
text(s,"GUARDiA 폐쇄망 ↔ 개방망",Inches(.8),Inches(1.5),Inches(11.73),Inches(1.0),sz=38,bold=True,color=WHITE,align=PP_ALIGN.CENTER)
text(s,"데이터 Export / Import 연동 가이드",Inches(.8),Inches(2.7),Inches(11.73),Inches(.7),sz=20,color=RGBColor(0xaa,0xc4,0xe8),align=PP_ALIGN.CENTER)
text(s,"v1.0.0 | 2026-05-30 | (주)지오정보기술",Inches(.8),Inches(3.5),Inches(11.73),Inches(.5),sz=13,color=MUTED,align=PP_ALIGN.CENTER)
for i,(t_,c) in enumerate([("🔒 HMAC 서명",RGBColor(0x22,0xc5,0x5e)),("📦 번들 ZIP",ACCENT),
("🔍 Dry Run",RGBColor(0xf5,0x9e,0x0b)),("🚫 민감 마스킹",RGBColor(0xef,0x44,0x44))]):
bx=Inches(2.3+i*2.2)
rect(s,bx,Inches(4.9),Inches(2.0),Inches(.5),fill=c)
text(s,t_,bx+Inches(.1),Inches(4.95),Inches(1.8),Inches(.4),sz=11,bold=True,color=WHITE,align=PP_ALIGN.CENTER)
# S2 연동 흐름
s=blank()
rect(s,0,0,W,Inches(1.2),fill=BRAND)
text(s,"데이터 연동 흐름",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE)
boxes=[("🖥️","폐쇄망\nGUARDiA","폐쇄망 서버"),("📦","번들 Export\n(ZIP+HMAC)","GET /export/bundle"),
("🔌","물리적 이동","USB/보안매체"),("☁️","개방망\nManager","Import UI")]
for i,(icon,lbl,sub) in enumerate(boxes):
bx=Inches(.4+i*3.1)
rect(s,bx,Inches(1.5),Inches(2.7),Inches(2.5),fill=GRAY)
text(s,icon,bx+Inches(.9),Inches(1.7),Inches(.9),Inches(.9),sz=28,align=PP_ALIGN.CENTER,color=DARK)
text(s,lbl,bx+Inches(.2),Inches(2.7),Inches(2.3),Inches(.7),sz=13,bold=True,color=BRAND,align=PP_ALIGN.CENTER)
text(s,sub,bx+Inches(.2),Inches(3.4),Inches(2.3),Inches(.4),sz=10,color=MUTED,align=PP_ALIGN.CENTER)
if i<3:
text(s,"",Inches(.4+i*3.1+2.75),Inches(2.4),Inches(.35),Inches(.5),sz=22,color=MUTED,align=PP_ALIGN.CENTER)
tbl_sl(s,["단계","작업","비고"],
[["1","폐쇄망 Export","GET /export/bundle → ZIP"],["2","물리적 이동","USB/보안매체"],
["3","Manager 업로드","http://zioinfo.co.kr:8090/export-import"],
["4","Dry Run 검증","HMAC + 데이터 카운트"],["5","Import 실행","중복 SKIP"]],
Inches(.4),Inches(4.2),Inches(12.5),Inches(2.8),cws=[Inches(1.0),Inches(5.5),Inches(6.0)])
# S3 API 명세
s=blank()
rect(s,0,0,W,Inches(1.2),fill=BRAND)
text(s,"API 명세",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE)
tbl_sl(s,["메서드","경로","설명"],
[["GET","/api/export-import/export/bundle","전체 번들 ZIP (HMAC 서명 포함)"],
["GET","/api/export-import/export/sr","SR 목록 JSON (최대 5000건)"],
["GET","/api/export-import/export/cmdb","CMDB 서버 자산 JSON"],
["GET","/api/export-import/export/institutions","기관 목록 JSON"],
["GET","/api/export-import/export/audit","감사 로그 JSON (최대 2000건)"],
["POST","/api/export-import/import/bundle","번들 ZIP Import (dry_run 지원)"],
["POST","/api/export-import/import/sr","SR JSON Import (중복 SKIP)"]],
Inches(.4),Inches(1.4),Inches(12.5),Inches(3.5),cws=[Inches(1.5),Inches(5.5),Inches(5.5)])
rect(s,Inches(.4),Inches(5.2),Inches(12.5),Inches(.8),fill=RGBColor(0xff,0xf3,0xcd))
text(s,"📌 민감 필드 자동 마스킹: ip_addr, os_pw_enc, ssh_user, ssh_pw → '****'",
Inches(.6),Inches(5.3),Inches(12.1),Inches(.6),sz=12,color=RGBColor(0x85,0x4d,0x0e))
# S4 테스트 결과
s=blank()
rect(s,0,0,W,Inches(1.2),fill=BRAND)
text(s,"테스트 결과 — 7/7 PASS ✅",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE)
tbl_sl(s,["#","테스트 항목","내용","결과"],
[["T1","SR Export","5건 JSON","PASS"],["T2","CMDB Export","6건 JSON","PASS"],
["T3","기관 Export","3건 JSON","PASS"],["T4","감사 로그 Export","5건 JSON","PASS"],
["T5","번들 ZIP Export","HMAC 서명 포함 5KB ZIP","PASS"],
["T6","SR Import dry_run","1건 검증, 저장 없음","PASS"],
["T7","Manager UI","HTTP 200","PASS"]],
Inches(.4),Inches(1.4),Inches(12.5),Inches(4.0),cws=[Inches(.7),Inches(3.5),Inches(5.5),Inches(2.3)])
rect(s,Inches(.4),Inches(5.8),Inches(12.5),Inches(.8),fill=GREEN)
text(s,"✅ 전체 7개 테스트 모두 통과 | 버그 수정: date JSON 직렬화 오류 → isoformat() 처리",
Inches(.6),Inches(5.9),Inches(12.1),Inches(.6),sz=13,bold=True,color=WHITE,align=PP_ALIGN.CENTER)
# S5 마지막
s=blank()
rect(s,0,0,W,H,fill=BRAND)
text(s,"GUARDiA 폐쇄망 ↔ 개방망 데이터 연동",Inches(1),Inches(2.3),Inches(11.33),Inches(1.2),sz=28,bold=True,color=WHITE,align=PP_ALIGN.CENTER)
text(s,"안전한 HMAC 서명으로 데이터 무결성을 보장합니다",Inches(1),Inches(3.7),Inches(11.33),Inches(.6),sz=14,color=RGBColor(0xaa,0xc4,0xe8),align=PP_ALIGN.CENTER)
text(s,"(주)지오정보기술 | GUARDiA v2.0.0 | 2026-05-30",Inches(1),Inches(5.5),Inches(11.33),Inches(.5),sz=12,color=MUTED,align=PP_ALIGN.CENTER)
prs.save(out); print(f"PPTX: {out}")
if __name__ == "__main__":
gen_pdf(str(OUT_DIR/"29_GUARDiA_폐쇄망_데이터연동_가이드.pdf"))
gen_pptx(str(OUT_DIR/"30_GUARDiA_폐쇄망_데이터연동_발표자료.pptx"))
print("완료")