Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Listing and filtering entities #42

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading