- 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>
231 lines
14 KiB
Python
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("완료")
|