Skip to content

Commit 5b1c14e

Browse files
authored
Merge pull request #19 from nstankov-bg/main
[NEW FEATURE ADDED] πŸš€ Add Status Page Endpoints and Error Handling πŸ› οΈ
2 parents 7393362 + 2ec4c71 commit 5b1c14e

File tree

15 files changed

+319
-64
lines changed

15 files changed

+319
-64
lines changed

β€Ž.env.distβ€Ž

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
KUMA_SERVER=
2+
KUMA_USERNAME=
3+
KUMA_PASSWORD=
4+
ADMIN_PASSWORD=
5+
ACCESS_TOKEN_EXPIRATION=5

β€Ž.gitignoreβ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.env
2+
db/*

β€ŽMakefileβ€Ž

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
up:
2+
docker compose up --build
3+
4+
test:
5+
bash tests/monitor.sh
6+
bash tests/statuspage.sh

β€ŽREADME.mdβ€Ž

Lines changed: 53 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,84 @@
11
# Uptime-Kuma-Web-API
2+
23
## A REST API wrapper for [Uptime Kuma](https://github.com/louislam/uptime-kuma) using [Uptime-Kuma-API](https://github.com/lucasheld/uptime-kuma-api)
4+
35
---
4-
## EndPoints:
56

6-
![Alt text](./images/1.png )
7-
![Alt text](./images/2.png )
7+
## EndPoints:
88

9+
![Alt text](./images/1.png)
10+
![Alt text](./images/2.png)
911

1012
## How to use it:
11-
----
13+
14+
---
1215

1316
### Environment Variables :
14-
You have to define these ENV VARS in order to connect to your KUMA server.
17+
18+
You have to define these ENV VARS in order to connect to your KUMA server.
1519

1620
KUMA_SERVER=<your_kuma_server>
1721
KUMA_USERNAME=<your_kuma_username>
1822
KUMA_PASSWORD=<your_kuma_password>
1923
ADMIN_PASSWORD=<your admin password so you can connect with via the api>
2024

21-
#### Note:
25+
#### Note:
2226

23-
You have to define your ADMIN_PASSWORD or you wont be able to connect to your rest api.
27+
You have to define your ADMIN_PASSWORD or you wont be able to connect to your rest api.
2428

25-
You will connect with those credentials:
29+
You will connect with those credentials:
2630

2731
username = admin
2832
password = <ADMIN_PASSWORD>
2933

3034
### Features:
31-
- Multi user Kuma api ( without privilege YET!!) with a small SQLite db
32-
- Easy to use REST API with most of the Uptime-Kuma features
33-
- Swagger Docs
34-
- Dockerized [UptimeKuma_RestAPI Image](https://hub.docker.com/repository/docker/medaziz11/uptimekuma_restapi)
3535

36-
### Example :
37-
You can simply create a docker compose file like this :
36+
- Multi user Kuma api ( without privilege YET!!) with a small SQLite db
37+
- Easy to use REST API with most of the Uptime-Kuma features
38+
- Swagger Docs
39+
- Dockerized [UptimeKuma_RestAPI Image](https://hub.docker.com/repository/docker/medaziz11/uptimekuma_restapi)
40+
- Multi-architecture support (amd64, arm64, arm/v7, arm/v6)
41+
42+
### Example :
43+
44+
You can simply create a docker compose file like this :
3845

3946
```yaml
40-
version: '3.9'
41-
services:
42-
kuma:
43-
container_name: uptime-kuma
44-
image: louislam/uptime-kuma:latest
45-
ports:
46-
- "3001:3001"
47-
restart: always
48-
volumes:
49-
- uptime-kuma:/app/data
50-
51-
api:
52-
container_name: backend
53-
image: medaziz11/uptimekuma_restapi
54-
volumes:
55-
- ./db:/db
56-
restart: always
57-
environment:
58-
- KUMA_SERVER=http://kuma:3001
59-
- KUMA_USERNAME=test
60-
- KUMA_PASSWORD=123test.
61-
- ADMIN_PASSWORD=admin
62-
depends_on:
63-
- kuma
64-
ports:
65-
- "8000:8000"
66-
67-
volumes:
68-
uptime-kuma:
47+
version: "3.9"
48+
services:
49+
kuma:
50+
container_name: uptime-kuma
51+
image: louislam/uptime-kuma:latest
52+
ports:
53+
- "3001:3001"
54+
restart: always
55+
volumes:
56+
- uptime-kuma:/app/data
57+
58+
api:
59+
container_name: backend
60+
image: medaziz11/uptimekuma_restapi
61+
volumes:
62+
- ./db:/db
63+
restart: always
64+
environment:
65+
- KUMA_SERVER=http://kuma:3001
66+
- KUMA_USERNAME=test
67+
- KUMA_PASSWORD=123test.
68+
- ADMIN_PASSWORD=admin
69+
depends_on:
70+
- kuma
71+
ports:
72+
- "8000:8000"
73+
74+
volumes:
75+
uptime-kuma:
6976
```
70-
### In order for the example to work: You have to run kuma first then create your kuma username and password then re-run the compose file.
77+
78+
### In order for the example to work: You have to run kuma first then create your kuma username and password then re-run the compose file.
7179
7280
### Example CURL Script:
81+
7382
---
7483
7584
```bash

β€Žapp/main.pyβ€Ž

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
from fastapi import FastAPI
22
from fastapi.responses import RedirectResponse
33
import os
4-
4+
from routers import statuspage
55
from routers import monitor, auth, tags, cert, info, uptime, ping, database, settings as settings, user, maintenance
66
from config import settings as app_settings
77
from utils.admin import check_admin
88

99
from tortoise import Tortoise
1010

11-
1211
app = FastAPI(title=app_settings.PROJECT_NAME)
1312
app.router.redirect_slashes = True
1413

1514
app.include_router(user.router, prefix="/users", tags=["Users"])
1615
app.include_router(settings.router, prefix="/settings", tags=["Settings"])
1716
app.include_router(database.router, prefix="/database", tags=["DataBase"])
1817
app.include_router(monitor.router, prefix="/monitors", tags=["Monitor"])
18+
app.include_router(statuspage.router, prefix="/statuspages", tags=["Status Page"])
1919
app.include_router(maintenance.router, prefix="/maintenance", tags=["Maintenance"])
2020
app.include_router(tags.router, prefix="/tags", tags=["Tags"])
2121
app.include_router(cert.router, prefix="/cert_info", tags=["Certification Info"])
@@ -36,16 +36,12 @@ async def startup_event():
3636

3737
await Tortoise.generate_schemas()
3838

39-
40-
4139
await check_admin()
4240

43-
4441
@app.on_event("shutdown")
4542
async def shutdown_event():
4643
await Tortoise.close_connections()
4744

48-
4945
@app.get("/", include_in_schema=False)
5046
async def root():
51-
return RedirectResponse(url='/docs')
47+
return RedirectResponse(url='/docs')

β€Žapp/routers/statuspage.pyβ€Ž

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
from fastapi import APIRouter, Depends, HTTPException, Path, Body
2+
from uptime_kuma_api import UptimeKumaApi, UptimeKumaException
3+
from config import logger as logging
4+
from schemas.api import API
5+
from utils.deps import get_current_user
6+
from schemas.statuspage import StatusPageList, StatusPage, AddStatusPageResponse, AddStatusPageRequest,SaveStatusPageRequest,SaveStatusPageResponse, DeleteStatusPageResponse
7+
8+
router = APIRouter(redirect_slashes=True)
9+
10+
11+
@router.get("", response_model=StatusPageList, description="Get all status pages")
12+
async def get_all_status_pages(cur_user: API = Depends(get_current_user)):
13+
api: UptimeKumaApi = cur_user['api']
14+
try:
15+
return {"statuspages": api.get_status_pages()}
16+
except Exception as e:
17+
logging.fatal(e)
18+
raise HTTPException(500, str(e))
19+
20+
21+
@router.get("/{slug}", response_model=StatusPage, description="Get a status page")
22+
async def get_status_page(slug: str, cur_user: API = Depends(get_current_user)):
23+
api: UptimeKumaApi = cur_user['api']
24+
try:
25+
return api.get_status_page(slug)
26+
except UptimeKumaException as e:
27+
logging.error(e)
28+
raise HTTPException(404, str(e))
29+
except Exception as e:
30+
logging.fatal(e)
31+
raise HTTPException(500, str(e))
32+
33+
34+
@router.post("", response_model=AddStatusPageResponse, description="Add a status page")
35+
async def add_status_page(status_page_data: AddStatusPageRequest, cur_user: API = Depends(get_current_user)):
36+
api: UptimeKumaApi = cur_user['api']
37+
try:
38+
return api.add_status_page(status_page_data.slug, status_page_data.title)
39+
except UptimeKumaException as e:
40+
logging.error(e)
41+
raise HTTPException(400, str(e))
42+
except Exception as e:
43+
logging.fatal(e)
44+
raise HTTPException(500, str(e))
45+
46+
47+
@router.post("/{slug}",response_model=SaveStatusPageResponse, description="Save a status page")
48+
async def save_status_page(
49+
slug: str = Path(...),
50+
status_page_data: SaveStatusPageRequest = Body(...),
51+
cur_user: API = Depends(get_current_user),
52+
):
53+
api: UptimeKumaApi = cur_user['api']
54+
try:
55+
print (status_page_data.id)
56+
return api.save_status_page(
57+
slug,
58+
id=status_page_data.id,
59+
title=status_page_data.title,
60+
description=status_page_data.description,
61+
theme=status_page_data.theme,
62+
published=status_page_data.published,
63+
showTags=status_page_data.showTags,
64+
domainNameList=status_page_data.domainNameList,
65+
googleAnalyticsId=status_page_data.googleAnalyticsId,
66+
customCSS=status_page_data.customCSS,
67+
footerText=status_page_data.footerText,
68+
showPoweredBy=status_page_data.showPoweredBy,
69+
icon=status_page_data.icon,
70+
publicGroupList=status_page_data.publicGroupList,
71+
)
72+
except UptimeKumaException as e:
73+
logging.error(e)
74+
raise HTTPException(400, str(e))
75+
except Exception as e:
76+
logging.fatal(e)
77+
raise HTTPException(500, str(e))
78+
79+
@router.delete("/{slug}", response_model=DeleteStatusPageResponse, description="Delete a status page")
80+
async def delete_status_page(slug: str = Path(...), cur_user: API = Depends(get_current_user)):
81+
api: UptimeKumaApi = cur_user['api']
82+
try:
83+
return api.delete_status_page(slug)
84+
#Catch type error...which is actually a success, go figgure. {"detail":"'NoneType' object has no attribute 'values'"}
85+
except TypeError as e:
86+
if "NoneType" in str(e):
87+
logging.info("Status page deleted successfully")
88+
return {"status": "success"}
89+
except UptimeKumaException as e:
90+
logging.error(e)
91+
raise HTTPException(404, str(e))
92+
except Exception as e:
93+
logging.fatal(e)
94+
raise HTTPException(500, str(e))

β€Žapp/schemas/statuspage.pyβ€Ž

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from typing import List, Optional
2+
from pydantic import BaseModel, Field, HttpUrl, constr
3+
4+
class Incident(BaseModel):
5+
content: str
6+
createdDate: str
7+
id: int
8+
lastUpdatedDate: Optional[str]
9+
pin: int
10+
style: str
11+
title: str
12+
13+
class Monitor(BaseModel):
14+
id: int
15+
maintenance: Optional[bool]
16+
name: str
17+
sendUrl: int
18+
19+
class PublicGroup(BaseModel):
20+
id: int
21+
monitorList: List[Monitor]
22+
name: str
23+
weight: int
24+
25+
class StatusPage(BaseModel):
26+
customCSS: Optional[str] = None
27+
description: Optional[str]
28+
domainNameList: List[HttpUrl]
29+
footerText: Optional[str]
30+
googleAnalyticsId: Optional[str]
31+
icon: str
32+
id: int
33+
incident: Optional[Incident]
34+
maintenanceList: Optional[List]
35+
published: bool
36+
showPoweredBy: bool
37+
showTags: bool
38+
slug: constr(min_length=1)
39+
theme: str
40+
title: str
41+
publicGroupList: Optional[List[PublicGroup]]
42+
43+
class StatusPageList(BaseModel):
44+
statuspages: List[StatusPage]
45+
46+
class AddStatusPageRequest(BaseModel):
47+
slug: Optional[str] = None
48+
title: Optional[str] = None
49+
msg: Optional[str] = None
50+
51+
class AddStatusPageResponse(BaseModel):
52+
msg: Optional[str] = None
53+
class SaveStatusPageRequest(BaseModel):
54+
id: int
55+
title: str
56+
slug: constr(min_length=1)
57+
description: Optional[str] = None
58+
theme: Optional[str] = "light"
59+
published: Optional[bool] = True
60+
showTags: Optional[bool] = False
61+
domainNameList: Optional[List[HttpUrl]] = None
62+
googleAnalyticsId: Optional[str] = None
63+
customCSS: Optional[str] = ""
64+
footerText: Optional[str] = None
65+
showPoweredBy: Optional[bool] = True
66+
icon: Optional[str] = "/icon.svg"
67+
publicGroupList: Optional[List] = None
68+
69+
class SaveStatusPageResponse(BaseModel):
70+
detail: str
71+
72+
class DeleteStatusPageResponse(BaseModel):
73+
detail: str = "Status page deleted"

β€Žapp/utils/admin.pyβ€Ž

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,4 @@ async def check_admin():
66
admin_obj = await Users.get_or_none(username="admin")
77
if not admin_obj:
88
admin_obj = await Users.create(username="admin", password_hash=hash_password(settings.ADMIN_PASSWORD))
9-
109
return admin_obj

β€Ždb/test.sqlite3β€Ž

0 Bytes
Binary file not shown.

β€Ždb/test.sqlite3-shmβ€Ž

-32 KB
Binary file not shown.

0 commit comments

Comments
Β (0)