First version
This commit is contained in:
commit
c9d885d94e
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
.idea/
|
||||
elm-stuff/
|
||||
target/
|
16
elm-package.json
Normal file
16
elm-package.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"summary": "Lightweight Jenkins walldisplay",
|
||||
"repository": "https://github.com/asvanberg/better-walldisplay.git",
|
||||
"license": "BSD3",
|
||||
"source-directories": [
|
||||
"src/main/elm"
|
||||
],
|
||||
"exposed-modules": [],
|
||||
"dependencies": {
|
||||
"elm-lang/core": "5.1.1 <= v < 6.0.0",
|
||||
"elm-lang/html": "2.0.0 <= v < 3.0.0",
|
||||
"elm-lang/http": "1.0.0 <= v < 2.0.0"
|
||||
},
|
||||
"elm-version": "0.18.0 <= v < 0.19.0"
|
||||
}
|
64
pom.xml
Normal file
64
pom.xml
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.jenkins-ci.plugins</groupId>
|
||||
<artifactId>plugin</artifactId>
|
||||
<version>2.26</version>
|
||||
</parent>
|
||||
|
||||
<groupId>io.github.asvanberg</groupId>
|
||||
<artifactId>better-walldisplay</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<packaging>hpi</packaging>
|
||||
|
||||
<properties>
|
||||
<jenkins.version>2.56</jenkins.version>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>repo.jenkins-ci.org</id>
|
||||
<url>http://repo.jenkins-ci.org/public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>repo.jenkins-ci.org</id>
|
||||
<url>http://repo.jenkins-ci.org/public/</url>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<id>stil4m-releases</id>
|
||||
<name>stil4m-releases</name>
|
||||
<url>https://github.com/stil4m/maven-repository/raw/master/releases/</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>nl.stil4m</groupId>
|
||||
<artifactId>elm-maven-plugin</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>Make Elm Source</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>make</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<executablePath>elm-make</executablePath>
|
||||
<inputFiles>src/main/elm/Main.elm</inputFiles>
|
||||
<outputFile>${project.build.directory}/${project.build.finalName}/app.js</outputFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
194
src/main/elm/Main.elm
Normal file
194
src/main/elm/Main.elm
Normal file
@ -0,0 +1,194 @@
|
||||
module Main exposing (main)
|
||||
|
||||
import Dict exposing (Dict)
|
||||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Http
|
||||
import Json.Decode as Decode exposing (Decoder, field, int, list, string)
|
||||
import Task
|
||||
import Time exposing (Time, second)
|
||||
|
||||
type Message
|
||||
= Update Time
|
||||
| JobListRetrieved (Result Http.Error (List Job))
|
||||
| JobDetailsRetrieved String (Result Http.Error JobDetails)
|
||||
|
||||
type alias Model =
|
||||
{ viewName : String
|
||||
, state : State
|
||||
, now : Time
|
||||
, jobs : Dict String (Result () JobDetails)
|
||||
}
|
||||
|
||||
type State
|
||||
= Working
|
||||
| Error
|
||||
|
||||
type alias Job =
|
||||
{ url : String
|
||||
, name : String
|
||||
, color : String
|
||||
}
|
||||
|
||||
type alias JobDetails =
|
||||
{ color : String
|
||||
, name : String
|
||||
, lastBuild : Maybe Build
|
||||
, lastSuccessfulBuild : Maybe Build
|
||||
}
|
||||
|
||||
type alias Build =
|
||||
{ duration : Int
|
||||
, timestamp : Int
|
||||
}
|
||||
|
||||
main : Program String Model Message
|
||||
main =
|
||||
programWithFlags
|
||||
{ init = init
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
, view = view
|
||||
}
|
||||
|
||||
init : String -> ( Model, Cmd Message )
|
||||
init jenkinsViewName =
|
||||
({ viewName = jenkinsViewName, state = Working, jobs = Dict.empty, now = 0 }, Task.perform Update Time.now)
|
||||
|
||||
update : Message -> Model -> ( Model, Cmd Message )
|
||||
update msg model =
|
||||
case msg of
|
||||
Update now ->
|
||||
let
|
||||
request =
|
||||
Http.get ("/view/" ++ model.viewName ++ "/api/json") (field "jobs" <| Decode.list decodeJob)
|
||||
command =
|
||||
Http.send JobListRetrieved request
|
||||
in
|
||||
({ model | now = now }, command)
|
||||
|
||||
JobListRetrieved (Err e) ->
|
||||
({ model | state = Error}, Cmd.none)
|
||||
|
||||
JobListRetrieved (Ok jobList) ->
|
||||
let
|
||||
toDetails job =
|
||||
{ name = job.name
|
||||
, color = job.color
|
||||
, lastBuild = Nothing
|
||||
, lastSuccessfulBuild = Nothing
|
||||
}
|
||||
details =
|
||||
List.map toDetails jobList
|
||||
|> List.map (\details -> (details.name, details))
|
||||
|> Dict.fromList
|
||||
addJob name jobDetail =
|
||||
Dict.insert name (Ok jobDetail)
|
||||
updateColor name jobDetail oldJobDetail =
|
||||
case oldJobDetail of
|
||||
Ok f ->
|
||||
Dict.insert name (Ok { f | color = jobDetail.color })
|
||||
Err () ->
|
||||
Dict.insert name (Ok jobDetail)
|
||||
removeJob name oldJobDetail =
|
||||
identity
|
||||
newModel =
|
||||
{ model
|
||||
| jobs = Dict.merge addJob updateColor removeJob details model.jobs Dict.empty
|
||||
}
|
||||
requestJobDetails job =
|
||||
Http.send (JobDetailsRetrieved job.name)
|
||||
<| Http.get (job.url ++ "api/json?tree=" ++ treeParameter) decodeJobDetails
|
||||
in
|
||||
(newModel, Cmd.batch <| List.map requestJobDetails <| List.filter isBuilding jobList)
|
||||
|
||||
JobDetailsRetrieved jobName (Err _) ->
|
||||
let
|
||||
newJobs =
|
||||
Dict.insert jobName (Err ()) model.jobs
|
||||
in
|
||||
({ model | jobs = newJobs }, Cmd.none)
|
||||
|
||||
JobDetailsRetrieved jobName (Ok details) ->
|
||||
let
|
||||
newJobs =
|
||||
Dict.insert jobName (Ok details) model.jobs
|
||||
in
|
||||
({ model | jobs = newJobs }, Cmd.none)
|
||||
|
||||
isBuilding : { c | color : String } -> Bool
|
||||
isBuilding =
|
||||
.color >> String.endsWith "_anime"
|
||||
|
||||
|
||||
treeParameter : String
|
||||
treeParameter = "color,displayName,lastBuild[timestamp,duration],lastSuccessfulBuild[duration,timestamp]"
|
||||
|
||||
decodeJob : Decoder Job
|
||||
decodeJob =
|
||||
Decode.map3 Job
|
||||
(field "url" string)
|
||||
(field "name" string)
|
||||
(field "color" string)
|
||||
|
||||
decodeJobDetails : Decoder JobDetails
|
||||
decodeJobDetails =
|
||||
Decode.map4 JobDetails
|
||||
(field "color" string)
|
||||
(field "displayName" string)
|
||||
(field "lastBuild" (Decode.maybe decodeBuild))
|
||||
(field "lastSuccessfulBuild" (Decode.maybe decodeBuild))
|
||||
|
||||
decodeBuild : Decoder Build
|
||||
decodeBuild =
|
||||
Decode.map2 Build
|
||||
(field "duration" int)
|
||||
(field "timestamp" int)
|
||||
|
||||
subscriptions : Model -> Sub Message
|
||||
subscriptions {state} =
|
||||
let
|
||||
updateDelay =
|
||||
case state of
|
||||
Working ->
|
||||
5 * second
|
||||
|
||||
Error ->
|
||||
60 * second
|
||||
in
|
||||
Time.every updateDelay Update
|
||||
|
||||
view : Model -> Html msg
|
||||
view model =
|
||||
let
|
||||
fontSize =
|
||||
clamp 0 30 <| round <| 100 / (toFloat <| Dict.size model.jobs) * 0.70
|
||||
|
||||
viewJob (jobName, jobDetails) =
|
||||
case jobDetails of
|
||||
Ok {name, color, lastBuild, lastSuccessfulBuild} ->
|
||||
div [ class color, style [ ("font-size", (toString fontSize) ++ "vmin") ] ]
|
||||
[ if isBuilding { color = color } then
|
||||
progressBar lastBuild lastSuccessfulBuild
|
||||
else
|
||||
div [ class "progress", style [ ("width", "0") ] ] []
|
||||
, div [ style [ ("z-index", "1000") ] ] [ text name ]
|
||||
]
|
||||
|
||||
Err () ->
|
||||
div [ class "red" ] [ text jobName ]
|
||||
|
||||
progressBar lastBuild lastSuccessfulBuild =
|
||||
case (lastBuild, lastSuccessfulBuild) of
|
||||
(Just lb, Just lbs) ->
|
||||
let
|
||||
elapsed = (round model.now) - lb.timestamp
|
||||
progress = clamp 0 100 <| elapsed * 100 // lbs.duration
|
||||
in
|
||||
div [ class "progress", style [ ("width", (toString progress) ++ "%") ] ] []
|
||||
|
||||
_ ->
|
||||
div [ class "progress", style [ ("width", "0") ] ] []
|
||||
|
||||
in
|
||||
div [ class "job-container" ] (List.map viewJob <| Dict.toList model.jobs)
|
71
src/main/webapp/index.html
Normal file
71
src/main/webapp/index.html
Normal file
@ -0,0 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
font-family: sans-serif;
|
||||
text-shadow: 1px 1px 2px black;
|
||||
}
|
||||
div.job-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
div.job-container > div {
|
||||
flex: 1;
|
||||
margin: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
div.job-container > div + div {
|
||||
margin-top: 0;
|
||||
}
|
||||
div.job-container > div.red, div.job-container > div.red_anime {
|
||||
background-color: #800;
|
||||
}
|
||||
div.job-container > div.blue, div.job-container > div.blue_anime {
|
||||
background-color: #080;
|
||||
}
|
||||
.blue .progress, .red .progress, .yellow .progress, .aborted .progress {
|
||||
display: none;
|
||||
}
|
||||
div.job-container div.progress {
|
||||
background-image: linear-gradient(to left, rgba(255, 255, 255, .5) 50%, transparent);
|
||||
}
|
||||
div.job-container > div.yellow, div.job-container > div.yellow_anime {
|
||||
background-color: #880;
|
||||
}
|
||||
div.job-container > div.aborted, div.job-container > div.aborted_anime {
|
||||
background-color: #a8a6a6;
|
||||
}
|
||||
div.progress {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
transition: width 5s linear;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
border-right: 10px solid yellow;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script src="app.js"></script>
|
||||
<script>
|
||||
Elm.Main.fullscreen(window.location.search.substring(1))
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user