mustache templating, reorganisation and cleanup of code

This commit is contained in:
Jaromil 2018-10-14 12:53:35 +02:00
parent 527201ad89
commit 687dec948e
11 changed files with 215 additions and 284 deletions

View File

@ -26,7 +26,8 @@
[me.raynes/conch "0.8.0"]
;; time from joda-time
[clj-time "0.14.4"]
[clojure-humanize "0.2.2"]]
[clojure-humanize "0.2.2"]
[de.ubercode.clostache/clostache "1.4.0"]]
:aliases {"test" "midje"}
:source-paths ["src"]
:resource-paths ["resources"]

View File

@ -0,0 +1,24 @@
<div class="box">
<h1 class="title">Upload a Dockerfile to toast</h1>
<p>Choose the file in your computer and click 'Submit' to proceed to validation.</p>
<form action="dockerfile" class="form-shell" enctype="multipart/form-data" method="post">
<div class="file has-label is-fullwidth">
<label class="file-label">
<input class="file-input inputfile inputfile-2" id="file" type="file" />
<label for="file"><span id="filename">Choose a Dockerfile...</span></label>
<span class="file-cta"><span class="file-icon"><i class="fa fa-upload"></i></span><span class="file-label">Upload</span></span>
</label></div>
<div class="field">
<div class="control">
<input class="button is-block is-info is-large is-fullwidth" name="submit" type="submit" value="submit" />
</div></div>
</form>
<script>
var file = document.getElementById("file");
file.onchange = function(){
if(file.files.length > 0) {
document.getElementById('filename').innerHTML = file.files[0].name;
}
};
</script>
</div>

View File

@ -0,0 +1,28 @@
<footer class="footer">
<div class="content has-text-centered">
<p class="has-text-grey">toaster.do transforms your Docker prototype into an installable <a href="https://devuan.org">Devuan GNU+Linux</a> image, choose any supported target architecture.</p>
<div class="columns is-variable is-8">
<div class="column is-one-fifth">
<figure class="image"><img alt="European project DECODE (H2020 nr. 732546)" src="/static/img/ec_logo.png" /></figure>
</div>
<div class="column is-one-fifth">
<a href="https://decodeproject.eu">
<figure class="image"><img alt="DECODE project" src="/static/img/decode_logo.png" /></figure>
</a>
</div>
<div class="column is-one-fifth">
<a href="https://www.dyne.org">
<figure class="image"><img alt="Software by Dyne.org" src="/static/img/swbydyne.png" title="Software by Dyne.org" /></figure>
</a>
</div>
<div class="column is-one-fifth">
<a href="https://devuan.org">
<figure class="image"><img alt="powered by Devuan GNU+Linux" src="/static/img/devuan_logo.png" /></figure>
</a>
</div>
<div class="column is-one-fifth">
<figure class="image"><img alt="Affero GPLv3 License" src="/static/img/AGPLv3.png" title="Affero GPLv3 License" /></figure>
</div>
</div>
</div>
</footer>

View File

@ -0,0 +1,27 @@
<section class="hero is-fullheight">
<div class="hero-body">
<div class="container has-text-centered">
<div class="column is-4 is-offset-4">
<h3 class="title has-text-grey">toaster.do</h3>
<h4 class="subtitle has-text-grey">from Docker to VM in a few clicks, powered by <a href="https://decodeproject.eu">DECODEproject.EU</a></h4>
<p class="subtitle has-text-grey">please login to operate</p>
<div class="box">
<figure class="avatar"><img src="/static/img/cafudda.jpg" /></figure>
<form action="/login" method="post">
<div class="field">
<div class="control has-icons-left">
<input class="input is-large" name="username" placeholder="Email" type="email" /><span class="icon is-small is-left"><i class="fa fa-envelope fa-xs"></i></span></div>
</div>
<div class="field">
<div class="control has-icons-left"><input class="input is-large" name="password" placeholder="Password" type="password" /><span class="icon is-small is-left"><i class="fa fa-lock fa-xs"></i></span></div>
</div>
<div class="field">
<p class="control"><input class="button is-block is-info is-large has-icons-left is-fullwidth" type="submit" value="Login" /></p>
</div>
</form>
</div>
<p class="subtitle has-text-grey">...or <a href="/signup">signup for a new account</a></p>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,32 @@
<section class="hero is-fullheight">
<div class="hero-body">
<div class="container has-text-centered">
<div class="column is-4 is-offset-4">
<h3 class="title has-text-grey">toaster.do</h3>
<h4 class="subtitle has-text-grey">sign up for a new account</h4>
<div class="box">
<figure class="avatar"><img src="/static/img/cafudda.jpg" /></figure>
<form action="/signup" method="post">
<div class="field">
<div class="control"><input class="input" name="name" placeholder="Name" type="text" /></div>
</div>
<div class="field">
<div class="control"><input class="input" name="email" placeholder="Email" type="email" /></div>
</div>
<div class="field">
<div class="control"><input class="input" name="password" placeholder="Password" type="password" /></div>
</div>
<div class="field">
<div class="control"><input class="input" name="repeat-password" placeholder="Repeat password" type="password" /></div>
</div>
<div class="field">
<div class="control"><input class="button is-block is-info is-large is-fullwidth" type="submit" value="Sign In" /></div>
</div>
</form>
</div>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,15 @@
<head>
<meta charset="utf-8" />
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<title>toaster.do</title>
<link href="https://fonts.googleapis.com/css?family=Ubuntu" rel="stylesheet" />
<script src="/static/js/codemirror.js" type="text/javascript"></script>
<script src="/static/js/codemirror-simple.js" type="text/javascript"></script>
<script src="/static/js/codemirror-dockerfile.js" type="text/javascript"></script>
<link href="/static/css/bulma.min.css" rel="stylesheet" type="text/css" />
<link href="/static/css/json-html.css" rel="stylesheet" type="text/css" />
<link href="/static/css/codemirror.css" rel="stylesheet" type="text/css" />
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
<link href="/static/css/toaster.css" rel="stylesheet" type="text/css" />
</head>

View File

@ -24,20 +24,6 @@
[hiccup.page :as page]
[hiccup.form :as hf]))
(declare render)
(declare render-head)
(declare navbar-guest)
(declare navbar-account)
(declare render-footer)
(declare render-yaml)
(declare render-edn)
;; TODO: navbars for bulma
(def navbar-brand [:div {:class "navbar"}])
(def navbar-guest [:div {:class "navbar"}])
(def navbar-account [:div {:class "navbar"}])
(defn button
([url text] (button url text [:p]))
@ -65,106 +51,6 @@
(:submit-params argmap)
"btn-primary btn-lg btn-success col-md-3")])
(defn render
([body]
{:headers {"Content-Type"
"text/html; charset=utf-8"}
:body (page/html5
(render-head)
;; navbar-guest
body
(render-footer))})
([account body]
{:headers {"Content-Type"
"text/html; charset=utf-8"}
:body (page/html5
(render-head)
;; (if (empty? account)
;; navbar-guest
;; navbar-account)
body
(render-footer))}))
(defn notify
"render a notification message without ending the page"
([msg] (notify msg ""))
([msg class]
[:div {:class (str "notification " class " has-text-centered")} msg]))
(defn render-error [err] (->> "is-error" (notify (log/spy :error err)) render))
(defn render-head
([] (render-head
"toaster.do" ;; default title
"From Docker to VM or ARM SDcard in a few clicks, powered by Devuan and the DECODE project" "https://toaster.dyne.org")) ;; default desc
([title desc url]
[:head [:meta {:charset "utf-8"}]
[:meta {:http-equiv "X-UA-Compatible" :content "IE=edge,chrome=1"}]
[:meta
{:name "viewport"
:content "width=device-width, initial-scale=1"}]
[:title title]
[:link {:href "https://fonts.googleapis.com/css?family=Ubuntu:300,400,700"
:rel "stylesheet"}]
;; non-invasive js for syntax highlight of text-area
(page/include-js "/static/js/codemirror.js")
(page/include-js "/static/js/codemirror-simple.js")
(page/include-js "/static/js/codemirror-dockerfile.js")
;; cascade style sheets
(page/include-css "/static/css/bulma.min.css")
(page/include-css "/static/css/json-html.css")
(page/include-css "/static/css/codemirror.css")
;; (page/include-css "/static/css/fa-regular.min.css")
;; (page/include-css "/static/css/fontawesome.min.css")
(page/include-css "https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css")
(page/include-css "/static/css/toaster.css")]))
(defn render-footer []
[:footer {:class "footer"}
[:div {:class "content has-text-centered"}
[:p {:class "has-text-grey"} "toaster.do transforms your Docker prototype into "
"an installable " [:a {:href "https://devuan.org"} "Devuan GNU+Linux"]
" image, choose any supported target architecture."]
[:div {:class "columns is-variable is-8"}
[:div {:class "column is-one-fifth"}
[:figure {:class "image"}
[:img {:src "/static/img/ec_logo.png"
:alt "European project DECODE (H2020 nr. 732546)"}]]]
[:div {:class "column is-one-fifth"}
[:a {:href "https://decodeproject.eu"}
[:figure {:class "image"}
[:img {:src "/static/img/decode_logo.png"
:alt "DECODE project"}]]]]
[:div {:class "column is-one-fifth"}
[:a {:href "https://www.dyne.org"}
[:figure {:class "image"}
[:img {:src "/static/img/swbydyne.png"
:alt "Software by Dyne.org"
:title "Software by Dyne.org"}]]]]
[:div {:class "column is-one-fifth"}
[:a {:href "https://devuan.org"}
[:figure {:class "image"}
[:img {:src "/static/img/devuan_logo.png"
:alt "powered by Devuan GNU+Linux"}]]]]
[:div {:class "column is-one-fifth"}
[:figure {:class "image"}
[:img {:src "/static/img/AGPLv3.png" ;; :style "margin-top: 2.5em"
:alt "Affero GPLv3 License"
:title "Affero GPLv3 License"}]]]]]])
;; highlight functions do no conversion, take the format they highlight
;; render functions take edn and convert to the highlight format
;; download functions all take an edn and convert it in target format
@ -222,83 +108,3 @@
:type "submit" :value "submit"}]])
(def brand-img "/static/img/cafudda.jpg")
(defn- hero-login-box [body]
[:section {:class "hero is-fullheight"}
[:div {:class "hero-body"}
[:div {:class "container has-text-centered"}
body
]]])
(def login-form
(hero-login-box
[:div {:class "column is-4 is-offset-4"}
[:h3 {:class "title has-text-grey"} "toaster.do"]
[:h4 {:class "subtitle has-text-grey"}
"from Docker to VM in a few clicks, powered by "
[:a {:href "https://decodeproject.eu"} "DECODEproject.EU"]]
[:p {:class "subtitle has-text-grey"}
"please login to operate"]
[:div {:class "box"}
[:figure {:class "avatar"}
[:img {:src brand-img }]]
[:form {:action "/login"
:method "post"}
[:div {:class "field"}
;; [:label {:class "label is-large"} "Email"]
[:div {:class "control has-icons-left"}
[:input {:type "email" :name "username"
:placeholder "Email"
:class "input is-large"}]
[:span {:class "icon is-small is-left"}
[:i {:class "fa fa-envelope fa-xs"}]]]]
[:div {:class "field"}
;; [:label {:class "label is-large"} "Password"]
[:div {:class "control has-icons-left"}
[:input {:type "password" :name "password"
:placeholder "Password"
:class "input is-large"}]
[:span {:class "icon is-small is-left"}
[:i {:class "fa fa-lock fa-xs"}]]]]
[:div {:class "field"}
[:p {:class "control"}
[:input {:type "submit" :value "Login"
:class "button is-block is-info is-large has-icons-left is-fullwidth"}]]]]]
[:p {:class "subtitle has-text-grey"}
"...or " [:a {:href "/signup"} "signup for a new account"]]]))
(def signup-form
(hero-login-box
[:div {:class "column is-4 is-offset-4"}
[:h3 {:class "title has-text-grey"} "toaster.do"]
[:h4 {:class "subtitle has-text-grey"}
"sign up for a new account"]
;; [:p {:class "subtitle has-text-grey"}]
[:div {:class "box"}
[:figure {:class "avatar"}
[:img {:src brand-img}]]
[:form {:action "/signup"
:method "post"}
[:div {:class "field"}
[:div {:class "control"}
[:input {:type "text" :name "name"
:placeholder "Name"
:class "input"}]]]
[:div {:class "field"}
[:div {:class "control"}
[:input {:type "email" :name "email"
:placeholder "Email"
:class "input"}]]]
[:div {:class "field"}
[:div {:class "control"}
[:input {:type "password" :name "password"
:placeholder "Password"
:class "input"}]]]
[:div {:class "field"}
[:div {:class "control"}
[:input {:type "password" :name "repeat-password"
:placeholder "Repeat password"
:class "input"}]]]
[:div {:class "field"}
[:div {:class "control"}
[:input {:type "submit" :value "Sign In"
:class "button is-block is-info is-large is-fullwidth"}]]]]]]))

View File

@ -37,16 +37,17 @@
[toaster.session :as s]
[toaster.config :as conf]
[toaster.bulma :as web :refer [render notify login-form]]
[toaster.ring :as ring]
[toaster.views :as views]
[toaster.jobs :as job])
[toaster.views :as views])
(:gen-class))
(defonce config (conf/load-config "toaster" conf/default-settings))
(defn auth-wrap [request fun]
(defn auth-wrap
"Comfortably wrap routes using a function pointer to be called if an
account is correctly authenticated in the currently running session.
(fun) is passed 3 hash-maps args: request, config and account."
[request fun]
(f/attempt-all
[db (s/check-database)
config (s/check-config request)
@ -58,22 +59,9 @@
(s/check-account request))]
(fun request config account)
(f/when-failed [e]
(web/render [:body
(web/notify (f/message e) "is-error")
web/login-form]))))
(defn- login-page [request form]
(f/attempt-all
[acct (s/check-account request)]
(web/render acct
[:body
[:h1 {:class "title"}
(str "Already logged in with account: "
(:email acct))]
[:h2 {:class "subtitle"}
[:a {:href "/logout"} "Logout"]]])
(f/when-failed [e]
(web/render [:body form]))))
(s/render [:body
(s/notify (f/message e) "is-error")
(s/resource s/login)]))))
(defroutes
app-routes
@ -83,16 +71,16 @@
[db (s/check-database)
conf (s/check-config request)]
(f/if-let-ok? [account (s/check-account request)]
(web/render [:body (views/dashboard account)])
(s/render [:body (views/dashboard account)])
;; else
(web/render [:body web/login-form]))
(s/render [:body (s/resource s/login)]))
(f/when-failed[e]
(web/render [:body (web/notify (f/message e) "is-error")]))))
(s/render [:body (s/notify (f/message e) "is-error")]))))
;; NEW ROUTES HERE
(POST "/dockerfile" request
(->> (fn [req conf acct]
(web/render acct
(s/render acct
[:body
(views/dockerfile-upload-post req conf acct)
(views/dashboard acct)]))
@ -100,34 +88,34 @@
(POST "/remove" request
(->> (fn [req conf acct]
(web/render acct [:body
(s/render acct [:body
(views/remove-job req conf acct)
(views/dashboard acct)]))
(auth-wrap request)))
(POST "/start" request
(->> (fn [req conf acct]
(web/render acct [:body
(s/render acct [:body
(views/start-job req conf acct)
(views/dashboard acct)]))
(auth-wrap request)))
(POST "/view" request
(->> (fn [req conf acct]
(web/render acct [:body
(s/render acct [:body
(views/view-job req conf acct)
(views/dashboard acct)]))
(auth-wrap request)))
(GET "/error" request
(->> (fn [req conf acct]
(web/render acct [:body
(web/notify "Generic Error Page" "is-error")
(s/render acct [:body
(s/notify "Generic Error Page" "is-error")
(views/dashboard acct)]))
(auth-wrap request)))
;; JUST-AUTH ROUTES
(GET "/login" request (login-page request web/login-form))
(GET "/login" request (s/render (s/resource s/login)))
(POST "/login" request
(f/attempt-all
@ -139,22 +127,22 @@
(let [session {:session {:config config
:auth logged}}]
(conj session
(web/render logged [:body (views/dashboard logged)])))
;; (web/render
(s/render logged [:body (views/dashboard logged)])))
;; (s/render
;; logged
;; [:div
;; [:h1 "Logged in: " username]
;; views/welcome-menu])))
(f/when-failed [e]
(web/render [:body (web/notify
(s/render [:body (s/notify
(str "Login failed: " (f/message e)) "is-error")]))))
(GET "/logout" request
(conj {:session {:config config}}
(web/render [:body
(s/render [:body
[:h1 {:class "title"} "Logged out."]])))
(GET "/signup" request (login-page request web/signup-form))
(GET "/signup" request (s/render (s/resource s/signup)))
(POST "/signup" request
(f/attempt-all
[name (s/param request :name)
@ -163,7 +151,7 @@
repeat-password (s/param request :repeat-password)
activation {:activation-uri
(get-in request [:headers "host"])}]
(web/render
(s/render
(if (= password repeat-password)
(f/try*
(f/if-let-ok?
@ -178,15 +166,15 @@
name " &lt;" email "&gt;")]
[:h3 "Account pending activation."]]
[:body
(web/notify
(s/notify
(str "Failure creating account: "
(f/message signup)) "is-error")
(login-page request web/signup-form)]))
[:body (web/notify
(s/resource s/signup)]))
[:body (s/notify
"Repeat password didnt match" "is-error")]))
(f/when-failed [e]
(web/render
[:body (web/notify
(s/render
[:body (s/notify
(str "Sign-up failure: " (f/message e)) "is-error")]))))
(GET "/activate/:email/:activation-id"
@ -195,28 +183,28 @@
(str "http://"
(get-in request [:headers "host"])
"/activate/" email "/" activation-id)]
(web/render
(s/render
[:body
(f/if-let-failed?
[act (auth/activate-account
@ring/auth email
{:activation-link activation-uri})]
(web/notify
(s/notify
[:span
[:h1 {:class "title"} "Failure activating account"]
[:h2 {:class "subtitle"} (f/message act)]
[:p (str "Email: " email " activation-id: " activation-id)]] "is-error")
(web/notify [:h1 {:class "title"} (str "Account activated - " email)] "is-success"))])))
(s/notify [:h1 {:class "title"} (str "Account activated - " email)] "is-success"))])))
;; -- end of JUST-AUTH
(POST "/" request
;; generic endpoint for canceled operations
(web/render (s/check-account request)
(web/notify
(s/render (s/check-account request)
(s/notify
(s/param request :message) "is-error")))
(route/resources "/")
(route/not-found (web/render [:body (web/notify "Page Not Found" "is-error")]))
(route/not-found (s/render [:body (s/notify "Page Not Found" "is-error")]))
) ;; end of routes

View File

@ -9,7 +9,6 @@
[failjure.core :as f]
[taoensso.timbre :as log :refer [debug]]
[me.raynes.conch :as sh :refer [with-programs]]
[toaster.bulma :as web]
[toaster.config :refer :all]
[toaster.ring :refer [jobs]]
[hiccup.form :as hf]))

View File

@ -5,8 +5,19 @@
[taoensso.timbre :as log]
[failjure.core :as f]
[just-auth.core :as auth]
[toaster.ring :as ring]
[toaster.bulma :as web]))
[hiccup.page :as page :refer [html5]]
[clostache.parser :refer [render-resource]]
[toaster.ring :as ring]))
(defn resource
"renders a template, optionally passing it an hash-map of parameters."
([template] (resource template {}))
([template params] (render-resource template params)))
(defonce login "templates/body_loginform.html")
(defonce signup "templates/body_signupform.html")
(defonce head "templates/html_head.html")
(defonce footer "templates/body_footer.html")
(defn param [request param]
(let [value
@ -46,3 +57,29 @@
(if-let [db @ring/db]
db
(f/fail "No connection to database.")))
(defn notify
"render a notification message without ending the page"
([msg] (notify msg ""))
([msg class]
;; support also is-error (not included as notify class in bulma
(let [tclass (if (= class "is-error") "is-danger" class)]
(cond ;; log to console using timbre
(= tclass "is-danger") (log/error msg)
(= tclass "is-warning") (log/warn msg)
(= tclass "is-success") (log/info msg)
(= tclass "is-primary") (log/debug msg))
[:div {:class (str "notification " tclass " has-text-centered")} msg])))
(defn render
"render a full webpage using headers, navbar, body and footer"
([body] (render nil body))
([account body]
{:headers {"Content-Type"
"text/html; charset=utf-8"}
:body (page/html5
(resource head)
(conj body (resource footer)))}))
(defn render-error [err] (->> "is-danger" (notify (log/spy :error err)) render))

View File

@ -22,7 +22,7 @@
[clojure.string :as str]
[clojure.contrib.humanize :as humanize :refer [datetime]]
;; [clojure.data.json :as json :refer [read-str]]
[toaster.bulma :as web :refer [button notify render-yaml]]
[toaster.bulma :as web :refer [button render-yaml]]
[toaster.session :as s]
[toaster.ring :as ring]
[toaster.jobs :as job]
@ -35,8 +35,10 @@
[clj-time.coerce :as tc]
[clj-time.local :as tl]
[clj-storage.core :as db]
[hiccup.form :as hf]))
[hiccup.form :as hf]
[clostache.parser :refer [render-resource]]))
;; TODO: templated
(defn- box-list [account joblist]
[:div {:class "box"}
[:h1 {:class "title"} (str "List all toaster jobs for " (:name account))]
@ -61,34 +63,6 @@
(web/button "/remove" "\uD83D\uDDD1" (hf/hidden-field "jobid" jobid))]]]
))]]])
(defn- box-add []
[:div {:class "box"}
[:h1 {:class "title"} "Upload a Dockerfile to toast"]
[:p " Choose the file in your computer and click 'Submit' to
proceed to validation."]
[:form {:action "dockerfile" :method "post"
:class "form-shell"
:enctype "multipart/form-data"}
[:div {:class "file has-label is-fullwidth"}
[:label {:class "file-label"}
[:input {:class "file-input inputfile inputfile-2" :id "file" :type "file"}]
[:label {:for "file"} [:span {:id "filename"} "Choose a Dockerfile..."]]
[:span {:class "file-cta"}
[:span {:class "file-icon"}
[:i {:class "fa fa-upload"}]]
[:span {:class "file-label"} "Upload"]]]]
;; [:fieldset {:class "fieldset-submit"}
[:div {:class "field"}
[:div {:class "control"}
[:input {:class "button is-block is-info is-large is-fullwidth"
:type "submit"
:name "submit" :value "submit"}]]]]
[:script "var file = document.getElementById(\"file\");
file.onchange = function(){
if(file.files.length > 0) {
document.getElementById('filename').innerHTML = file.files[0].name;
} };"]])
(defn dockerfile-upload-post [request config account]
(let
[tempfile (get-in request [:params :file :tempfile])
@ -112,7 +86,7 @@
[:h1 {:class "title"} "Job uploaded and added"]
[:p "Log messages:"]
(web/render-yaml newjob)]
(web/notify (f/message newjob) "is-error")))))))
(s/notify (f/message newjob) "is-error")))))))
(defn dashboard
([account] (dashboard {} {} account))
@ -125,9 +99,9 @@
;(if (> 0 (count joblist))
(box-list account joblist)
;)
(box-add) ]]
(s/resource "templates/body_addjob.html") ]]
(f/when-failed [e]
(web/notify
(s/notify
(str "Job list failure: " (f/message e)) "is-error")))))
(defn remove-job [request config account]
@ -136,18 +110,18 @@
jobfound (db/query @ring/jobs {:jobid jobid})
r_rmjob (db/delete! @ring/jobs jobid)
r_sync (job/sync_jobs config "-d" jobid)]
(web/notify (str "Job removed: " jobid) "is-primary")
(s/notify (str "Job removed: " jobid) "is-primary")
(f/when-failed [e]
(web/notify (str "Failure removing job: " (f/message e)) "is-error"))))
(s/notify (str "Failure removing job: " (f/message e)) "is-error"))))
(defn start-job [request config account]
(f/attempt-all
[jobid (s/param request :jobid)
jobfound (db/query @ring/jobs {:jobid jobid})
r_sync (job/sync_jobs config "-r" jobid)]
(web/notify (str "Job started: " jobid) "is-success")
(s/notify (str "Job started: " jobid) "is-success")
(f/when-failed [e]
(web/notify (str "Failure starting job: " (f/message e)) "is-error"))))
(s/notify (str "Failure starting job: " (f/message e)) "is-error"))))
(defn view-job [request config account]
(f/attempt-all
@ -160,4 +134,4 @@
[:script "var editor = CodeMirror.fromTextArea(document.getElementById(\"code\"),
{ lineNumbers: true, mode: \"dockerfile\" });"]]
(f/when-failed [e]
(web/notify (str "Failure viewing job: " (f/message e)) "is-error"))))
(s/notify (str "Failure viewing job: " (f/message e)) "is-error"))))