Compare commits

...

43 Commits

Author SHA1 Message Date
Oscar Krause
ce6283d4fa Merge branch 'ui' into 'main'
UI

See merge request oscar.krause/fastapi-dls!37
2025-03-12 13:41:06 +01:00
Oscar Krause
584eee41ef Merge branch 'dev' into 'main'
fixed logging

See merge request oscar.krause/fastapi-dls!47
2025-03-12 13:40:45 +01:00
Oscar Krause
25658cb1fb code styling 2025-03-12 11:41:58 +01:00
Oscar Krause
43fdf1170c Reverse Engineering Notes.md bearbeiten 2025-03-12 08:44:37 +01:00
Oscar Krause
3f5fcbebb3 fixed logging 2025-03-11 22:04:35 +01:00
Oscar Krause
0d8d546545 typos 2025-01-21 07:31:18 +01:00
Oscar Krause
b1c7d4f4b4 Merge branch 'main' into ui
# Conflicts:
#	.DEBIAN/control
#	.PKGBUILD/PKGBUILD
2024-12-16 15:38:41 +01:00
Oscar Krause
f95ba1347f Merge branch 'main' into ui
# Conflicts:
#	.PKGBUILD/PKGBUILD
2024-11-21 11:14:32 +01:00
Oscar Krause
9926f8248e fixes 2024-11-15 06:33:45 +01:00
Oscar Krause
36094276ea fixes 2024-11-14 07:23:12 +01:00
Oscar Krause
04bbca1a3a added "branchVersion" to origin table (if matrix is present) 2024-11-14 07:13:30 +01:00
Oscar Krause
486ebdb4fb implemented origins & leases auto refresh button 2024-11-14 07:12:33 +01:00
Oscar Krause
ef78fec58a fixed setTimeout to setInterval 2024-11-14 07:12:33 +01:00
Oscar Krause
9283129a08 updated bootstrap icons to 1.11.4 2024-11-14 07:12:33 +01:00
Oscar Krause
1bf8d27dbe updated bootstrap to 5.3.3 2024-11-14 07:12:33 +01:00
Oscar Krause
5fafda1123 implemented origins & leases counter for sidebar 2024-11-14 07:12:33 +01:00
Oscar Krause
227ee8cfe1 code styling 2024-11-14 07:12:33 +01:00
Oscar Krause
8f4ed2d9ff improved dashboard config view 2024-11-14 07:12:32 +01:00
Oscar Krause
87a79815dd implemented endpoint for expired origins (origins without active leases) 2024-11-14 07:10:01 +01:00
Oscar Krause
d25921dbb0 requirements.txt updated 2024-11-14 07:10:01 +01:00
Oscar Krause
161b48be8e removed "url_for" to prevent proxy http/https missmatch 2024-11-14 07:10:01 +01:00
Oscar Krause
90c1ce979d added meta tag for "Content-Security-Policy" 2024-11-14 07:10:01 +01:00
Oscar Krause
5d12226e9c fixed layouts path 2024-11-14 07:10:01 +01:00
Oscar Krause
0e39f1849b implemented button for delete all expired leases 2024-11-14 07:08:50 +01:00
Oscar Krause
77dd0922d8 fixed Origin.delete() 2024-11-14 07:08:50 +01:00
Oscar Krause
64327c3a64 fixes 2024-11-14 07:08:50 +01:00
Oscar Krause
0769a7fcaa helper.js - added "lease_renewal" to table 2024-11-14 07:08:50 +01:00
Oscar Krause
f9ac3786ab fixed origin null pointer exception on leases table 2024-11-14 07:08:19 +01:00
Oscar Krause
956f92eca6 PKGBUILD - fixed jinja dependency 2024-11-14 07:08:19 +01:00
Oscar Krause
a61ce2d478 dashboard_origins.html - implemented confirmation before deleting all origins 2024-11-14 07:08:19 +01:00
Oscar Krause
3a3401fb90 implemented refresh button 2024-11-14 07:08:19 +01:00
Oscar Krause
e04b4e09d9 display hostname as title on leases table 2024-11-14 07:08:19 +01:00
Oscar Krause
0c86f3275d display hostname as title on leases table 2024-11-14 07:08:19 +01:00
Oscar Krause
fc00dab5c9 fixed refreshing data after deleting some origin or lease 2024-11-14 07:07:50 +01:00
Oscar Krause
dcbcba88cb implemented delete origin endpoint for frontend 2024-11-14 07:07:50 +01:00
Oscar Krause
c8df32375a fixed timezone handling 2024-11-14 07:07:48 +01:00
Oscar Krause
2c9e8a8b0b sort leases by expires 2024-11-14 07:03:54 +01:00
Oscar Krause
02b2d5cbe8 sort origins by hostname 2024-11-14 07:03:54 +01:00
Oscar Krause
a77d3aa623 fixed origin table 2024-11-14 07:03:54 +01:00
Oscar Krause
783d95ab0d added jinja2 dependency 2024-11-14 07:03:51 +01:00
Oscar Krause
a6b82f0466 fixed imports and tests 2024-11-14 07:03:25 +01:00
Oscar Krause
46822bca80 added config values to dashboard 2024-11-14 07:00:50 +01:00
Oscar Krause
a30b8d039b implemented basic ui 2024-11-14 07:00:46 +01:00
35 changed files with 882 additions and 96 deletions

View File

@ -2,7 +2,7 @@ Package: fastapi-dls
Version: 0.0
Architecture: all
Maintainer: Oscar Krause oscar.krause@collinwebdesigns.de
Depends: python3, python3-fastapi, python3-uvicorn, python3-dotenv, python3-dateutil, python3-josepy, python3-sqlalchemy, python3-pycryptodome, python3-markdown, uvicorn, openssl
Depends: python3, python3-fastapi, python3-uvicorn, python3-dotenv, python3-dateutil, python3-josepy, python3-sqlalchemy, python3-pycryptodome, python3-markdown, python3-jinja2, uvicorn, openssl
Recommends: curl
Installed-Size: 10240
Homepage: https://git.collinwebdesigns.de/oscar.krause/fastapi-dls

View File

@ -8,7 +8,7 @@ pkgdesc='NVIDIA DLS server implementation with FastAPI'
arch=('any')
url='https://git.collinwebdesigns.de/oscar.krause/fastapi-dls'
license=('MIT')
depends=('python' 'python-jose' 'python-starlette' 'python-httpx' 'python-fastapi' 'python-dotenv' 'python-dateutil' 'python-sqlalchemy' 'python-pycryptodome' 'uvicorn' 'python-markdown' 'openssl')
depends=('python' 'python-jose' 'python-starlette' 'python-httpx' 'python-fastapi' 'python-dotenv' 'python-dateutil' 'python-sqlalchemy' 'python-pycryptodome' 'python-jinja' 'uvicorn' 'python-markdown' 'openssl')
provider=("$pkgname")
install="$pkgname.install"
backup=('etc/default/fastapi-dls')
@ -37,17 +37,52 @@ check() {
}
package() {
# create directories
install -d "$pkgdir/usr/share/doc/$pkgname"
install -d "$pkgdir/var/lib/$pkgname/cert"
# copy docs & static files
cp -r "$srcdir/$pkgname/doc"/* "$pkgdir/usr/share/doc/$pkgname/"
install -Dm644 "$srcdir/$pkgname/README.md" "$pkgdir/usr/share/doc/$pkgname/README.md"
install -Dm644 "$srcdir/$pkgname/version.env" "$pkgdir/usr/share/doc/$pkgname/version.env"
sed -i "s/README.md/\/usr\/share\/doc\/$pkgname\/README.md/g" "$srcdir/$pkgname/app/main.py"
# copy main app python files
sed -i "s/join(dirname(__file__), 'cert\//join('\/var\/lib\/$pkgname', 'cert\//g" "$srcdir/$pkgname/app/main.py"
install -Dm755 "$srcdir/$pkgname/app/main.py" "$pkgdir/opt/$pkgname/main.py"
install -Dm755 "$srcdir/$pkgname/app/orm.py" "$pkgdir/opt/$pkgname/orm.py"
install -Dm755 "$srcdir/$pkgname/app/util.py" "$pkgdir/opt/$pkgname/util.py"
# copy static asset files
install -Dm755 "$srcdir/$pkgname/app/static/assets/css/bootstrap.min.css" "$pkgdir/opt/$pkgname/static/assets/css/bootstrap.min.css"
install -Dm755 "$srcdir/$pkgname/app/static/assets/css/bootstrap-icons.min.css" "$pkgdir/opt/$pkgname/static/assets/css/bootstrap-icons.min.css"
install -Dm755 "$srcdir/$pkgname/app/static/assets/css/custom.css" "$pkgdir/opt/$pkgname/static/assets/css/custom.css"
install -Dm755 "$srcdir/$pkgname/app/static/assets/css/dashboard.css" "$pkgdir/opt/$pkgname/static/assets/css/dashboard.css"
install -Dm755 "$srcdir/$pkgname/app/static/assets/fonts/bootstrap-icons.woff" "$pkgdir/opt/$pkgname/static/assets/fonts/bootstrap-icons.woff"
install -Dm755 "$srcdir/$pkgname/app/static/assets/fonts/bootstrap-icons.woff2" "$pkgdir/opt/$pkgname/static/assets/fonts/bootstrap-icons.woff2"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/android-chrome-192x192.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/android-chrome-192x192.png"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/android-chrome-512x512.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/android-chrome-512x512.png"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/apple-touch-icon.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/apple-touch-icon.png"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/favicon.ico" "$pkgdir/opt/$pkgname/static/assets/img/favicons/favicon.ico"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/favicon-16x16.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/favicon-16x16.png"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/favicon-32x32.png" "$pkgdir/opt/$pkgname/static/assets/img/favicons/favicon-32x32.png"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/favicons/manifest.json" "$pkgdir/opt/$pkgname/static/assets/img/favicons/manifest.json"
install -Dm755 "$srcdir/$pkgname/app/static/assets/img/logo.png" "$pkgdir/opt/$pkgname/static/assets/img/logo.png"
install -Dm755 "$srcdir/$pkgname/app/static/assets/js/bootstrap.min.js" "$pkgdir/opt/$pkgname/static/assets/js/bootstrap.min.js"
install -Dm755 "$srcdir/$pkgname/app/static/assets/js/helper.js" "$pkgdir/opt/$pkgname/static/assets/js/helper.js"
install -Dm755 "$srcdir/$pkgname/app/static/assets/js/popper.min.js" "$pkgdir/opt/$pkgname/static/assets/js/popper.min.js"
install -Dm755 "$srcdir/$pkgname/app/templates/components/navbar.html" "$pkgdir/opt/$pkgname/templates/components/navbar.html"
install -Dm755 "$srcdir/$pkgname/app/templates/components/sidebar.html" "$pkgdir/opt/$pkgname/templates/components/sidebar.html"
install -Dm755 "$srcdir/$pkgname/app/templates/layouts/base.html" "$pkgdir/opt/$pkgname/templates/layouts/base.html"
install -Dm755 "$srcdir/$pkgname/app/templates/layouts/bootstrap.html" "$pkgdir/opt/$pkgname/templates/layouts/bootstrap.html"
install -Dm755 "$srcdir/$pkgname/app/templates/layouts/bootstrap-dashboard.html" "$pkgdir/opt/$pkgname/templates/layouts/bootstrap-dashboard.html"
install -Dm755 "$srcdir/$pkgname/app/templates/views/dashboard.html" "$pkgdir/opt/$pkgname/templates/views/dashboard.html"
install -Dm755 "$srcdir/$pkgname/app/templates/views/dashboard_leases.html" "$pkgdir/opt/$pkgname/templates/views/dashboard_leases.html"
install -Dm755 "$srcdir/$pkgname/app/templates/views/dashboard_origins.html" "$pkgdir/opt/$pkgname/templates/views/dashboard_origins.html"
install -Dm755 "$srcdir/$pkgname/app/templates/views/dashboard_readme.html" "$pkgdir/opt/$pkgname/templates/views/dashboard_readme.html"
install -Dm755 "$srcdir/$pkgname/app/templates/views/index.html" "$pkgdir/opt/$pkgname/templates/views/index.html"
# copy service files
install -Dm644 "$srcdir/$pkgname.default" "$pkgdir/etc/default/$pkgname"
install -Dm644 "$srcdir/$pkgname.service" "$pkgdir/usr/lib/systemd/system/$pkgname.service"
install -Dm644 "$srcdir/$pkgname.tmpfiles" "$pkgdir/usr/lib/tmpfiles.d/$pkgname.conf"

View File

@ -734,6 +734,9 @@ The error message can safely be ignored (since we have no license limitation :P)
**18.x Drivers are not supported on FastAPI-DLS Versions < 1.6.0**
<details>
<summary>Show Table</summary>
Successfully tested with this package versions.
| vGPU Suftware | Driver Branch | Linux vGPU Manager | Linux Driver | Windows Driver | Release Date | EOL Date |
@ -757,6 +760,8 @@ Successfully tested with this package versions.
| `15.4` | R525 | `525.147.01` | `525.147.05` | `529.19` | June 2023 | December 2023 |
| `14.4` | R510 | `510.108.03` | `510.108.03` | `514.08` | December 2022 | February 2023 |
</details>
- https://docs.nvidia.com/grid/index.html
- https://docs.nvidia.com/grid/gpus-supported-by-vgpu.html

View File

@ -18,7 +18,9 @@ from jose.constants import ALGORITHMS
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from starlette.middleware.cors import CORSMiddleware
from starlette.responses import StreamingResponse, JSONResponse as JSONr, HTMLResponse as HTMLr, Response, RedirectResponse
from starlette.responses import StreamingResponse, JSONResponse as JSONr, Response, RedirectResponse
from starlette.staticfiles import StaticFiles
from starlette.templating import Jinja2Templates
from orm import Origin, Lease, init as db_init, migrate
from util import load_key, load_file
@ -87,6 +89,8 @@ async def lifespan(_: FastAPI):
config = dict(openapi_url=None, docs_url=None, redoc_url=None) # dict(openapi_url='/-/openapi.json', docs_url='/-/docs', redoc_url='/-/redoc')
app = FastAPI(title='FastAPI-DLS', description='Minimal Delegated License Service (DLS).', version=VERSION, lifespan=lifespan, **config)
app.mount('/static', StaticFiles(directory=join(dirname(__file__), 'static'), html=True), name='static')
templates = Jinja2Templates(directory=join(dirname(__file__), 'templates'))
app.debug = DEBUG
app.add_middleware(
@ -105,26 +109,8 @@ def __get_token(request: Request) -> dict:
return jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False})
# Endpoints
@app.get('/', summary='Index')
async def index():
return RedirectResponse('/-/readme')
@app.get('/-/', summary='* Index')
async def _index():
return RedirectResponse('/-/readme')
@app.get('/-/health', summary='* Health')
async def _health():
return JSONr({'status': 'up'})
@app.get('/-/config', summary='* Config', description='returns environment variables.')
async def _config():
return JSONr({
def __json_config() -> dict:
return {
'VERSION': str(VERSION),
'COMMIT': str(COMMIT),
'DEBUG': str(DEBUG),
@ -138,52 +124,66 @@ async def _config():
'LEASE_RENEWAL_PERIOD': str(LEASE_RENEWAL_PERIOD),
'CORS_ORIGINS': str(CORS_ORIGINS),
'TZ': str(TZ),
})
# static / calculated
'LEASE_RENEWAL_DELTA': str(LEASE_RENEWAL_DELTA),
'LEASE_CALCULATED_RENEWAL': str(Lease.calculate_renewal(LEASE_RENEWAL_PERIOD, LEASE_RENEWAL_DELTA)),
'CLIENT_TOKEN_EXPIRE_DELTA': str(CLIENT_TOKEN_EXPIRE_DELTA),
}
# Endpoints
@app.get('/', summary='* Index')
async def index():
return RedirectResponse('/-/')
@app.get('/-/', summary='* Index')
async def _index(request: Request):
return templates.TemplateResponse(name='views/index.html', context={'request': request, 'VERSION': VERSION})
@app.get('/-/health', summary='* Health')
async def _health():
return JSONr({'status': 'up'})
@app.get('/-/config', summary='* Config', description='returns environment variables.')
async def _config():
return JSONr(__json_config())
@app.get('/-/readme', summary='* Readme')
async def _readme():
async def _readme(request: Request):
from markdown import markdown
content = load_file(join(dirname(__file__), '../README.md')).decode('utf-8')
return HTMLr(markdown(text=content, extensions=['tables', 'fenced_code', 'md_in_html', 'nl2br', 'toc']))
markdown = markdown(text=content, extensions=['tables', 'fenced_code', 'md_in_html', 'nl2br', 'toc'])
context = {'request': request, 'VERSION': VERSION, 'markdown': markdown }
return templates.TemplateResponse(name='views/dashboard_readme.html', context=context)
@app.get('/-/manage', summary='* Management UI')
async def _manage(request: Request):
response = '''
<!DOCTYPE html>
<html>
<head>
<title>FastAPI-DLS Management</title>
</head>
<body>
<button onclick="deleteOrigins()">delete ALL origins and their leases</button>
<button onclick="deleteLease()">delete specific lease</button>
context = {'request': request, 'VERSION': VERSION}
return templates.TemplateResponse(name='views/manage.html', context=context)
<script>
function deleteOrigins() {
const response = confirm('Are you sure you want to delete all origins and their leases?');
if (response) {
var xhr = new XMLHttpRequest();
xhr.open("DELETE", '/-/origins', true);
xhr.send();
}
}
function deleteLease(lease_ref) {
if(lease_ref === undefined)
lease_ref = window.prompt("Please enter 'lease_ref' which should be deleted");
if(lease_ref === null || lease_ref === "")
return
var xhr = new XMLHttpRequest();
xhr.open("DELETE", `/-/lease/${lease_ref}`, true);
xhr.send();
}
</script>
</body>
</html>
'''
return HTMLr(response)
@app.get('/-/dashboard', summary='* Dashboard')
async def _dashboard(request: Request):
context = {'request': request, 'VERSION': VERSION, 'CONFIG': __json_config()}
return templates.TemplateResponse(name='views/dashboard.html', context=context)
@app.get('/-/dashboard/origins', summary='* Dashboard - Origins')
async def _dashboard_origins(request: Request):
context = {'request': request, 'VERSION': VERSION}
return templates.TemplateResponse(name='views/dashboard_origins.html', context=context)
@app.get('/-/dashboard/leases', summary='* Dashboard - Leases')
async def _dashboard_origins(request: Request):
context = {'request': request, 'VERSION': VERSION}
return templates.TemplateResponse(name='views/dashboard_leases.html', context=context)
@app.get('/-/origins', summary='* Origins')
@ -206,6 +206,19 @@ async def _origins_delete(request: Request):
return Response(status_code=201)
@app.delete('/-/origins/expired', summary='* Delete all Origins without active Lease')
async def _origins_delete_expired(request: Request):
Origin.delete_expired(db)
return Response(status_code=201)
@app.delete('/-/origins/{origin_ref}', summary='* Delete specific Origin')
async def _origins_delete_origin_ref(request: Request, origin_ref: str):
if Origin.delete(db, [origin_ref]) == 1:
return Response(status_code=201)
return JSONr(status_code=404, content={'status': 404, 'detail': 'lease not found'})
@app.get('/-/leases', summary='* Leases')
async def _leases(request: Request, origin: bool = False):
session = sessionmaker(bind=db)()
@ -222,13 +235,13 @@ async def _leases(request: Request, origin: bool = False):
return JSONr(response)
@app.delete('/-/leases/expired', summary='* Leases')
@app.delete('/-/leases/expired', summary='* Delete all expired Leases')
async def _lease_delete_expired(request: Request):
Lease.delete_expired(db)
return Response(status_code=201)
@app.delete('/-/lease/{lease_ref}', summary='* Lease')
@app.delete('/-/lease/{lease_ref}', summary='* Delete specific Lease')
async def _lease_delete(request: Request, lease_ref: str):
if Lease.delete(db, lease_ref) == 1:
return Response(status_code=201)
@ -287,7 +300,7 @@ async def auth_v1_origin(request: Request):
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC)
origin_ref = j.get('candidate_origin_ref')
logging.info(f'> [ origin ]: {origin_ref}: {j}')
logger.info(f'> [ origin ]: {origin_ref}: {j}')
data = Origin(
origin_ref=origin_ref,
@ -317,7 +330,7 @@ async def auth_v1_origin_update(request: Request):
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC)
origin_ref = j.get('origin_ref')
logging.info(f'> [ update ]: {origin_ref}: {j}')
logger.info(f'> [ update ]: {origin_ref}: {j}')
data = Origin(
origin_ref=origin_ref,
@ -344,7 +357,7 @@ async def auth_v1_code(request: Request):
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC)
origin_ref = j.get('origin_ref')
logging.info(f'> [ code ]: {origin_ref}: {j}')
logger.info(f'> [ code ]: {origin_ref}: {j}')
delta = relativedelta(minutes=15)
expires = cur_time + delta
@ -381,7 +394,7 @@ async def auth_v1_token(request: Request):
return JSONr(status_code=400, content={'status': 400, 'title': 'invalid token', 'detail': str(e)})
origin_ref = payload.get('origin_ref')
logging.info(f'> [ auth ]: {origin_ref}: {j}')
logger.info(f'> [ auth ]: {origin_ref}: {j}')
# validate the code challenge
challenge = b64enc(sha256(j.get('code_verifier').encode('utf-8')).digest()).rstrip(b'=').decode('utf-8')
@ -424,7 +437,7 @@ async def leasing_v1_lessor(request: Request):
origin_ref = token.get('origin_ref')
scope_ref_list = j.get('scope_ref_list')
logging.info(f'> [ create ]: {origin_ref}: create leases for scope_ref_list {scope_ref_list}')
logger.info(f'> [ create ]: {origin_ref}: create leases for scope_ref_list {scope_ref_list}')
lease_result_list = []
for scope_ref in scope_ref_list:
@ -468,7 +481,7 @@ async def leasing_v1_lessor_lease(request: Request):
origin_ref = token.get('origin_ref')
active_lease_list = list(map(lambda x: x.lease_ref, Lease.find_by_origin_ref(db, origin_ref)))
logging.info(f'> [ leases ]: {origin_ref}: found {len(active_lease_list)} active leases')
logger.info(f'> [ leases ]: {origin_ref}: found {len(active_lease_list)} active leases')
response = {
"active_lease_list": active_lease_list,
@ -486,7 +499,7 @@ async def leasing_v1_lease_renew(request: Request, lease_ref: str):
token, cur_time = __get_token(request), datetime.now(UTC)
origin_ref = token.get('origin_ref')
logging.info(f'> [ renew ]: {origin_ref}: renew {lease_ref}')
logger.info(f'> [ renew ]: {origin_ref}: renew {lease_ref}')
entity = Lease.find_by_origin_ref_and_lease_ref(db, origin_ref, lease_ref)
if entity is None:
@ -513,7 +526,7 @@ async def leasing_v1_lease_delete(request: Request, lease_ref: str):
token, cur_time = __get_token(request), datetime.now(UTC)
origin_ref = token.get('origin_ref')
logging.info(f'> [ return ]: {origin_ref}: return {lease_ref}')
logger.info(f'> [ return ]: {origin_ref}: return {lease_ref}')
entity = Lease.find_by_lease_ref(db, lease_ref)
if entity.origin_ref != origin_ref:
@ -542,7 +555,7 @@ async def leasing_v1_lessor_lease_remove(request: Request):
released_lease_list = list(map(lambda x: x.lease_ref, Lease.find_by_origin_ref(db, origin_ref)))
deletions = Lease.cleanup(db, origin_ref)
logging.info(f'> [ remove ]: {origin_ref}: removed {deletions} leases')
logger.info(f'> [ remove ]: {origin_ref}: removed {deletions} leases')
response = {
"released_lease_list": released_lease_list,
@ -564,7 +577,7 @@ async def leasing_v1_lessor_shutdown(request: Request):
released_lease_list = list(map(lambda x: x.lease_ref, Lease.find_by_origin_ref(db, origin_ref)))
deletions = Lease.cleanup(db, origin_ref)
logging.info(f'> [ shutdown ]: {origin_ref}: removed {deletions} leases')
logger.info(f'> [ shutdown ]: {origin_ref}: removed {deletions} leases')
response = {
"released_lease_list": released_lease_list,
@ -587,7 +600,7 @@ if __name__ == '__main__':
#
###
logging.info(f'> Starting dev-server ...')
logger.info(f'> Starting dev-server ...')
ssl_keyfile = join(dirname(__file__), 'cert/webserver.key')
ssl_certfile = join(dirname(__file__), 'cert/webserver.crt')

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,56 @@
/*
Original: #76b900
Darken 1: #5DA000 (10%)
Darken 2: #438600 (20%)
Darken 3: #2A6D00 (30%)
Darken 4: #105300 (40%)
Darken 5: #003A00 (50%)
*/
.text-primary {
color: #76b900 !important;
}
.lead {
color: #105300 !important;
}
.navbar-green {
background-color: #76b900 !important;
}
.navbar-brand {
background-color: transparent;
color: #ffffff;
}
.navbar-brand:focus, .navbar-brand:hover {
color: #fcfcfc;
}
.btn-primary {
background-color: #76b900 !important;
border-color: #76b900 !important;
}
.btn-primary:focus, .btn-primary:hover {
background-color: #5DA000 !important;
border-color: #5DA000 !important;
}
code {
color: #105300 !important;
}
.sidebar .nav-link.active {
color: #76b900 !important;
}
.sidebar .nav-link:focus, .sidebar .nav-link:hover {
color: #105300 !important;
}
.navbar-nav .nav-item .nav-link {
color: white !important;
}

View File

@ -0,0 +1,101 @@
body {
font-size: .875rem;
}
.feather {
width: 16px;
height: 16px;
vertical-align: text-bottom;
}
/*
* Sidebar
*/
.sidebar {
position: fixed;
top: 0;
/* rtl:raw:
right: 0;
*/
bottom: 0;
/* rtl:remove */
left: 0;
z-index: 100; /* Behind the navbar */
padding: 48px 0 0; /* Height of navbar */
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
}
@media (max-width: 767.98px) {
.sidebar {
top: 0;
}
}
.sidebar-sticky {
position: relative;
top: 0;
height: calc(100vh - 48px);
padding-top: .5rem;
overflow-x: hidden;
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
}
.sidebar .nav-link {
font-weight: 500;
color: #333;
}
.sidebar .nav-link .feather {
margin-right: 4px;
color: #727272;
}
.sidebar .nav-link.active {
color: #2470dc;
}
.sidebar .nav-link:hover .feather,
.sidebar .nav-link.active .feather {
color: inherit;
}
.sidebar-heading {
font-size: .75rem;
text-transform: uppercase;
}
/*
* Navbar
*/
.navbar-brand {
padding-top: .75rem;
padding-bottom: .75rem;
font-size: 1rem;
background-color: rgba(0, 0, 0, .25);
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .25);
}
.navbar .navbar-toggler {
top: .25rem;
right: 1rem;
}
.navbar .form-control {
padding: .75rem 1rem;
border-width: 0;
border-radius: 0;
}
.form-control-dark {
color: #fff;
background-color: rgba(255, 255, 255, .1);
border-color: rgba(255, 255, 255, .1);
}
.form-control-dark:focus {
border-color: transparent;
box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

7
app/static/assets/js/bootstrap.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,140 @@
async function fetchConfig(element) {
let xhr = new XMLHttpRequest();
xhr.open("GET", '/-/config', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
element.innerHTML = JSON.stringify(JSON.parse(xhr.response),null,2);
}
};
xhr.send();
}
async function fetchOriginsWithLeases(element) {
let xhr = new XMLHttpRequest();
xhr.open("GET", '/-/origins?leases=true', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
const x = JSON.parse(xhr.response)
console.debug(x)
element.innerHTML = ''
let table = document.createElement('table')
table.classList.add('table', 'mt-4');
let thead = document.createElement('thead');
thead.innerHTML = `
<tr>
<th scope="col">origin</th>
<th scope="col">hostname</th>
<th scope="col">OS</th>
<th scope="col">driver version</th>
<th scope="col">leases</th>
</tr>`
table.appendChild(thead)
let tbody = document.createElement('thead');
x.sort((a, b) => a.hostname.localeCompare(b.hostname)).forEach((o) => {
let row = document.createElement('tr');
const branchVersion= o.$driver ? `(<code>${o.$driver.branch_version}</code>) ` : ''
row.innerHTML = `
<td><code>${o.origin_ref}</code></td>
<td>${o.hostname}</td>
<td>${o.os_platform} (${o.os_version})</td>
<td>${branchVersion}<code>${o.guest_driver_version}</code></td>
<td>${o.leases.map(x => `<code title="expires: ${x.lease_expires}">${x.lease_ref}</code>`).join(', ')}</td>`
tbody.appendChild(row);
})
table.appendChild(tbody)
element.appendChild(table)
}
};
xhr.send();
}
async function fetchLeases(element) {
// datetime config
const dtc = {
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
timeZoneName: "short"
}
let xhr = new XMLHttpRequest();
xhr.open("GET", '/-/leases?origin=true', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
const x = JSON.parse(xhr.response)
console.debug(x)
element.innerHTML = ''
let table = document.createElement('table')
table.classList.add('table', 'mt-4');
let thead = document.createElement('thead');
thead.innerHTML = `
<tr>
<th scope="col">lease</th>
<th scope="col">created</th>
<th scope="col">updated</th>
<th scope="col">next renew</th>
<th scope="col">expires</th>
<th scope="col">origin</th>
</tr>`
table.appendChild(thead)
let tbody = document.createElement('thead');
x.sort((a, b) => new Date(a.lease_expires) - new Date(b.lease_expires)).forEach((o) => {
let row = document.createElement('tr');
row.innerHTML = `
<td><code>${o.lease_ref}</code></td>
<td>${new Date(o.lease_created).toLocaleDateString('system', dtc)}</td>
<td>${new Date(o.lease_updated).toLocaleDateString('system', dtc)}</td>
<td>${new Date(o.lease_renewal).toLocaleDateString('system', dtc)}</td>
<td>${new Date(o.lease_expires).toLocaleDateString('system', dtc)}</td>
<td><code title="hostname: ${o.origin?.hostname}">${o.origin_ref}</code></td>`
tbody.appendChild(row);
})
table.appendChild(tbody)
element.appendChild(table)
}
};
xhr.send();
}
async function deleteOrigins() {
let xhr = new XMLHttpRequest();
xhr.open("DELETE", '/-/origins', true);
xhr.send();
}
async function deleteOrigin(origin_ref) {
if (origin_ref === undefined)
origin_ref = window.prompt("Please enter 'origin_ref' which should be deleted");
if (origin_ref === null || origin_ref === "")
return
let xhr = new XMLHttpRequest();
xhr.open("DELETE", `/-/origins/${origin_ref}`, true);
xhr.send();
}
async function deleteExpiredOrigins() {
let xhr = new XMLHttpRequest();
xhr.open("DELETE", `/-/origins/expired`, true);
xhr.send();
}
async function deleteLease(lease_ref) {
if (lease_ref === undefined)
lease_ref = window.prompt("Please enter 'lease_ref' which should be deleted");
if (lease_ref === null || lease_ref === "")
return
let xhr = new XMLHttpRequest();
xhr.open("DELETE", `/-/lease/${lease_ref}`, true);
xhr.send();
}
async function deleteExpiredLeases() {
let xhr = new XMLHttpRequest();
xhr.open("DELETE", `/-/leases/expired`, true);
xhr.send();
}

5
app/static/assets/js/popper.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
<header class="navbar navbar-expand-md navbar-green sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="/-/">FastAPI-DLS {{ VERSION }}</a>
<button class="navbar-toggler position-absolute d-lg-none collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</header>

View File

@ -0,0 +1,93 @@
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="position-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link {{ 'active' if request.url.path == '/-/dashboard' }}" aria-current="page" href="/-/dashboard">
<i class="bi-house-door"></i> Dashboard
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.url.path == '/-/dashboard/origins' }}" aria-current="page" href="/-/dashboard/origins">
<i class="bi-pc-display-horizontal"></i> Origins <span id="origin-cnt" class="badge text-bg-secondary"></span>
</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.url.path == '/-/dashboard/leases' }}" aria-current="page" href="/-/dashboard/leases">
<i class="bi-layers"></i> Leases <span id="lease-cnt" class="badge text-bg-secondary"></span>
</a>
</li>
</ul>
<script type="application/javascript">
function loadLOriginCnt() {
let xhr = new XMLHttpRequest();
xhr.open("GET", '/-/origins?leases=false', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
const x = JSON.parse(xhr.response)
document.getElementById('origin-cnt').innerHTML = x.length
}
};
xhr.send();
}
function loadLeaseCnt() {
let xhr = new XMLHttpRequest();
xhr.open("GET", '/-/leases?origin=false', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
const x = JSON.parse(xhr.response)
document.getElementById('lease-cnt').innerHTML = x.length
}
};
xhr.send();
}
// load initial
loadLOriginCnt()
loadLeaseCnt()
// refresh every 5 seconds
setInterval(() => {
loadLOriginCnt()
loadLeaseCnt()
}, 5000);
</script>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted text-uppercase">
<span>Help</span>
</h6>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link {{ 'active' if request.url.path == '/-/readme' }}" aria-current="page" href="/-/readme">
<i class="bi-question-circle"></i> Readme
</a>
</li>
<li class="nav-item">
<a class="nav-link" aria-current="page" href="https://git.collinwebdesigns.de/oscar.krause/fastapi-dls" target="_blank">
<i class="bi-git"></i> Git Repo
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted text-uppercase">
<span>Integrations</span>
</h6>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/-/doc" target="_blank">
<i class="bi-file-text"></i> Swagger UI
</a>
</li>
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/-/redoc" target="_blank">
<i class="bi-file-text"></i> Redoc
</a>
</li>
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/-/openapi.json" target="_blank">
<i class="bi bi-filetype-json"></i> OpenAPI JSON
</a>
</li>
</ul>
</div>
</nav>

View File

@ -0,0 +1,33 @@
<!doctype html>
<html lang="en" class="h-100">
<head>
{% block title %}
<title>FastAPI-DLS</title>
{% endblock %}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">
<link rel="icon" href="/static/assets/img/favicons/favicon-32x32.png" sizes="32x32" type="image/png">
<link rel="icon" href="/static/assets/img/favicons/favicon-16x16.png" sizes="16x16" type="image/png">
<link rel="manifest" href="/static/assets/img/favicons/manifest.json">
<link rel="icon" href="/static/assets/img/favicons/favicon.ico">
<link rel="apple-touch-icon" href="/static/assets/img/favicons/apple-touch-icon.png" sizes="180x180">
{% block styles %}
{% endblock %}
<link rel="stylesheet" type="text/css" href="/static/assets/css/custom.css">
</head>
<body class="d-flex flex-column {% block body_class %}{% endblock %}">
{% block body %}
{% endblock %}
<script src="/static/assets/js/helper.js"></script>
{% block scripts %}
{% endblock %}
</body>
</html>

View File

@ -0,0 +1,16 @@
{% extends 'layouts/bootstrap.html' %}
{% block body %}
{% include 'components/navbar.html' %}
<div class="container-fluid">
<div class="row">
{% include 'components/sidebar.html' %}
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
{% block content %}
{% endblock %}
</main>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,14 @@
{% extends 'layouts/base.html' %}
{% block styles %}
{{ super() }}
<link rel="stylesheet" type="text/css" href="/static/assets/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="/static/assets/css/bootstrap-icons.min.css">
<link rel="stylesheet" type="text/css" href="/static/assets/css/dashboard.css">
<script src="/static/assets/js/popper.min.js"></script>
<script src="/static/assets/js/bootstrap.min.js"></script>
{% endblock %}

View File

@ -0,0 +1,69 @@
{% extends 'layouts/bootstrap-dashboard.html' %}
{% block title %}
<title>Dashboard</title>
{% endblock %}
{% block content %}
<div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboard</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="downloadClientToken()">
<i class="bi bi-download"></i>
Client Token
</button>
</div>
</div>
</div>
<div class="p-5 mb-4 bg-light rounded-3">
<div class="container-fluid py-5">
<h1 class="display-5 fw-bold">FastAPI-DLS</h1>
<p class="col-md-8 fs-4">Minimal Delegated License Service (DLS).</p>
<a href="https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/releases" class="btn btn-primary btn-lg" target="_blank">
Releases &raquo;
</a>
</div>
</div>
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">Configuration</h5>
<h6 class="card-subtitle mb-2 text-body-secondary">
Using timezone: {{ CONFIG.TZ }}. Make sure this is correct and match your clients!
</h6>
<p class="card-text">
Your clients renew their license every {{ CONFIG.LEASE_CALCULATED_RENEWAL }}.<br/>
If the renewal fails, the license is {{ CONFIG.LEASE_RENEWAL_DELTA }} valid.<br/>
<br/>
Your client-token file (.tok) is valid for {{ CONFIG.CLIENT_TOKEN_EXPIRE_DELTA }}.
</p>
</div>
</div>
<div class="card">
<div class="card-body">
<pre id="config"></pre>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script type="application/javascript">
function downloadClientToken() {
window.open('/-/client-token', "_blank")
}
function load() {
const config = document.getElementById('config')
fetchConfig(config)
}
load()
</script>
{% endblock %}

View File

@ -0,0 +1,60 @@
{% extends 'layouts/bootstrap-dashboard.html' %}
{% block title %}
<title>Origins</title>
{% endblock %}
{% block content %}
<div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Leases <small>with origin</small></h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteLease().finally(() => load())">
delete lease
</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteExpiredLeases().finally(() => load())">
delete all expired leases
</button>
</div>
<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="load()" title="refresh">
<i class="bi bi-arrow-clockwise"></i>
</button>
<button id="btn-auto-refresh" type="button" class="btn btn-sm active">auto-refresh</button>
</div>
</div>
<div id="leases" class="mt-3"></div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script type="application/javascript">
let autoRefresh = true
function load() {
const leases = document.getElementById('leases')
fetchLeases(leases)
}
load()
setInterval(() => {
if(autoRefresh)
load()
}, 5000);
const btnAutoRefresh = document.getElementById('btn-auto-refresh')
btnAutoRefresh.addEventListener("click", () => {
if(btnAutoRefresh.classList.contains('active')) {
autoRefresh = false
btnAutoRefresh.classList.remove('active')
} else {
autoRefresh = false
btnAutoRefresh.classList.add('active')
}
}, true);
</script>
{% endblock %}

View File

@ -0,0 +1,70 @@
{% extends 'layouts/bootstrap-dashboard.html' %}
{% block title %}
<title>Origins</title>
{% endblock %}
{% block content %}
<div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Origins <small>with leases</small></h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteOrigin().finally(() => load())">
delete origin
</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteExpiredOrigins().finally(() => load())">
delete all expired origins
</button>
<button type="button" class="btn btn-sm btn-outline-danger" onclick="deleteOriginsWrapper()">
delete all
</button>
</div>
<button type="button" class="btn btn-sm btn-outline-secondary me-2" onclick="load()" title="refresh">
<i class="bi bi-arrow-clockwise"></i>
</button>
<button id="btn-auto-refresh" type="button" class="btn btn-sm active">auto-refresh</button>
</div>
</div>
<div id="origins" class="mt-3"></div>
</div>
{% endblock %}
{% block scripts %}
{{ super() }}
<script type="application/javascript">
let autoRefresh = true
function load() {
const origins = document.getElementById('origins')
fetchOriginsWithLeases(origins)
}
load()
function deleteOriginsWrapper() {
const response = confirm('Are you sure you want to delete all origins and their leases?');
if (response)
deleteOrigins().finally(() => load())
}
setInterval(() => {
if(autoRefresh)
load()
}, 5000);
const btnAutoRefresh = document.getElementById('btn-auto-refresh')
btnAutoRefresh.addEventListener("click", () => {
if(btnAutoRefresh.classList.contains('active')) {
autoRefresh = false
btnAutoRefresh.classList.remove('active')
} else {
autoRefresh = false
btnAutoRefresh.classList.add('active')
}
}, true);
</script>
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends 'layouts/bootstrap-dashboard.html' %}
{% block title %}
<title>Origins</title>
{% endblock %}
{% block content %}
<div>
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div class="overflow-hidden">
{{ markdown|safe }}
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,26 @@
{% extends 'layouts/bootstrap.html' %}
{% block title %}
<title>Index</title>
{% endblock %}
{% block body_class %}h-100{% endblock %}
{% block body %}
<main class="flex-shrink-0">
<div class="container">
<h1 class="mt-5 text-primary">FastAPI-DLS</h1>
<p class="lead">Minimal Delegated License Service (DLS).</p>
<p>
<a href="/-/dashboard">Dashboard</a>,
<a href="/-/readme">Readme</a>
</p>
</div>
</main>
<footer class="footer mt-auto py-3 bg-light">
<div class="container">
<span class="text-muted">FastAPI-DLS Version {{ VERSION }}</span>
</div>
</footer>
{% endblock %}

View File

@ -46,9 +46,10 @@ class NV:
self.log = logging.getLogger(self.__class__.__name__)
if NV.__DRIVER_MATRIX is None:
from os.path import join, dirname
from json import load as json_load
try:
file = open(NV.__DRIVER_MATRIX_FILENAME)
file = open(join(dirname(__file__), NV.__DRIVER_MATRIX_FILENAME))
NV.__DRIVER_MATRIX = json_load(file)
file.close()
self.log.debug(f'Successfully loaded "{NV.__DRIVER_MATRIX_FILENAME}".')

View File

@ -29,7 +29,7 @@ nvidia-gridd[2986]: Acquiring license. (Info: license.nvidia.space; NVIDIA RTX V
nvidia-gridd[2986]: License acquired successfully. (Info: license.nvidia.space, NVIDIA RTX Virtual Workstation; Expiry: 2023-1-29 22:3:0 GMT)
```
# DLS-Container File-System (Docker)
# Docker DLS-Container File-System
- More about Docker Images https://git.collinwebdesigns.de/nvidia/nls
@ -42,23 +42,6 @@ Files can be modified with `docker cp <container-id>:/venv/... /opt/localfile/..
Config-Variables are in `etc/dls/config/service_env.conf`.
## Dive / Docker image inspector
- `dive dls:appliance`
The source code is stored in `/venv/lib/python3.9/site-packages/nls_*`.
Image-Reference:
```
Tags: (unavailable)
Id: d1c7976a5d2b3681ff6c5a30f8187e4015187a83f3f285ba4a37a45458bd6b98
Digest: sha256:311223c5af7a298ec1104f5dc8c3019bfb0e1f77256dc3d995244ffb295a97
1f
Command:
#(nop) ADD file:c1900d3e3a29c29a743a8da86c437006ec5d2aa873fb24e48033b6bf492bb37b in /
```
## Site Key Uri - `/etc/dls/config/site_key_uri.bin`
@ -80,18 +63,43 @@ cat dls_db_password.bin | base64 -d > dls_db_password.bin.raw
openssl rsautl -decrypt -inkey /tmp/private-key.pem -in dls_db_password.bin.raw
```
# Database
# Docker Postgres-Container
- It's enough to manipulate database licenses. There must not be changed any line of code to bypass licensing
validations.
## Inspect
Valid users are `dls_writer` and `postgres`.
```shell
docker exec -it <dls:pgsql> psql -h localhost -U postgres
```
Or you can modify `docker-compose.yaml` to forward Postgres port.
## External Access
Or you can modify `docker-compose.yaml` to forward Postgres port. To create a superuser for external access, use `docker exec` from above and rund the following:
```sql
CREATE USER admin WITH LOGIN SUPERUSER PASSWORD 'admin';
```
# Dive / Docker image inspector
- `dive dls:appliance`
The source code is stored in `/venv/lib/python3.9/site-packages/nls_*`.
Image-Reference:
```
Tags: (unavailable)
Id: d1c7976a5d2b3681ff6c5a30f8187e4015187a83f3f285ba4a37a45458bd6b98
Digest: sha256:311223c5af7a298ec1104f5dc8c3019bfb0e1f77256dc3d995244ffb295a97
1f
Command:
#(nop) ADD file:c1900d3e3a29c29a743a8da86c437006ec5d2aa873fb24e48033b6bf492bb37b in /
```
# Logging / Stack Trace

View File

@ -6,3 +6,4 @@ python-dateutil==2.8.2
sqlalchemy==2.0.38
markdown==3.7
python-dotenv==1.0.1
jinja2==3.1.3

View File

@ -59,8 +59,8 @@ def test_readme():
assert response.status_code == 200
def test_manage():
response = client.get('/-/manage')
def test_dashboard():
response = client.get('/-/dashboard')
assert response.status_code == 200