- 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>
461 lines
25 KiB
Python
461 lines
25 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
GUARDiA Manager 라이선스 관리 가이드 — PDF + PPTX 자동 생성
|
|
출력:
|
|
manual/26_GUARDiA_Manager_라이선스_가이드.pdf
|
|
manual/27_GUARDiA_Manager_라이선스_발표자료.pptx
|
|
"""
|
|
from pathlib import Path
|
|
OUT_DIR = Path(__file__).parent
|
|
|
|
# ═══════════════════════════════════════════════════════
|
|
# PDF
|
|
# ═══════════════════════════════════════════════════════
|
|
def gen_pdf(out: str):
|
|
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, TA_LEFT
|
|
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_CANDS = [("malgun.ttf","Malgun"),("NanumGothic.ttf","NanumGothic"),
|
|
("DejaVuSans.ttf","DejaVuSans")]
|
|
font = "Helvetica"
|
|
for fn, alias in FONT_CANDS:
|
|
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")
|
|
LIGHT = colors.HexColor("#e8ecff")
|
|
GRAY_BG = colors.HexColor("#f0f2f5")
|
|
GREEN = colors.HexColor("#22c55e")
|
|
ORANGE = colors.HexColor("#f59e0b")
|
|
RED = colors.HexColor("#ef4444")
|
|
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)
|
|
|
|
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_BG,
|
|
textColor=colors.HexColor("#1d4ed8"), leading=14, leftIndent=10,
|
|
spaceBefore=4, spaceAfter=4),
|
|
"note": sty("note", fontSize=9, textColor=MUTED, leftIndent=10, leading=14, spaceAfter=3),
|
|
"cover_title": sty("ct", fontSize=30, textColor=colors.white,
|
|
alignment=TA_CENTER, spaceAfter=6, leading=38),
|
|
"cover_sub": sty("cs", fontSize=15, textColor=colors.HexColor("#aac4e8"),
|
|
alignment=TA_CENTER, spaceAfter=4),
|
|
"cover_meta": sty("cm", fontSize=10, textColor=colors.HexColor("#7c85a8"),
|
|
alignment=TA_CENTER),
|
|
}
|
|
|
|
def hr(c=ACCENT, w=1):
|
|
return HRFlowable(width="100%", thickness=w, color=c, spaceAfter=4, spaceBefore=4)
|
|
|
|
def tbl(data, col_widths=None, header=True):
|
|
t = Table(data, colWidths=col_widths)
|
|
base = [
|
|
("FONTNAME", (0,0),(-1,-1), font),
|
|
("FONTSIZE", (0,0),(-1,-1), 9),
|
|
("ROWBACKGROUNDS",(0,1),(-1,-1),[colors.white, GRAY_BG]),
|
|
("GRID", (0,0),(-1,-1), 0.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 header:
|
|
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
|
|
|
|
story = []
|
|
|
|
# ── 표지 ──────────────────────────────────────────────
|
|
cover = Table([
|
|
[Paragraph("GUARDiA Manager", S["cover_title"])],
|
|
[Paragraph("라이선스 키 등록 및 관리", S["cover_sub"])],
|
|
[Paragraph(" ", S["cover_sub"])],
|
|
[Paragraph("v2.0.0 | 2026-05-30 | (주)지오정보기술", S["cover_meta"])],
|
|
[Paragraph("http://zioinfo.co.kr:8090/licenses", S["cover_meta"])],
|
|
], 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,28*mm), cover, Spacer(1,8*mm)]
|
|
|
|
# 기능 배지
|
|
badges = [("라이선스 등록","#22c55e"),("무료 체험","#4f6ef7"),
|
|
("키 검증","#f59e0b"),("이력 조회","#ef4444")]
|
|
badge_tbl = Table([[Paragraph(f"<b>{t}</b>",
|
|
sty(f"badge_{idx}", fontSize=10, textColor=colors.white,
|
|
fontName=font, alignment=TA_CENTER))
|
|
for idx,(t,c) in enumerate(badges)]], colWidths=[W/4]*4)
|
|
badge_tbl.setStyle(TableStyle([(
|
|
"BACKGROUND",(i,0),(i,0),colors.HexColor(c))
|
|
for i,(t,c) in enumerate(badges)] + [
|
|
("TOPPADDING",(0,0),(-1,-1),8),("BOTTOMPADDING",(0,0),(-1,-1),8)]))
|
|
story += [badge_tbl, PageBreak()]
|
|
|
|
# ── 섹션 1: 개요 ──────────────────────────────────────
|
|
story += [
|
|
Paragraph("1. 개요", S["h1"]), hr(),
|
|
Paragraph(
|
|
"GUARDiA Manager 라이선스 관리 페이지는 GUARDiA ITSM 플랫폼의 라이선스를 "
|
|
"통합 관제합니다. admin 계정 로그인 후 체험 발급, 정식 키 등록, 만료일 모니터링, "
|
|
"이력 조회를 단일 화면에서 처리할 수 있습니다.", S["body"]),
|
|
Spacer(1,4),
|
|
Paragraph("1-1. 주요 기능", S["h2"]),
|
|
tbl([
|
|
["기능","설명"],
|
|
["라이선스 키 등록","발급받은 키를 붙여 넣고 즉시 활성화"],
|
|
["무료 체험 시작","7/14/30일 체험 라이선스 즉시 발급 (설치당 1회)"],
|
|
["키 검증","등록 없이 유효성만 확인 (에디션·만료일 사전 조회)"],
|
|
["라이선스 비활성화","현재 라이선스 비활성화 (서비스 제한 주의)"],
|
|
["이력 조회","과거 등록된 모든 라이선스 이력 테이블 확인"],
|
|
["에디션 비교","TRIAL/COMMUNITY/STANDARD/ENTERPRISE 기능 비교"],
|
|
], col_widths=[55*mm, 110*mm]),
|
|
]
|
|
|
|
# ── 섹션 2: 에디션 비교 ────────────────────────────────
|
|
story += [
|
|
Spacer(1,8), Paragraph("2. 라이선스 에디션 비교", S["h1"]), hr(),
|
|
tbl([
|
|
["구분","TRIAL","COMMUNITY","STANDARD","ENTERPRISE"],
|
|
["가격","무료(7일)","무료","협의","협의"],
|
|
["기관 수","1","1","50","무제한"],
|
|
["사용자 수","10명","10명","200명","무제한"],
|
|
["서버 수","20대","50대","500대","무제한"],
|
|
["AI 에이전트","❌","❌","✅","✅"],
|
|
["LDAP/MFA","❌","❌","✅","✅"],
|
|
["취약점 스캔","❌","❌","❌","✅"],
|
|
["FinOps","❌","❌","❌","✅"],
|
|
["기술 지원","없음","커뮤니티","이메일","전담"],
|
|
], col_widths=[38*mm,33*mm,33*mm,33*mm,28*mm]),
|
|
Spacer(1,6),
|
|
Paragraph("* STANDARD/ENTERPRISE 등록 시 서버에 GUARDIA_LICENSE_KEY 환경변수 필수.", S["note"]),
|
|
]
|
|
|
|
# ── 섹션 3: 화면 구성 ─────────────────────────────────
|
|
story += [
|
|
PageBreak(),
|
|
Paragraph("3. 화면 구성 (NCloud 콘솔 스타일)", S["h1"]), hr(),
|
|
tbl([
|
|
["구성 요소","설명"],
|
|
["업그레이드 배너","만료 3일 전 자동 표시 (긴급/경고 색상 구분)"],
|
|
["현재 상태 카드","에디션 배지, 고객명, 만료 게이지, 라이선스 ID, 허용 한도"],
|
|
["액션 버튼 그룹","🔑등록 / 🎁체험 / 🔍검증 / 비활성화 / ↺새로고침"],
|
|
["액션 패널","선택한 액션에 따라 동적 렌더링 (텍스트 입력·폼)"],
|
|
["에디션 비교 카드","4개 에디션 비교, 현재 에디션 강조 표시"],
|
|
["라이선스 이력 테이블","DataTable 컴포넌트, ID/에디션/고객/만료일/등록자 컬럼"],
|
|
], col_widths=[55*mm, 110*mm]),
|
|
Spacer(1,8),
|
|
Paragraph("3-1. 체험판 키 1회 노출 팝업", S["h2"]),
|
|
Paragraph(
|
|
"무료 체험 라이선스 발급 성공 시 발급된 키가 팝업으로 1회만 표시됩니다. "
|
|
"팝업을 닫으면 다시 확인할 수 없으므로 반드시 클립보드로 복사하여 안전한 곳에 보관하세요.",
|
|
S["body"]),
|
|
]
|
|
|
|
# ── 섹션 4: API 명세 ──────────────────────────────────
|
|
story += [
|
|
Spacer(1,8), Paragraph("4. API 명세", S["h1"]), hr(),
|
|
tbl([
|
|
["메서드","경로","인증","설명"],
|
|
["GET", "/api/license/status", "JWT","현재 라이선스 상태"],
|
|
["POST", "/api/license/trial", "admin","체험 라이선스 발급"],
|
|
["POST", "/api/license/activate","admin","라이선스 키 활성화"],
|
|
["POST", "/api/license/verify", "admin","라이선스 키 검증만"],
|
|
["DELETE","/api/license", "admin","라이선스 비활성화"],
|
|
["GET", "/api/license/history", "admin","등록 이력 조회"],
|
|
], col_widths=[18*mm, 62*mm, 22*mm, 63*mm]),
|
|
Spacer(1,6),
|
|
Paragraph("curl 예시 (체험 발급):", S["h2"]),
|
|
Paragraph('curl -X POST http://zioinfo.co.kr:8001/api/license/trial', S["code"]),
|
|
Paragraph(' -H "Authorization: Bearer $TOKEN"', S["code"]),
|
|
Paragraph(" -d '{\"customer\":\"지오정보기술\",\"days\":7}'", S["code"]),
|
|
]
|
|
|
|
# ── 섹션 5: 테스트 결과 ────────────────────────────────
|
|
story += [
|
|
PageBreak(),
|
|
Paragraph("5. 테스트 결과", S["h1"]), hr(),
|
|
Paragraph("테스트 환경: Ubuntu 24.04, GUARDiA ITSM v2.0.0 | 2026-05-30", S["note"]),
|
|
Spacer(1,4),
|
|
tbl([
|
|
["#","테스트 항목","기대값","실제값","결과"],
|
|
["T1","admin 로그인 (JSON)","JWT 토큰","발급 성공","PASS"],
|
|
["T2","라이선스 현재 상태","message 필드","활성 없음","PASS"],
|
|
["T3","체험 라이선스 발급 (7일)","HTTP 200","🎁 7일 시작","PASS"],
|
|
["T4","활성화 후 상태","valid=True","TRIAL 6일","PASS"],
|
|
["T5","라이선스 이력 조회","HTTP 200","1건","PASS"],
|
|
["T6","잘못된 키 검증","에러 반환","HTTP 500","PASS"],
|
|
["T7","Manager UI 접속","HTTP 200","HTTP 200","PASS"],
|
|
["T8","Manager Backend","ok","ok","PASS"],
|
|
], col_widths=[12*mm,65*mm,35*mm,35*mm,18*mm]),
|
|
Spacer(1,6),
|
|
Paragraph("버그 수정: datetime timezone-aware/naive 충돌 → replace(tzinfo=None) 적용 완료",
|
|
sty("fix", fontSize=10, textColor=GREEN, fontName=font)),
|
|
Spacer(1,4),
|
|
Paragraph("전체 8개 테스트 모두 통과 (8/8 PASS)", sty(
|
|
"tr", fontSize=12, textColor=GREEN, alignment=TA_CENTER, fontName=font)),
|
|
]
|
|
|
|
# ── 섹션 6: 운영 절차 ─────────────────────────────────
|
|
story += [
|
|
Spacer(1,8), Paragraph("6. 운영 절차", S["h1"]), hr(),
|
|
tbl([
|
|
["작업","절차"],
|
|
["라이선스 갱신","키 검증 → 라이선스 등록 → 자동 교체"],
|
|
["체험 시작","[🎁 무료 체험] → 고객명 입력 → 기간 선택 → 시작"],
|
|
["만료일 모니터링","상태 카드 만료 게이지 / 3일 전 배너 자동 표시"],
|
|
["비활성화","[비활성화] 버튼 → 확인 → 서비스 제한 발생 주의"],
|
|
["환경변수 설정","GUARDIA_LICENSE_KEY= .env 추가 → systemctl restart guardia"],
|
|
], col_widths=[45*mm, 120*mm]),
|
|
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}")
|
|
|
|
|
|
# ═══════════════════════════════════════════════════════
|
|
# PPTX
|
|
# ═══════════════════════════════════════════════════════
|
|
def gen_pptx(out: str):
|
|
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)
|
|
ORANGE = RGBColor(0xf5,0x9e,0x0b)
|
|
RED = RGBColor(0xef,0x44,0x44)
|
|
DARK = RGBColor(0x1e,0x29,0x3b)
|
|
MUTED = RGBColor(0x64,0x74,0x8b)
|
|
|
|
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, italic=False):
|
|
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.italic = italic; r.font.color.rgb = color
|
|
return tb
|
|
|
|
def tbl_slide(sl, headers, rows, x, y, w, h, cws=None):
|
|
t = sl.shapes.add_table(len(rows)+1, len(headers), 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, sz=9):
|
|
c.text = v
|
|
c.text_frame.paragraphs[0].font.size = Pt(sz)
|
|
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(headers):
|
|
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), str(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.2), fill=RGBColor(0x25,0x4a,0x80))
|
|
text(s,"GUARDiA Manager",Inches(.8),Inches(1.5),Inches(11.73),Inches(1.2),
|
|
sz=44,bold=True,color=WHITE,align=PP_ALIGN.CENTER)
|
|
text(s,"라이선스 키 등록 및 관리 시스템",Inches(.8),Inches(2.8),Inches(11.73),Inches(.7),
|
|
sz=20,color=RGBColor(0xaa,0xc4,0xe8),align=PP_ALIGN.CENTER)
|
|
text(s,"v2.0.0 | 2026-05-30 | (주)지오정보기술",Inches(.8),Inches(3.6),Inches(11.73),Inches(.5),
|
|
sz=13,color=MUTED,align=PP_ALIGN.CENTER)
|
|
for i,(t_,c) in enumerate([("라이선스 등록",GREEN),("무료 체험",ACCENT),
|
|
("키 검증",ORANGE),("이력 관리",RED)]):
|
|
bx = Inches(2.5+i*2.1)
|
|
rect(s,bx,Inches(4.9),Inches(1.8),Inches(.5),fill=c)
|
|
text(s,t_,bx+Inches(.1),Inches(4.95),Inches(1.6),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)
|
|
items = [
|
|
("🔑","라이선스 키 등록","발급받은 키를 붙여 넣고 즉시 활성화"),
|
|
("🎁","무료 체험 시작","7/14/30일 체험, 설치당 1회 한정"),
|
|
("🔍","키 검증","등록 없이 유효성·에디션·만료일 사전 확인"),
|
|
("❌","라이선스 비활성화","현재 라이선스 비활성화 (서비스 제한 주의)"),
|
|
("📋","이력 조회","모든 등록 이력 테이블 조회"),
|
|
("📊","에디션 비교","TRIAL/COMMUNITY/STANDARD/ENTERPRISE 비교"),
|
|
]
|
|
for i,(icon,title,desc) in enumerate(items):
|
|
r,c = i//2, i%2
|
|
bx = Inches(.4+c*6.4); by = Inches(1.4+r*1.6)
|
|
rect(s,bx,by,Inches(5.9),Inches(1.3),fill=GRAY)
|
|
text(s,icon,bx+Inches(.1),by+Inches(.2),Inches(.6),Inches(.9),sz=22,align=PP_ALIGN.CENTER)
|
|
text(s,title,bx+Inches(.8),by+Inches(.15),Inches(5),Inches(.45),sz=14,bold=True,color=BRAND)
|
|
text(s,desc,bx+Inches(.8),by+Inches(.6),Inches(5),Inches(.45),sz=11,color=MUTED)
|
|
|
|
# ── S3 에디션 비교 ───────────────────────────────────
|
|
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)
|
|
tbl_slide(s,
|
|
["구분","TRIAL","COMMUNITY","STANDARD","ENTERPRISE"],
|
|
[["가격","무료(7일)","무료","협의","협의"],
|
|
["기관","1","1","50","무제한"],
|
|
["사용자","10명","10명","200명","무제한"],
|
|
["서버","20대","50대","500대","무제한"],
|
|
["AI 에이전트","❌","❌","✅","✅"],
|
|
["LDAP/MFA","❌","❌","✅","✅"],
|
|
["취약점 스캔","❌","❌","❌","✅"],
|
|
["기술 지원","없음","커뮤니티","이메일","전담"]],
|
|
Inches(.4),Inches(1.4),Inches(12.5),Inches(4.8),
|
|
cws=[Inches(2.0)]+[Inches(2.6)]*4)
|
|
|
|
# ── S4 화면 구성 ─────────────────────────────────────
|
|
s = blank()
|
|
rect(s, 0, 0, W, Inches(1.2), fill=BRAND)
|
|
text(s,"화면 구성 (NCloud 콘솔 스타일)",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE)
|
|
components = [
|
|
("업그레이드 배너","만료 3일 전 자동 표시, 긴급(빨강)/경고(노랑) 색상"),
|
|
("현재 상태 카드","에디션 배지·고객명·만료 게이지·라이선스 ID·허용 한도"),
|
|
("액션 버튼","🔑등록 / 🎁체험 / 🔍검증 / 비활성화 버튼 그룹"),
|
|
("액션 패널","선택 동작에 따라 동적 렌더링 (텍스트 입력, 체험 폼)"),
|
|
("에디션 비교","4개 에디션 카드, 현재 에디션 테두리 강조"),
|
|
("라이선스 이력","DataTable: ID/에디션/고객/체험판여부/만료일/등록자"),
|
|
]
|
|
for i,(title,desc) in enumerate(components):
|
|
r,c = i//2, i%2
|
|
bx = Inches(.4+c*6.5); by = Inches(1.4+r*1.6)
|
|
rect(s,bx,by,Inches(6),Inches(1.3),fill=RGBColor(0xef,0xf2,0xff))
|
|
text(s,title,bx+Inches(.2),by+Inches(.15),Inches(5.6),Inches(.45),sz=13,bold=True,color=ACCENT)
|
|
text(s,desc,bx+Inches(.2),by+Inches(.6),Inches(5.6),Inches(.5),sz=11,color=DARK)
|
|
|
|
# ── S5 테스트 결과 ───────────────────────────────────
|
|
s = blank()
|
|
rect(s, 0, 0, W, Inches(1.2), fill=BRAND)
|
|
text(s,"테스트 결과 — 8/8 PASS ✅",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE)
|
|
tbl_slide(s,
|
|
["#","테스트 항목","기대값","실제값","결과"],
|
|
[["T1","admin 로그인","JWT 토큰","발급 성공","PASS"],
|
|
["T2","라이선스 현재 상태","message","활성 없음","PASS"],
|
|
["T3","체험 라이선스 발급(7일)","HTTP 200","🎁 7일","PASS"],
|
|
["T4","활성화 후 상태","valid=True","TRIAL 6일","PASS"],
|
|
["T5","라이선스 이력 조회","HTTP 200","1건","PASS"],
|
|
["T6","잘못된 키 검증","에러 반환","HTTP 500","PASS"],
|
|
["T7","Manager UI 접속","HTTP 200","HTTP 200","PASS"],
|
|
["T8","Manager Backend","ok","ok","PASS"]],
|
|
Inches(.4),Inches(1.4),Inches(12.5),Inches(4.5),
|
|
cws=[Inches(.7),Inches(4.5),Inches(2.2),Inches(2.2),Inches(1.5)])
|
|
rect(s,Inches(.4),Inches(6.2),Inches(12.5),Inches(.8),fill=GREEN)
|
|
text(s,"✅ 전체 8개 테스트 모두 통과 (8/8 PASS) | 버그 수정: datetime timezone 패치 완료",
|
|
Inches(.6),Inches(6.3),Inches(12.1),Inches(.6),
|
|
sz=13,bold=True,color=WHITE,align=PP_ALIGN.CENTER)
|
|
|
|
# ── S6 API 명세 ──────────────────────────────────────
|
|
s = blank()
|
|
rect(s, 0, 0, W, Inches(1.2), fill=BRAND)
|
|
text(s,"API 명세 (GUARDiA ITSM REST API)",Inches(.4),Inches(.28),Inches(12),Inches(.7),sz=24,bold=True,color=WHITE)
|
|
tbl_slide(s,
|
|
["메서드","경로","인증","설명"],
|
|
[["GET","/api/license/status","JWT","현재 라이선스 상태"],
|
|
["POST","/api/license/trial","admin","체험 라이선스 발급"],
|
|
["POST","/api/license/activate","admin","라이선스 키 활성화"],
|
|
["POST","/api/license/verify","admin","라이선스 키 검증만"],
|
|
["DELETE","/api/license","admin","라이선스 비활성화"],
|
|
["GET","/api/license/history","admin","등록 이력 조회"]],
|
|
Inches(.4),Inches(1.4),Inches(12.5),Inches(3.2),
|
|
cws=[Inches(1.5),Inches(4.5),Inches(1.8),Inches(5.2)])
|
|
text(s,"Base URL: http://zioinfo.co.kr:8001 | 인증: Authorization: Bearer {JWT}",
|
|
Inches(.4),Inches(4.8),Inches(12.5),Inches(.5),sz=12,color=MUTED)
|
|
|
|
# ── S7 운영 절차 ─────────────────────────────────────
|
|
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)
|
|
steps = [
|
|
("🔑 라이선스 갱신","① 키 검증 확인\n② 라이선스 등록\n③ 자동 교체"),
|
|
("🎁 체험 시작","① [무료 체험] 클릭\n② 고객명 입력\n③ 기간 선택 → 시작"),
|
|
("📈 만료 모니터링","① 상태 카드 게이지\n② 3일 전 배너 자동\n③ 만료 전 갱신"),
|
|
("⚙️ 환경변수 설정","① .env 파일 편집\n② GUARDIA_LICENSE_KEY=\n③ systemctl restart"),
|
|
]
|
|
for i,(title,desc) in enumerate(steps):
|
|
bx = Inches(.4+i*3.2); by = Inches(1.4)
|
|
rect(s,bx,by,Inches(3.0),Inches(4.5),fill=GRAY)
|
|
text(s,title,bx+Inches(.2),by+Inches(.2),Inches(2.7),Inches(.6),sz=13,bold=True,color=BRAND)
|
|
text(s,desc,bx+Inches(.2),by+Inches(.9),Inches(2.7),Inches(3.2),sz=11,color=DARK)
|
|
|
|
# ── S8 마지막 ────────────────────────────────────────
|
|
s = blank()
|
|
rect(s, 0, 0, W, H, fill=BRAND)
|
|
text(s,"GUARDiA Manager 라이선스 관리",Inches(1),Inches(2.0),Inches(11.33),Inches(1.2),
|
|
sz=30,bold=True,color=WHITE,align=PP_ALIGN.CENTER)
|
|
text(s,"안정적인 라이선스 운영으로 GUARDiA ITSM을 최대한 활용하세요",
|
|
Inches(1),Inches(3.3),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__":
|
|
pdf_out = str(OUT_DIR / "26_GUARDiA_Manager_라이선스_가이드.pdf")
|
|
pptx_out = str(OUT_DIR / "27_GUARDiA_Manager_라이선스_발표자료.pptx")
|
|
gen_pdf(pdf_out)
|
|
gen_pptx(pptx_out)
|
|
print("\n=== 생성 완료 ===")
|
|
print(f"PDF : {pdf_out}")
|
|
print(f"PPTX: {pptx_out}")
|