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 +0 -0
- badges/generator.py +9 -0
- cli/__init__.py +0 -0
- cli/main.py +43 -0
- core/model.py +34 -0
- core/scorer.py +28 -0
- devops_maturity-0.1.1.dist-info/METADATA +108 -0
- devops_maturity-0.1.1.dist-info/RECORD +19 -0
- devops_maturity-0.1.1.dist-info/WHEEL +5 -0
- devops_maturity-0.1.1.dist-info/entry_points.txt +2 -0
- devops_maturity-0.1.1.dist-info/top_level.txt +5 -0
- web/__init__.py +0 -0
- web/main.py +168 -0
- web/static/badge.svg +1 -0
- web/static/devops-maturity.svg +1 -0
- web/static/logo.png +0 -0
- web/templates/assessments.html +42 -0
- web/templates/form.html +55 -0
- web/templates/result.html +34 -0
__init__.py
ADDED
|
File without changes
|
badges/generator.py
ADDED
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
|
+
[](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,,
|
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
|
+
© 2025 DevOps Maturity Assessment
|
|
39
|
+
</div>
|
|
40
|
+
</footer>
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
web/templates/form.html
ADDED
|
@@ -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
|
+
© {{ 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"></div>
|
|
22
|
+
<strong>reStructuredText:</strong>
|
|
23
|
+
<div class="copy-box">.. image:: {{ badge_url }}</div>
|
|
24
|
+
<strong>HTML:</strong>
|
|
25
|
+
<div class="copy-box"><img src="{{ badge_url }}" alt="DevOps Maturity Badge"></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>
|