devops-maturity 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
__init__.py ADDED
File without changes
badges/generator.py ADDED
@@ -0,0 +1,9 @@
1
+ import pybadges
2
+
3
+
4
+ def generate_badge(score: float, level: str, output: str):
5
+ svg = pybadges.badge(
6
+ left_text="DevOps Maturity", right_text=level, right_color="green"
7
+ )
8
+ with open(output, "w") as f:
9
+ f.write(svg)
cli/__init__.py ADDED
File without changes
cli/main.py ADDED
@@ -0,0 +1,43 @@
1
+ import typer
2
+ from src.core.model import UserResponse, Assessment, SessionLocal
3
+ from src.core.scorer import calculate_score, score_to_level
4
+ from src.web.main import criteria
5
+
6
+ app = typer.Typer(help="Run DevOps maturity assessment interactively.")
7
+
8
+
9
+ @app.command(name="assess")
10
+ def assess():
11
+ """Run an interactive DevOps maturity assessment."""
12
+ responses = []
13
+ typer.echo("DevOps Maturity Assessment\n")
14
+ for c in criteria:
15
+ answer = typer.confirm(f"{c.question} (yes/no)", default=False)
16
+ responses.append(UserResponse(id=c.id, answer=answer))
17
+ score = calculate_score(criteria, responses)
18
+ level = score_to_level(score)
19
+ typer.echo(f"\nYour score: {score:.2f}")
20
+ typer.echo(f"Your maturity level: {level}")
21
+
22
+ # Save to database
23
+ db = SessionLocal()
24
+ responses_dict = {r.id: r.answer for r in responses}
25
+ assessment = Assessment(responses=responses_dict)
26
+ db.add(assessment)
27
+ db.commit()
28
+ db.close()
29
+ typer.echo("Assessment saved to database.")
30
+
31
+
32
+ @app.command(name="list")
33
+ def list_assessments():
34
+ """List all assessments from the database."""
35
+ db = SessionLocal()
36
+ assessments = db.query(Assessment).all()
37
+ db.close()
38
+ for a in assessments:
39
+ typer.echo(f"ID: {a.id} | Responses: {a.responses}")
40
+
41
+
42
+ if __name__ == "__main__":
43
+ app()
core/model.py ADDED
@@ -0,0 +1,34 @@
1
+ from pydantic import BaseModel
2
+ from sqlalchemy import Column, Integer, JSON, create_engine
3
+ from sqlalchemy.ext.declarative import declarative_base
4
+ from sqlalchemy.orm import sessionmaker
5
+
6
+ Base = declarative_base()
7
+
8
+
9
+ class Criteria(BaseModel):
10
+ id: str
11
+ question: str
12
+ weight: float
13
+
14
+
15
+ class UserResponse(BaseModel):
16
+ id: str
17
+ answer: bool
18
+
19
+
20
+ class Assessment(Base): # type: ignore
21
+ __tablename__ = "assessments"
22
+ id = Column(Integer, primary_key=True, index=True)
23
+ responses = Column(JSON)
24
+
25
+
26
+ engine = None
27
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False)
28
+
29
+
30
+ def init_db():
31
+ global engine
32
+ engine = create_engine("sqlite:///./devops_maturity.db")
33
+ SessionLocal.configure(bind=engine)
34
+ Base.metadata.create_all(bind=engine)
core/scorer.py ADDED
@@ -0,0 +1,28 @@
1
+ from typing import List
2
+ from .model import Criteria, UserResponse
3
+
4
+
5
+ def calculate_score(criteria: List[Criteria], responses: List[UserResponse]) -> float:
6
+ total = 0.0
7
+ max_score = 0.0
8
+ response_map = {r.id: r.answer for r in responses}
9
+
10
+ for c in criteria:
11
+ max_score += c.weight
12
+ if response_map.get(c.id):
13
+ total += c.weight
14
+
15
+ return (total / max_score) * 100 if max_score else 0.0
16
+
17
+
18
+ def score_to_level(score: float) -> str:
19
+ if score == 0:
20
+ return "WIP"
21
+ elif score < 50:
22
+ return "PASSING"
23
+ elif score < 70:
24
+ return "BRONZE"
25
+ elif score < 90:
26
+ return "SILVER"
27
+ else:
28
+ return "GOLD"
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: devops-maturity
3
+ Version: 0.1.1
4
+ Summary: A DevOps maturity assessment tool with CLI and Web interface.
5
+ Author-email: Xianpeng Shen <xianpeng.shen@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: homepage, https://github.com/devops-maturity
8
+ Project-URL: source, https://github.com/devops-maturity/devops-maturity
9
+ Project-URL: tracker, https://github.com/devops-maturity/devops-maturity/issues
10
+ Keywords: devops,maturity,assessment,cli,web
11
+ Classifier: Development Status :: 1 - Planning
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Requires-Python: >=3.8
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: fastapi
22
+ Requires-Dist: badgepy
23
+ Requires-Dist: typer
24
+ Requires-Dist: jinja2
25
+ Requires-Dist: pydantic
26
+ Requires-Dist: python-multipart
27
+ Requires-Dist: sqlalchemy
28
+ Provides-Extra: dev
29
+ Requires-Dist: ruff; extra == "dev"
30
+ Requires-Dist: uvicorn; extra == "dev"
31
+ Provides-Extra: test
32
+ Requires-Dist: fastapi; extra == "test"
33
+ Requires-Dist: httpx; extra == "test"
34
+ Requires-Dist: pytest; extra == "test"
35
+ Requires-Dist: pytest-cov; extra == "test"
36
+
37
+ # DevOps Maturity Assessment
38
+
39
+ [![CI](https://github.com/devops-maturity/devops-maturity/actions/workflows/ci.yml/badge.svg)](https://github.com/devops-maturity/devops-maturity/actions/workflows/ci.yml)
40
+
41
+ ## Overview
42
+
43
+ This repository contains a DevOps Maturity Assessment tool designed to help organizations evaluate their DevOps practices and identify areas for improvement. The assessment is based on a set of questions that cover various aspects of DevOps, including culture, automation, measurement, sharing, and security.
44
+
45
+ ## Usage
46
+
47
+ ### Launch Web Application
48
+
49
+ To launch the web application, you can use the following command:
50
+
51
+ ```bash
52
+ uvicorn src.web.main:app --reload
53
+ ```
54
+
55
+ Then open [http://localhost:8000](http://localhost:8000) in your browser to access the DevOps Maturity Assessment web interface.
56
+
57
+ ### Use the CLI
58
+
59
+ To run the assessment in your terminal, use:
60
+
61
+ ```bash
62
+ python -m src.cli.main check
63
+ ```
64
+
65
+ This will prompt you to answer questions interactively and generate a badge based on your score.
66
+
67
+ ## Assessment Categories & Criteria
68
+
69
+ The assessment covers the following categories and criteria:
70
+
71
+ ### CI/CD Basics
72
+ - Build a specific branch (must have)
73
+ - Build upon pull request (must have)
74
+ - Docker (nice to have)
75
+
76
+ ### Quality
77
+ - Automated Testing: Functional testing (must have)
78
+ - Automated Testing: Performance testing (must have)
79
+ - Code Coverage (nice to have)
80
+ - Accessibility Testing (nice to have)
81
+
82
+ ### Security
83
+ - Security scan (must have)
84
+ - License scan (nice to have)
85
+
86
+ ### Secure Supply Chain
87
+ - Documented Build Chain (must have)
88
+ - CICD as coded (must have)
89
+ - Artifacts are signed (nice to have)
90
+ - Artifactory download for Package Managers (nice to have)
91
+
92
+ ### Reporting
93
+ - Email/Slack reporting functionality
94
+
95
+ ### Analysis
96
+ - Quality Gate (nice to have)
97
+ - Code Lint (nice to have)
98
+ - Static code analysis (nice to have)
99
+ - Dynamic code analysis (nice to have)
100
+
101
+ ## Badge Levels
102
+
103
+ Your score will generate one of the following badges:
104
+ - **WIP**: 0%
105
+ - **PASSING**: 1–49%
106
+ - **BRONZE**: 50–69%
107
+ - **SILVER**: 70–89%
108
+ - **GOLD**: 90–100%
@@ -0,0 +1,19 @@
1
+ __init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ badges/generator.py,sha256=YBKvXIeOMyZf5uf9mGb7mQ4dhjr5He4062We0uWDepM,238
3
+ cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ cli/main.py,sha256=lfBf9LMimOIEj2qfOT9xnIZBLqVRc1jAg3fioOXa1w8,1320
5
+ core/model.py,sha256=4lpFpPwjKClry8rX9YXtrizJf8T0Bo6ybFIHus3-no0,770
6
+ core/scorer.py,sha256=1Efv275N75QDojlEQlm_B0d-7TUEfbipMAi0mlb-hw4,676
7
+ web/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ web/main.py,sha256=hfaVIrDCIaIG6i_WlusD6MpzCI5xmIosCAeaR5JXOcM,5160
9
+ web/static/badge.svg,sha256=XM2p4Kd51GPom86SPHtW1VwkItR5MMnMcq-X3axKcpA,1142
10
+ web/static/devops-maturity.svg,sha256=XM2p4Kd51GPom86SPHtW1VwkItR5MMnMcq-X3axKcpA,1142
11
+ web/static/logo.png,sha256=4IcAsfCrVv_OtpC8e0vnNfweRvcBHt-Lb8Z4dtM_mT4,41001
12
+ web/templates/assessments.html,sha256=gXlyF3UINJV39pn_rrJ9uCAfVPGdyY_QM7BRyET4QuE,1373
13
+ web/templates/form.html,sha256=FpFgIjUZ-gdkAmcoMc1wh1x1lDh_ie1RgfkEhm50EwA,2377
14
+ web/templates/result.html,sha256=hdR03fYLY-hrHq1UTOGwy3j0Aik3oSrVFt5ihCFl-GU,1276
15
+ devops_maturity-0.1.1.dist-info/METADATA,sha256=5CExjXxrd_0SBBno5DpBy-grL_DahTJ40nm8qiOjP6c,3421
16
+ devops_maturity-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ devops_maturity-0.1.1.dist-info/entry_points.txt,sha256=GuidsCU_0PQjWHUhkA4RIP9apBLQDHIslthHq6dveao,53
18
+ devops_maturity-0.1.1.dist-info/top_level.txt,sha256=HGiqY6MLXG0GFsMgFEltq_citYFbCNCKie1-7dW-n-I,29
19
+ devops_maturity-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ devops-maturity = src.cli.main:app
@@ -0,0 +1,5 @@
1
+ __init__
2
+ badges
3
+ cli
4
+ core
5
+ web
web/__init__.py ADDED
File without changes
web/main.py ADDED
@@ -0,0 +1,168 @@
1
+ from fastapi import FastAPI, Request
2
+ from fastapi.responses import HTMLResponse, FileResponse
3
+ from fastapi.staticfiles import StaticFiles
4
+ from fastapi.templating import Jinja2Templates
5
+ from ..core.model import Criteria, UserResponse, Assessment, SessionLocal
6
+ from ..core.scorer import calculate_score, score_to_level
7
+ from ..badges.generator import generate_badge
8
+
9
+ app = FastAPI()
10
+ templates = Jinja2Templates(directory="src/web/templates")
11
+ app.mount("/static", StaticFiles(directory="src/web/static"), name="static")
12
+
13
+ # categories of criteria
14
+ categories = {
15
+ "CI/CD Basics": "CI/CD Basics",
16
+ "Quality": "Quality",
17
+ "Security": "Security",
18
+ "Secure Supply Chain": "Secure Supply Chain",
19
+ "Reporting": "Reporting",
20
+ "Analysis": "Analysis",
21
+ }
22
+
23
+ criteria = [
24
+ # CI/CD Basics
25
+ Criteria(
26
+ id="build_branch",
27
+ question="Build a specific branch (CI/CD Basics, must have)",
28
+ weight=1.0,
29
+ ),
30
+ Criteria(
31
+ id="build_pr",
32
+ question="Build upon pull request (CI/CD Basics, must have)",
33
+ weight=1.0,
34
+ ),
35
+ Criteria(id="docker", question="Docker (CI/CD Basics, nice to have)", weight=0.5),
36
+ # Quality
37
+ Criteria(
38
+ id="func_test",
39
+ question="Automated Testing: Functional testing (Quality, must have)",
40
+ weight=1.0,
41
+ ),
42
+ Criteria(
43
+ id="perf_test",
44
+ question="Automated Testing: Performance testing (Quality, must have)",
45
+ weight=1.0,
46
+ ),
47
+ Criteria(
48
+ id="code_coverage", question="Code Coverage (Quality, nice to have)", weight=0.5
49
+ ),
50
+ Criteria(
51
+ id="accessibility",
52
+ question="Accessibility Testing (Quality, nice to have)",
53
+ weight=0.5,
54
+ ),
55
+ # Security
56
+ Criteria(
57
+ id="security_scan", question="Security scan (Security, must have)", weight=1.0
58
+ ),
59
+ Criteria(
60
+ id="license_scan", question="License scan (Security, nice to have)", weight=0.5
61
+ ),
62
+ # Secure Supply Chain
63
+ Criteria(
64
+ id="doc_build_chain",
65
+ question="Documented Build Chain (Secure Supply Chain, must have)",
66
+ weight=1.0,
67
+ ),
68
+ Criteria(
69
+ id="cicd_as_code",
70
+ question="CICD as coded (Secure Supply Chain, must have)",
71
+ weight=1.0,
72
+ ),
73
+ Criteria(
74
+ id="signed_artifacts",
75
+ question="Artifacts are signed (Secure Supply Chain, nice to have)",
76
+ weight=0.5,
77
+ ),
78
+ Criteria(
79
+ id="artifactory_download",
80
+ question="Artifactory download for Package Managers (Secure Supply Chain, nice to have)",
81
+ weight=0.5,
82
+ ),
83
+ # Reporting
84
+ Criteria(
85
+ id="reporting",
86
+ question="Email/Slack reporting functionality (Reporting, must have)",
87
+ weight=1.0,
88
+ ),
89
+ # Analysis
90
+ Criteria(
91
+ id="quality_gate", question="Quality Gate (Analysis, nice to have)", weight=0.5
92
+ ),
93
+ Criteria(id="code_lint", question="Code Lint (Analysis, nice to have)", weight=0.5),
94
+ Criteria(
95
+ id="static_analysis",
96
+ question="Static code analysis (Analysis, nice to have)",
97
+ weight=0.5,
98
+ ),
99
+ Criteria(
100
+ id="dynamic_analysis",
101
+ question="Dynamic code analysis (Analysis, nice to have)",
102
+ weight=0.5,
103
+ ),
104
+ ]
105
+
106
+
107
+ @app.get("/", response_class=HTMLResponse)
108
+ def read_form(request: Request):
109
+ return templates.TemplateResponse(
110
+ "form.html",
111
+ {"request": request, "criteria": criteria, "categories": categories},
112
+ )
113
+
114
+
115
+ @app.post("/submit")
116
+ async def submit(request: Request):
117
+ form = await request.form()
118
+ responses = []
119
+ responses_dict = {}
120
+ for k, v in form.items():
121
+ answer = v == "yes"
122
+ responses.append(UserResponse(id=k, answer=answer))
123
+ responses_dict[k] = answer # store as dict for database
124
+
125
+ # Save to database
126
+ db = SessionLocal()
127
+ assessment = Assessment(responses=responses_dict)
128
+ db.add(assessment)
129
+ db.commit()
130
+ db.close()
131
+
132
+ score = calculate_score(criteria, responses)
133
+ level = score_to_level(score)
134
+ badge_filename = "devops-maturity.svg"
135
+ badge_path = f"src/web/static/{badge_filename}"
136
+ badge_url = f"/static/{badge_filename}"
137
+ generate_badge(score, level, badge_path)
138
+ return templates.TemplateResponse(
139
+ "result.html",
140
+ {
141
+ "request": request,
142
+ "score": score,
143
+ "level": level,
144
+ "badge_url": badge_url,
145
+ },
146
+ )
147
+
148
+
149
+ @app.get("/badge.svg")
150
+ def get_badge():
151
+ return FileResponse("src/web/static/badge.svg", media_type="image/svg+xml")
152
+
153
+
154
+ @app.get("/assessments", response_class=HTMLResponse)
155
+ def list_assessments(request: Request):
156
+ db = SessionLocal()
157
+ assessments = db.query(Assessment).all()
158
+ db.close()
159
+ assessment_data = []
160
+ for a in assessments:
161
+ # Convert responses from dict to UserResponse objects
162
+ responses = [UserResponse(id=k, answer=v) for k, v in a.responses.items()]
163
+ point = calculate_score(criteria, responses)
164
+ assessment_data.append({"id": a.id, "responses": a.responses, "point": point})
165
+ return templates.TemplateResponse(
166
+ "assessments.html",
167
+ {"request": request, "assessments": assessment_data},
168
+ )
web/static/badge.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="160.6" height="20"><linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="round"><rect width="160.6" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#round)"><rect width="102.6" height="20" fill="#555"/><rect x="102.6" width="58.0" height="20" fill="#97CA00"/><rect width="160.6" height="20" fill="url(#smooth)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="523.0" y="150" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="926.0" lengthAdjust="spacing">DevOps Maturity</text><text x="523.0" y="140" transform="scale(0.1)" textLength="926.0" lengthAdjust="spacing">DevOps Maturity</text><text x="1306.0" y="150" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="480.0" lengthAdjust="spacing">PASSING</text><text x="1306.0" y="140" transform="scale(0.1)" textLength="480.0" lengthAdjust="spacing">PASSING</text></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="160.6" height="20"><linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="round"><rect width="160.6" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#round)"><rect width="102.6" height="20" fill="#555"/><rect x="102.6" width="58.0" height="20" fill="#97CA00"/><rect width="160.6" height="20" fill="url(#smooth)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="523.0" y="150" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="926.0" lengthAdjust="spacing">DevOps Maturity</text><text x="523.0" y="140" transform="scale(0.1)" textLength="926.0" lengthAdjust="spacing">DevOps Maturity</text><text x="1306.0" y="150" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="480.0" lengthAdjust="spacing">PASSING</text><text x="1306.0" y="140" transform="scale(0.1)" textLength="480.0" lengthAdjust="spacing">PASSING</text></g></svg>
web/static/logo.png ADDED
Binary file
@@ -0,0 +1,42 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>All Assessments</title>
5
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
6
+ </head>
7
+ <body>
8
+ <div class="container mt-4">
9
+ <h1>All Assessments</h1>
10
+ <a href="/" class="btn btn-secondary mb-3">Back to Home Page</a>
11
+ <table class="table table-bordered table-striped">
12
+ <thead>
13
+ <tr>
14
+ <th>ID</th>
15
+ <th>Responses</th>
16
+ <th>Point</th>
17
+ </tr>
18
+ </thead>
19
+ <tbody>
20
+ {% for a in assessments %}
21
+ <tr>
22
+ <td>{{ a.id }}</td>
23
+ <td>
24
+ <ul class="mb-0">
25
+ {% for key, value in a.responses.items() %}
26
+ <li><strong>{{ key }}</strong>: {{ value }}</li>
27
+ {% endfor %}
28
+ </ul>
29
+ </td>
30
+ <td>{{ '%.2f' | format(a.point) }}</td>
31
+ </tr>
32
+ {% endfor %}
33
+ </tbody>
34
+ </table>
35
+ </div>
36
+ <footer class="footer mt-5 py-3 bg-light border-top">
37
+ <div class="container text-center text-muted">
38
+ &copy; 2025 DevOps Maturity Assessment
39
+ </div>
40
+ </footer>
41
+ </body>
42
+ </html>
@@ -0,0 +1,55 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>DevOps Maturity Assessment</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <link rel="icon" type="image/png" href="/static/logo.png">
9
+ <style>
10
+ body { background: #FFFFFF; }
11
+ .container { max-width: 800px; margin-top: 40px; }
12
+ .category-title { margin-top: 32px; margin-bottom: 16px; font-size: 1.3em; font-weight: bold; color: #2c3e50;}
13
+ .card { margin-bottom: 20px; }
14
+ .badge-level { font-size: 1.2em; }
15
+ </style>
16
+ </head>
17
+ <body>
18
+ <div class="container">
19
+ <ul class="nav nav-tabs mb-4">
20
+ <li class="nav-item">
21
+ <a class="nav-link active" aria-current="page" href="/">Assessment</a>
22
+ </li>
23
+ <li class="nav-item">
24
+ <a class="nav-link" href="/assessments">All Assessments</a>
25
+ </li>
26
+ </ul>
27
+ <div class="text-center mb-4">
28
+ <img src="/static/logo.png" alt="DevOps Maturity Logo" style="max-height:104px; background:#FFFFFF; padding:8px; border-radius:8px;">
29
+ </div>
30
+ <h1 class="mb-4 text-center">DevOps Maturity Assessment</h1>
31
+ <form method="post" action="/submit">
32
+ {% for cat in categories %}
33
+ <div class="category-title">{{ cat }}</div>
34
+ {% for c in criteria if cat in c.question %}
35
+ <div class="card">
36
+ <div class="card-body">
37
+ <label class="form-label fw-bold">{{ c.question }}</label>
38
+ <div>
39
+ <input type="radio" name="{{ c.id }}" value="yes"> Yes
40
+ <input type="radio" name="{{ c.id }}" value="no" checked> No
41
+ </div>
42
+ </div>
43
+ </div>
44
+ {% endfor %}
45
+ {% endfor %}
46
+ <div class="text-center">
47
+ <button type="submit" class="btn btn-primary btn-lg">Submit Assessment</button>
48
+ </div>
49
+ </form>
50
+ <footer class="mt-5 text-center text-muted">
51
+ &copy; {{ 2025 }} DevOps Maturity Assessment
52
+ </footer>
53
+ </div>
54
+ </body>
55
+ </html>
@@ -0,0 +1,34 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Assessment Result</title>
6
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
7
+ <style>
8
+ .badge-img { max-width: 220px; margin-bottom: 20px; }
9
+ .copy-box { font-family: monospace; background: #FFFFFF; padding: 8px; border-radius: 4px; margin-bottom: 10px; }
10
+ </style>
11
+ </head>
12
+ <body>
13
+ <div class="container mt-5">
14
+ <h2 class="mb-4 text-center">Your DevOps Maturity Badge</h2>
15
+ <div class="text-center">
16
+ <img src="{{ badge_url }}" alt="DevOps Maturity Badge" class="badge-img">
17
+ </div>
18
+ <h4 class="mt-4">Copy Badge Code</h4>
19
+ <div>
20
+ <strong>Markdown:</strong>
21
+ <div class="copy-box">![]({{ badge_url }})</div>
22
+ <strong>reStructuredText:</strong>
23
+ <div class="copy-box">.. image:: {{ badge_url }}</div>
24
+ <strong>HTML:</strong>
25
+ <div class="copy-box">&lt;img src="{{ badge_url }}" alt="DevOps Maturity Badge"&gt;</div>
26
+ <strong>AsciiDoc:</strong>
27
+ <div class="copy-box">image::{{ badge_url }}[]</div>
28
+ </div>
29
+ <div class="mt-4 text-center">
30
+ <a href="/" class="btn btn-secondary">Back to Assessment</a>
31
+ </div>
32
+ </div>
33
+ </body>
34
+ </html>