Skip to content

Commit

Permalink
Initial commit of monitor api
Browse files Browse the repository at this point in the history
  • Loading branch information
markusweigelt committed Dec 12, 2023
1 parent 9fee686 commit ae0c0d1
Show file tree
Hide file tree
Showing 16 changed files with 485 additions and 38 deletions.
10 changes: 9 additions & 1 deletion ocrdmonitor/database/_ocrdjobrepository.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,12 @@ async def insert(self, job: OcrdJob) -> None:
await MongoOcrdJob(**asdict(job)).insert() # type: ignore

async def find_all(self) -> list[OcrdJob]:
return [OcrdJob(**j.dict(exclude={"id"})) for j in await MongoOcrdJob.find_all().to_list()]
return [
OcrdJob(**j.dict())
for j in await MongoOcrdJob.find_all()
.sort(-MongoOcrdJob.time_created)
.to_list()
]

async def get(self, id: str) -> OcrdJob:
return await MongoOcrdJob.get(id)
14 changes: 8 additions & 6 deletions ocrdmonitor/ocrdcontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ async def status_for(self, ocrd_job: OcrdJob) -> ProcessStatus | None:
return None

pid = await self._remote.read_file(f"/data/{ocrd_job.remotedir}/ocrd.pid")
process_statuses = await self._remote.process_status(int(pid))

for status in process_statuses:
if status.state == ProcessState.RUNNING:
return status
if pid :
process_statuses = await self._remote.process_status(int(pid))

if process_statuses:
return process_statuses[0]
for status in process_statuses:
if status.state == ProcessState.RUNNING:
return status

if process_statuses:
return process_statuses[0]

return None
33 changes: 27 additions & 6 deletions ocrdmonitor/protocols.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Collection, NamedTuple, Protocol
Expand All @@ -7,6 +6,7 @@
from ocrdmonitor.processstatus import ProcessStatus
from ocrdmonitor.server.settings import Settings

from pydantic import BaseModel, computed_field

class BrowserRestoringFactory(Protocol):
def __call__(
Expand Down Expand Up @@ -37,12 +37,12 @@ async def count(self) -> int:
...


@dataclass(frozen=True)
class OcrdJob:
class OcrdJob(BaseModel):
id: str
pid: int | None
return_code: int | None
time_created: datetime
time_terminated: datetime
time_terminated: datetime | None
process_id: str
task_id: str
process_dir: Path
Expand All @@ -52,23 +52,44 @@ class OcrdJob:
controller_address: str

@property
def is_running(self) -> bool:
def is_processing(self) -> bool:
return self.pid is not None

@property
def is_completed(self) -> bool:
return self.return_code is not None

@computed_field
@property
def workflow(self) -> str:
return Path(self.workflow_file).name

@computed_field
@property
def workspace(self) -> str:
return Path(self.process_dir).name

@computed_field
@property
def status(self) -> str:
if self.is_processing :
return "PROCESSING"
if self.is_completed :
if self.return_code == 0 :
return "SUCCESS"
else :
return "FAILURE"
return "UNDEFINED"


class JobRepository(Protocol):
async def insert(self, job: OcrdJob) -> None:
...

async def find_all(self) -> list[OcrdJob]:
async def find_one(self) -> list[OcrdJob]:
...

async def get(self) -> OcrdJob:
...


Expand Down
17 changes: 17 additions & 0 deletions ocrdmonitor/server/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from __future__ import annotations

from fastapi import APIRouter

from ocrdmonitor.protocols import Environment

from .routers import workspaces
from .routers import jobs

def create_api(
environment: Environment
) -> APIRouter:
router = APIRouter(prefix="/api")

router.include_router(workspaces.router(browser_settings=environment.settings.ocrd_browser))
router.include_router(jobs.router(environment))
return router
File renamed without changes.
45 changes: 45 additions & 0 deletions ocrdmonitor/server/api/routers/jobs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from fastapi import APIRouter, Response, Depends

from ocrdmonitor.protocols import Environment, OcrdJob, Repositories

from typing import List

from ocrdmonitor.ocrdcontroller import OcrdController

class ResultList:
def __init__(self, results: List[OcrdJob]):
self.results = results


def router(environment: Environment) -> None:
router = APIRouter(prefix="/jobs")

@router.get("/", name="api.jobs")
async def jobs(
repositories: Repositories = Depends(environment.repositories),
) -> Response:
job_repository = repositories.ocrd_jobs
jobs = await job_repository.find_all()

return ResultList(jobs)

@router.get("/{job_id}", name="api.job")
async def job(
repositories: Repositories = Depends(environment.repositories),
) -> Response:
job_repository = repositories.ocrd_jobs
jobs = await job_repository.find_all()

return ResultList(jobs)

@router.get("/{job_id}/processstatus", name="api.job.processstatus")
async def job_processstatus(
job_id: str, repositories: Repositories = Depends(environment.repositories)
) -> Response:
controller = OcrdController(environment.controller_server())

job_repository = repositories.ocrd_jobs
job = await job_repository.get(job_id)
return await controller.status_for(job)

return router
30 changes: 30 additions & 0 deletions ocrdmonitor/server/api/routers/workspaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from pathlib import Path

from fastapi import APIRouter, Response
from ocrdbrowser import workspace
from ocrdmonitor.server.settings import OcrdBrowserSettings

from typing import List

class ResultList():
def __init__(self, results: List[Path]):
self.results = results

def router(
browser_settings: OcrdBrowserSettings
) -> None:
router = APIRouter(prefix="/workspaces")

@router.get("/", name="api.list.workspaces")
def list_workspaces(search: str | None = None) -> Response:
spaces = [
Path(space).relative_to(browser_settings.workspace_dir)
for space in workspace.list_all(browser_settings.workspace_dir)
]

if search:
spaces = list(filter(lambda workspace: search in str(workspace), spaces))

return ResultList(spaces)

return router
2 changes: 2 additions & 0 deletions ocrdmonitor/server/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from fastapi.templating import Jinja2Templates

from ocrdmonitor.protocols import Environment
from ocrdmonitor.server.api import create_api
from ocrdmonitor.server.index import create_index
from ocrdmonitor.server.jobs import create_jobs
from ocrdmonitor.server.lifespan import lifespan
Expand Down Expand Up @@ -45,6 +46,7 @@ async def validation_exception(
content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
)

app.include_router(create_api(environment))
app.include_router(create_index(templates))
app.include_router(create_jobs(templates, environment))
app.include_router(create_workspaces(templates, environment))
Expand Down
5 changes: 3 additions & 2 deletions ocrdmonitor/server/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class RunningJob:
def split_into_running_and_completed(
jobs: Iterable[OcrdJob],
) -> tuple[list[OcrdJob], list[OcrdJob]]:
running_ocrd_jobs = [job for job in jobs if job.is_running]
running_ocrd_jobs = [job for job in jobs if job.is_processing]
completed_ocrd_jobs = [job for job in jobs if job.is_completed]
return running_ocrd_jobs, completed_ocrd_jobs

Expand Down Expand Up @@ -63,9 +63,10 @@ async def jobs(

now = datetime.now(timezone.utc)
return templates.TemplateResponse(
"jobs.html.j2",
"table.html.j2",
{
"request": request,
"title": "Jobs",
"running_jobs": sorted(
running_jobs,
key=lambda x: x.ocrd_job.time_created or now,
Expand Down
32 changes: 32 additions & 0 deletions ocrdmonitor/server/static/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#loader {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
opacity: 0;
z-index: -1;
transition: opacity .3s;
display: flex;
justify-content: center;
align-items: center;
}

#loader.is-active {
opacity: 1;
z-index: 1;
}

#loader .is-loading {
position: relative;
}

#loader .loader {
height: 40px;
width: 40px;
}

.ocrd-status {
width: 40px;
border: 0px;
}
73 changes: 73 additions & 0 deletions ocrdmonitor/server/static/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
class ResultsView {
constructor( container, renderResultCallback, afterRenderCallback = false ) {
this.container = container;
this.renderResultCallback = renderResultCallback;
this.loader = this.initLoader()
this.afterRenderCallback = afterRenderCallback
}

async render(url) {
this.showLoader();
// Storing response
const response = await fetch(url);

// Storing data in form of JSON
let data = await response.json();
console.log(data);

this.show(data);

if( this.afterRenderCallback ) {
this.afterRenderCallback()
}

this.hideLoader();
}

show(data) {
let content = ""

if(!data.results || data.results.length == 0) {
content = "No results were found"
} else {
for (let result of data.results) {
content += this.renderResultCallback(result)
}
}

this.container.innerHTML = content;
}

hideLoader() {
this.loader.classList.remove("is-active");
}

showLoader() {
this.loader.classList.add("is-active");
}

initLoader() {
let loader = document.createElement('div');
loader.id = 'loader';
loader.innerHTML = '<div class="loader is-loading"></div>'
let loaderContainer = this.container
if(loaderContainer.tagName == 'TBODY') {
loaderContainer = loaderContainer.parentNode
}

loaderContainer.parentNode.insertBefore( loader, loaderContainer);
return document.getElementById('loader');
}

}


function diff(time_created, time_terminated) {
let from = moment.utc(time_created)
let to = moment.utc()
if( time_terminated ) {
to = moment.utc(time_terminated)
}

return moment.utc(to.diff(from)).format('HH:mm:ss')
}
23 changes: 13 additions & 10 deletions ocrdmonitor/server/templates/base.html.j2
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
{% block meta %}{% endblock %}
<title>{% block title %}{% endblock %} - OCR-D Monitor</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css">
<link rel="stylesheet" href="{{ url_for('static', path='style.css') }}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="{{ url_for('static', path='main.css') }}">
<script src="https://momentjs.com/downloads/moment.js"></script>
<script language="javascript" type="text/javascript" src="{{ url_for('static', path='main.js') }}"></script>
</head>
<body>
<nav class="navbar" role="navigation" aria-label="main navigation">
Expand Down Expand Up @@ -49,14 +52,14 @@
</div>
</div>
</div>
</nav>
<section class="section">
<div id="main-content" class="container">
<h1 class="title">
{% block headline %}{% endblock %}
</h1>
{% block content %}{% endblock %}
</div>
</section>
</nav>
<section id="main-content" class="section">
<div class="container">
<h1 class="title">
{% block headline %}{% endblock %}
</h1>
{% block content %}{% endblock %}
</div>
</section>
</body>
</html>
Loading

0 comments on commit ae0c0d1

Please sign in to comment.