mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-23 02:48:26 +00:00
Initial version.
Features: - Upload PDF files let them analyze - Manage meta data and items - See processing in webapp
This commit is contained in:
79
modules/webapp/src/main/elm/Page/Queue/Data.elm
Normal file
79
modules/webapp/src/main/elm/Page/Queue/Data.elm
Normal file
@ -0,0 +1,79 @@
|
||||
module Page.Queue.Data exposing (..)
|
||||
|
||||
import Http
|
||||
import Api.Model.JobQueueState exposing (JobQueueState)
|
||||
import Api.Model.JobDetail exposing (JobDetail)
|
||||
import Api.Model.BasicResult exposing (BasicResult)
|
||||
import Time
|
||||
import Util.Duration
|
||||
import Util.Maybe
|
||||
import Comp.YesNoDimmer
|
||||
|
||||
type alias Model =
|
||||
{ state: JobQueueState
|
||||
, error: String
|
||||
, pollingInterval: Float
|
||||
, init: Bool
|
||||
, stopRefresh: Bool
|
||||
, currentMillis: Int
|
||||
, showLog: Maybe JobDetail
|
||||
, deleteConfirm: Comp.YesNoDimmer.Model
|
||||
, cancelJobRequest: Maybe String
|
||||
}
|
||||
|
||||
emptyModel: Model
|
||||
emptyModel =
|
||||
{ state = Api.Model.JobQueueState.empty
|
||||
, error = ""
|
||||
, pollingInterval = 1200
|
||||
, init = False
|
||||
, stopRefresh = False
|
||||
, currentMillis = 0
|
||||
, showLog = Nothing
|
||||
, deleteConfirm = Comp.YesNoDimmer.emptyModel
|
||||
, cancelJobRequest = Nothing
|
||||
}
|
||||
|
||||
type Msg
|
||||
= Init
|
||||
| StateResp (Result Http.Error JobQueueState)
|
||||
| StopRefresh
|
||||
| NewTime Time.Posix
|
||||
| ShowLog JobDetail
|
||||
| QuitShowLog
|
||||
| RequestCancelJob JobDetail
|
||||
| DimmerMsg JobDetail Comp.YesNoDimmer.Msg
|
||||
| CancelResp (Result Http.Error BasicResult)
|
||||
|
||||
getRunningTime: Model -> JobDetail -> Maybe String
|
||||
getRunningTime model job =
|
||||
let
|
||||
mkTime: Int -> Int -> Maybe String
|
||||
mkTime start end =
|
||||
if start < end then Just <| Util.Duration.toHuman (end - start)
|
||||
else Nothing
|
||||
in
|
||||
case (job.started, job.finished) of
|
||||
(Just sn, Just fn) ->
|
||||
Util.Maybe.or
|
||||
[ mkTime sn fn
|
||||
, mkTime sn model.currentMillis
|
||||
]
|
||||
|
||||
(Just sn, Nothing) ->
|
||||
mkTime sn model.currentMillis
|
||||
|
||||
(Nothing, _) ->
|
||||
Nothing
|
||||
|
||||
getSubmittedTime: Model -> JobDetail -> Maybe String
|
||||
getSubmittedTime model job =
|
||||
if model.currentMillis > job.submitted then
|
||||
Just <| Util.Duration.toHuman (model.currentMillis - job.submitted)
|
||||
else
|
||||
Nothing
|
||||
|
||||
getDuration: Model -> JobDetail -> Maybe String
|
||||
getDuration model job =
|
||||
if job.state == "stuck" then getSubmittedTime model job
|
||||
else Util.Maybe.or [ (getRunningTime model job), (getSubmittedTime model job) ]
|
76
modules/webapp/src/main/elm/Page/Queue/Update.elm
Normal file
76
modules/webapp/src/main/elm/Page/Queue/Update.elm
Normal file
@ -0,0 +1,76 @@
|
||||
module Page.Queue.Update exposing (update)
|
||||
|
||||
import Api
|
||||
import Ports
|
||||
import Page.Queue.Data exposing (..)
|
||||
import Data.Flags exposing (Flags)
|
||||
import Util.Http
|
||||
import Time
|
||||
import Task
|
||||
import Comp.YesNoDimmer
|
||||
|
||||
update: Flags -> Msg -> Model -> (Model, Cmd Msg)
|
||||
update flags msg model =
|
||||
case msg of
|
||||
Init ->
|
||||
let
|
||||
start = if model.init
|
||||
then Cmd.none
|
||||
else Cmd.batch
|
||||
[Api.getJobQueueState flags StateResp
|
||||
,getNewTime
|
||||
]
|
||||
in
|
||||
({model|init = True, stopRefresh = False}, start)
|
||||
|
||||
StateResp (Ok s) ->
|
||||
let
|
||||
progressCmd =
|
||||
List.map (\job -> Ports.setProgress (job.id, job.progress)) s.progress
|
||||
_ = Debug.log "stopRefresh" model.stopRefresh
|
||||
refresh =
|
||||
if model.pollingInterval <= 0 || model.stopRefresh then Cmd.none
|
||||
else Cmd.batch
|
||||
[Api.getJobQueueStateIn flags model.pollingInterval StateResp
|
||||
,getNewTime
|
||||
]
|
||||
in
|
||||
({model | state = s, stopRefresh = False}, Cmd.batch (refresh :: progressCmd))
|
||||
|
||||
StateResp (Err err) ->
|
||||
({model | error = Util.Http.errorToString err }, Cmd.none)
|
||||
|
||||
StopRefresh ->
|
||||
({model | stopRefresh = True, init = False }, Cmd.none)
|
||||
|
||||
NewTime t ->
|
||||
({model | currentMillis = Time.posixToMillis t}, Cmd.none)
|
||||
|
||||
ShowLog job ->
|
||||
({model | showLog = Just job}, Cmd.none)
|
||||
|
||||
QuitShowLog ->
|
||||
({model | showLog = Nothing}, Cmd.none)
|
||||
|
||||
RequestCancelJob job ->
|
||||
let
|
||||
newModel = {model|cancelJobRequest = Just job.id}
|
||||
in
|
||||
update flags (DimmerMsg job Comp.YesNoDimmer.Activate) newModel
|
||||
|
||||
DimmerMsg job m ->
|
||||
let
|
||||
(cm, confirmed) = Comp.YesNoDimmer.update m model.deleteConfirm
|
||||
cmd = if confirmed then Api.cancelJob flags job.id CancelResp else Cmd.none
|
||||
in
|
||||
({model | deleteConfirm = cm}, cmd)
|
||||
|
||||
CancelResp (Ok r) ->
|
||||
(model, Cmd.none)
|
||||
CancelResp (Err err) ->
|
||||
(model, Cmd.none)
|
||||
|
||||
|
||||
getNewTime : Cmd Msg
|
||||
getNewTime =
|
||||
Task.perform NewTime Time.now
|
217
modules/webapp/src/main/elm/Page/Queue/View.elm
Normal file
217
modules/webapp/src/main/elm/Page/Queue/View.elm
Normal file
@ -0,0 +1,217 @@
|
||||
module Page.Queue.View exposing (view)
|
||||
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick)
|
||||
|
||||
import Page.Queue.Data exposing (..)
|
||||
import Api.Model.JobQueueState exposing (JobQueueState)
|
||||
import Api.Model.JobDetail exposing (JobDetail)
|
||||
import Api.Model.JobLogEvent exposing (JobLogEvent)
|
||||
import Data.Priority
|
||||
import Comp.YesNoDimmer
|
||||
import Util.Time exposing (formatDateTime, formatIsoDateTime)
|
||||
import Util.Duration
|
||||
|
||||
view: Model -> Html Msg
|
||||
view model =
|
||||
div [class "queue-page ui grid container"] <|
|
||||
List.concat
|
||||
[ case model.showLog of
|
||||
Just job ->
|
||||
[renderJobLog job]
|
||||
Nothing ->
|
||||
List.map (renderProgressCard model) model.state.progress
|
||||
|> List.map (\el -> div [class "row"][div [class "column"][el]])
|
||||
, [div [class "two column row"]
|
||||
[renderWaiting model
|
||||
,renderCompleted model
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
renderJobLog: JobDetail -> Html Msg
|
||||
renderJobLog job =
|
||||
div [class "ui fluid card"]
|
||||
[div [class "content"]
|
||||
[i [class "delete link icon", onClick QuitShowLog][]
|
||||
,text job.name
|
||||
]
|
||||
,div [class "content"]
|
||||
[div [class "job-log"]
|
||||
(List.map renderLogLine job.logs)
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
renderWaiting: Model -> Html Msg
|
||||
renderWaiting model =
|
||||
div [class "column"]
|
||||
[div [class "ui center aligned basic segment"]
|
||||
[i [class "ui large angle double up icon"][]
|
||||
]
|
||||
,div [class "ui centered cards"]
|
||||
(List.map (renderInfoCard model) model.state.queued)
|
||||
]
|
||||
|
||||
renderCompleted: Model -> Html Msg
|
||||
renderCompleted model =
|
||||
div [class "column"]
|
||||
[div [class "ui center aligned basic segment"]
|
||||
[i [class "ui large angle double down icon"][]
|
||||
]
|
||||
,div [class "ui centered cards"]
|
||||
(List.map (renderInfoCard model) model.state.completed)
|
||||
]
|
||||
|
||||
renderProgressCard: Model -> JobDetail -> Html Msg
|
||||
renderProgressCard model job =
|
||||
div [class "ui fluid card"]
|
||||
[div [id job.id, class "ui top attached indicating progress"]
|
||||
[div [class "bar"]
|
||||
[]
|
||||
]
|
||||
,Html.map (DimmerMsg job) (Comp.YesNoDimmer.view2 (model.cancelJobRequest == Just job.id) dimmerSettings model.deleteConfirm)
|
||||
,div [class "content"]
|
||||
[ div [class "right floated meta"]
|
||||
[div [class "ui label"]
|
||||
[text job.state
|
||||
,div [class "detail"]
|
||||
[Maybe.withDefault "" job.worker |> text
|
||||
]
|
||||
]
|
||||
,div [class "ui basic label"]
|
||||
[i [class "clock icon"][]
|
||||
,div [class "detail"]
|
||||
[getDuration model job |> Maybe.withDefault "-:-" |> text
|
||||
]
|
||||
]
|
||||
]
|
||||
, i [class "asterisk loading icon"][]
|
||||
, text job.name
|
||||
]
|
||||
,div [class "content"]
|
||||
[div [class "job-log"]
|
||||
(List.map renderLogLine job.logs)
|
||||
]
|
||||
,div [class "meta"]
|
||||
[div [class "right floated"]
|
||||
[button [class "ui button", onClick (RequestCancelJob job)]
|
||||
[text "Cancel"
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
renderLogLine: JobLogEvent -> Html Msg
|
||||
renderLogLine log =
|
||||
span [class (String.toLower log.level)]
|
||||
[formatIsoDateTime log.time |> text
|
||||
,text ": "
|
||||
,text log.message
|
||||
, br[][]
|
||||
]
|
||||
|
||||
isFinal: JobDetail -> Bool
|
||||
isFinal job =
|
||||
case job.state of
|
||||
"failed" -> True
|
||||
"success" -> True
|
||||
"cancelled" -> True
|
||||
_ -> False
|
||||
|
||||
dimmerSettings: Comp.YesNoDimmer.Settings
|
||||
dimmerSettings =
|
||||
let
|
||||
defaults = Comp.YesNoDimmer.defaultSettings
|
||||
in
|
||||
{ defaults | headerClass = "ui inverted header", headerIcon = "", message = "Cancel/Delete this job?"}
|
||||
|
||||
renderInfoCard: Model -> JobDetail -> Html Msg
|
||||
renderInfoCard model job =
|
||||
div [classList [("ui fluid card", True)
|
||||
,(jobStateColor job, True)
|
||||
]
|
||||
]
|
||||
[Html.map (DimmerMsg job) (Comp.YesNoDimmer.view2 (model.cancelJobRequest == Just job.id) dimmerSettings model.deleteConfirm)
|
||||
,div [class "content"]
|
||||
[div [class "right floated"]
|
||||
[if isFinal job || job.state == "stuck" then
|
||||
span [onClick (ShowLog job)]
|
||||
[i [class "file link icon", title "Show log"][]
|
||||
]
|
||||
else
|
||||
span[][]
|
||||
,i [class "delete link icon", title "Remove", onClick (RequestCancelJob job)][]
|
||||
]
|
||||
,if isFinal job then
|
||||
span [class "invisible"][]
|
||||
else
|
||||
div [class "right floated"]
|
||||
[div [class "meta"]
|
||||
[getDuration model job |> Maybe.withDefault "-:-" |> text
|
||||
]
|
||||
]
|
||||
,i [classList [("check icon", job.state == "success")
|
||||
,("redo icon", job.state == "stuck")
|
||||
,("bolt icon", job.state == "failed")
|
||||
,("meh outline icon", job.state == "canceled")
|
||||
,("cog icon", not (isFinal job) && job.state /= "stuck")
|
||||
]
|
||||
][]
|
||||
,text job.name
|
||||
]
|
||||
,div [class "content"]
|
||||
[div [class "right floated"]
|
||||
[if isFinal job then
|
||||
div [class ("ui basic label " ++ jobStateColor job)]
|
||||
[i [class "clock icon"][]
|
||||
,div [class "detail"]
|
||||
[getDuration model job |> Maybe.withDefault "-:-" |> text
|
||||
]
|
||||
]
|
||||
else
|
||||
span [class "invisible"][]
|
||||
,div [class ("ui basic label " ++ jobStateColor job)]
|
||||
[text "Prio"
|
||||
,div [class "detail"]
|
||||
[code [][Data.Priority.fromString job.priority
|
||||
|> Maybe.map Data.Priority.toName
|
||||
|> Maybe.withDefault job.priority
|
||||
|> text
|
||||
]
|
||||
]
|
||||
]
|
||||
,div [class ("ui basic label " ++ jobStateColor job)]
|
||||
[text "Retries"
|
||||
,div [class "detail"]
|
||||
[job.retries |> String.fromInt |> text
|
||||
]
|
||||
]
|
||||
]
|
||||
,jobStateLabel job
|
||||
,div [class "ui basic label"]
|
||||
[Util.Time.formatDateTime job.submitted |> text
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
jobStateColor: JobDetail -> String
|
||||
jobStateColor job =
|
||||
case job.state of
|
||||
"success" -> "green"
|
||||
"failed" -> "red"
|
||||
"canceled" -> "orange"
|
||||
"stuck" -> "purple"
|
||||
"scheduled" -> "blue"
|
||||
"waiting" -> "grey"
|
||||
_ -> ""
|
||||
|
||||
jobStateLabel: JobDetail -> Html Msg
|
||||
jobStateLabel job =
|
||||
let
|
||||
col = jobStateColor job
|
||||
in
|
||||
div [class ("ui label " ++ col)]
|
||||
[text job.state
|
||||
]
|
Reference in New Issue
Block a user