base frontend website with authentication
This commit is contained in:
101
clojure_frontend/src/toaster/config.clj
Normal file
101
clojure_frontend/src/toaster/config.clj
Normal file
@@ -0,0 +1,101 @@
|
||||
;; Copyright (C) 2015-2018 Dyne.org foundation
|
||||
|
||||
;; Sourcecode designed, written and maintained by
|
||||
;; Denis Roio <jaromil@dyne.org>
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU Affero General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU Affero General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU Affero General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(ns toaster.config
|
||||
(:require [clojure.pprint :refer [pprint]]
|
||||
[clojure.string :refer [upper-case]]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.walk :refer [keywordize-keys]]
|
||||
[auxiliary.core :as aux]
|
||||
[taoensso.timbre :as log]
|
||||
[failjure.core :as f]
|
||||
[schema.core :as s]
|
||||
[yaml.core :as yaml]
|
||||
[cheshire.core :refer :all]))
|
||||
|
||||
;; (s/defschema Config
|
||||
;; {s/Keyword
|
||||
;; (s/optional-key :webserver) {:port s/Num
|
||||
;; :host s/Str
|
||||
;; :anti-forgery s/Bool
|
||||
;; :ssl-redirect s/Bool}
|
||||
;; (s/optional-key :just-auth) {:email-server s/Str
|
||||
;; :email-user s/Str
|
||||
;; :email-pass s/Str
|
||||
;; :email-address s/Str}
|
||||
;; }
|
||||
;; :appname s/Str
|
||||
;; :paths [s/Str]
|
||||
;; :filename s/Str
|
||||
;; })
|
||||
|
||||
(def run-mode (atom :web))
|
||||
|
||||
(def default-settings {:webserver
|
||||
{:anti-forgery false
|
||||
:ssl-redirect false}})
|
||||
|
||||
(defn yaml-read [path]
|
||||
(if (.exists (io/as-file path))
|
||||
(-> path yaml/from-file keywordize-keys)))
|
||||
|
||||
|
||||
(defn- config-read
|
||||
"Read configurations from standard locations, overriding defaults or
|
||||
system-wide with user specific paths. Requires the application name
|
||||
and optionally default values."
|
||||
([appname] (config-read appname {}))
|
||||
([appname defaults & flags]
|
||||
(let [home (System/getenv "HOME")
|
||||
pwd (System/getenv "PWD" )
|
||||
file (str appname ".yaml")
|
||||
conf (-> {:appname appname
|
||||
:filename file
|
||||
:paths [(str "/etc/" appname "/" file)
|
||||
(str home "/." appname "/" file)
|
||||
(str pwd "/" file)
|
||||
;; TODO: this should be resources
|
||||
(str pwd "/resources/" file)
|
||||
(str pwd "/test-resources/" file)]}
|
||||
(conj defaults))]
|
||||
(loop [[p & paths] (:paths conf)
|
||||
res defaults]
|
||||
(let [res (merge res
|
||||
(if (.exists (io/as-file p))
|
||||
(conj res (yaml-read p))))]
|
||||
(if (empty? paths) (conj conf {(keyword appname) res})
|
||||
(recur paths res)))))))
|
||||
|
||||
(defn- spy "Print out a config structure nicely formatted"
|
||||
[edn]
|
||||
(if (log/may-log? :debug)
|
||||
(binding [*out* *err*] (pprint edn)))
|
||||
edn)
|
||||
|
||||
(defn q [conf path] ;; query a variable inside the config
|
||||
{:pre [(coll? path)]}
|
||||
;; (try ;; adds an extra check every time configuration is read
|
||||
;; (s/validate Config conf)
|
||||
;; (catch Exception ex
|
||||
;; (f/fail (log/spy :error ["Invalid configuration: " conf ex]))))
|
||||
(get-in conf path))
|
||||
|
||||
(defn load-config [name default]
|
||||
(log/info (str "Loading configuration: " name))
|
||||
(->> (config-read name default)))
|
||||
|
||||
167
clojure_frontend/src/toaster/handler.clj
Normal file
167
clojure_frontend/src/toaster/handler.clj
Normal file
@@ -0,0 +1,167 @@
|
||||
;; Copyright (C) 2015-2018 Dyne.org foundation
|
||||
|
||||
;; Sourcecode designed, written and maintained by
|
||||
;; Denis Roio <jaromil@dyne.org>
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU Affero General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU Affero General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU Affero General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(ns toaster.handler
|
||||
(:require
|
||||
[clojure.string :as str]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.data.json :as json]
|
||||
[compojure.core :refer :all]
|
||||
[compojure.handler :refer :all]
|
||||
[compojure.route :as route]
|
||||
[compojure.response :as response]
|
||||
|
||||
[ring.adapter.jetty :refer :all]
|
||||
[ring.middleware.session :refer :all]
|
||||
[ring.middleware.accept :refer [wrap-accept]]
|
||||
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
|
||||
|
||||
[failjure.core :as f]
|
||||
[taoensso.timbre :as log]
|
||||
[just-auth.core :as auth]
|
||||
|
||||
[toaster.session :as s]
|
||||
[toaster.config :as conf]
|
||||
[toaster.webpage :as web]
|
||||
[toaster.ring :as ring])
|
||||
(:import java.io.File)
|
||||
(:gen-class))
|
||||
|
||||
(defonce config (conf/load-config "toaster" conf/default-settings))
|
||||
|
||||
(defroutes app-routes
|
||||
|
||||
(GET "/" request (web/render "Hello World!"));; web/readme))
|
||||
|
||||
;; NEW ROUTES HERE
|
||||
|
||||
|
||||
|
||||
|
||||
;; JUST-AUTH ROUTES
|
||||
(GET "/login" request
|
||||
(f/attempt-all
|
||||
[acct (s/check-account request)]
|
||||
(web/render acct
|
||||
[:div
|
||||
[:h1 (str "Already logged in with account: "
|
||||
(:email acct))]
|
||||
[:h2 [:a {:href "/logout"} "Logout"]]])
|
||||
(f/when-failed [e]
|
||||
(web/render web/login-form))))
|
||||
(POST "/login" request
|
||||
(f/attempt-all
|
||||
[username (s/param request :username)
|
||||
password (s/param request :password)
|
||||
logged (auth/sign-in
|
||||
@ring/auth username password {})]
|
||||
;; TODO: pass :ip-address in last argument map
|
||||
(let [session {:session {:config config
|
||||
:auth logged}}]
|
||||
(conj session
|
||||
(web/render
|
||||
logged
|
||||
[:div
|
||||
[:h1 "Logged in: " username]
|
||||
(web/render-yaml session)])))
|
||||
(f/when-failed [e]
|
||||
(web/render-error-page
|
||||
(str "Login failed: " (f/message e))))))
|
||||
(GET "/session" request
|
||||
(-> (:session request) web/render-yaml web/render))
|
||||
(GET "/logout" request
|
||||
(conj {:session {:config config}}
|
||||
(web/render [:h1 "Logged out."])))
|
||||
(GET "/signup" request
|
||||
(web/render web/signup-form))
|
||||
(POST "/signup" request
|
||||
(f/attempt-all
|
||||
[name (s/param request :name)
|
||||
email (s/param request :email)
|
||||
password (s/param request :password)
|
||||
repeat-password (s/param request :repeat-password)
|
||||
activation {:activation-uri
|
||||
(get-in request [:headers "host"])}]
|
||||
(web/render
|
||||
(if (= password repeat-password)
|
||||
(f/try*
|
||||
(f/if-let-ok?
|
||||
[signup (auth/sign-up @ring/auth
|
||||
name
|
||||
email
|
||||
password
|
||||
activation
|
||||
[])]
|
||||
[:div
|
||||
[:h2 (str "Account created: "
|
||||
name " <" email ">")]
|
||||
[:h3 "Account pending activation."]]
|
||||
(web/render-error
|
||||
(str "Failure creating account: "
|
||||
(f/message signup)))))
|
||||
(web/render-error
|
||||
"Repeat password didnt match")))
|
||||
(f/when-failed [e]
|
||||
(web/render-error-page
|
||||
(str "Sign-up failure: " (f/message e))))))
|
||||
(GET "/activate/:email/:activation-id"
|
||||
[email activation-id :as request]
|
||||
(let [activation-uri
|
||||
(str "http://"
|
||||
(get-in request [:headers "host"])
|
||||
"/activate/" email "/" activation-id)]
|
||||
(web/render
|
||||
[:div
|
||||
(f/if-let-failed?
|
||||
[act (auth/activate-account
|
||||
@ring/auth email
|
||||
{:activation-link activation-uri})]
|
||||
(web/render-error
|
||||
[:div
|
||||
[:h1 "Failure activating account"]
|
||||
[:h2 (f/message act)]
|
||||
[:p (str "Email: " email " activation-id: " activation-id)]])
|
||||
[:h1 (str "Account activated - " email)])])))
|
||||
;; -- end of JUST-AUTH
|
||||
|
||||
(POST "/" request
|
||||
;; generic endpoint for canceled operations
|
||||
(web/render (s/check-account request)
|
||||
[:div {:class (str "alert alert-danger") :role "alert"}
|
||||
(s/param request :message)]))
|
||||
|
||||
(route/resources "/")
|
||||
(route/not-found (web/render-error-page "Page Not Found"))
|
||||
) ;; end of routes
|
||||
|
||||
(def app
|
||||
(-> (wrap-defaults app-routes ring/app-defaults)
|
||||
(wrap-accept {:mime ["text/html"]
|
||||
;; preference in language, fallback to english
|
||||
:language ["en" :qs 0.5
|
||||
"it" :qs 1
|
||||
"nl" :qs 1
|
||||
"hr" :qs 1]})
|
||||
(wrap-session)))
|
||||
|
||||
;; for uberjar
|
||||
(defn -main []
|
||||
(println "Starting standalone jetty server on http://localhost:6060")
|
||||
(run-jetty app {:port 6060
|
||||
:host "localhost"
|
||||
:join? true}))
|
||||
93
clojure_frontend/src/toaster/ring.clj
Normal file
93
clojure_frontend/src/toaster/ring.clj
Normal file
@@ -0,0 +1,93 @@
|
||||
;; Copyright (C) 2015-2017 Dyne.org foundation
|
||||
|
||||
;; Sourcecode designed, written and maintained by
|
||||
;; Denis Roio <jaromil@dyne.org>
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU Affero General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU Affero General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU Affero General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(ns toaster.ring
|
||||
(:require
|
||||
[clojure.java.io :as io]
|
||||
[toaster.config :as conf]
|
||||
[taoensso.timbre :as log]
|
||||
[failjure.core :as f]
|
||||
[clj-storage.db.mongo :refer [get-mongo-db create-mongo-store]]
|
||||
[just-auth.core :as auth]
|
||||
[just-auth.db.just-auth :as auth-db]
|
||||
|
||||
[auxiliary.translation :as trans]
|
||||
[compojure.core :refer :all]
|
||||
[compojure.handler :refer :all]
|
||||
[ring.middleware.session :refer :all]
|
||||
[ring.middleware.accept :refer [wrap-accept]]
|
||||
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
|
||||
|
||||
(def config (atom {}))
|
||||
(def db (atom {}))
|
||||
(def accts (atom {}))
|
||||
(def auth (atom {}))
|
||||
|
||||
(defn init []
|
||||
(log/merge-config! {:level :debug
|
||||
;; #{:trace :debug :info :warn :error :fatal :report}
|
||||
|
||||
;; Control log filtering by
|
||||
;; namespaces/patterns. Useful for turning off
|
||||
;; logging in noisy libraries, etc.:
|
||||
;; :ns-whitelist ["agiladmin.*" "just-auth.*"]
|
||||
:ns-blacklist ["org.eclipse.jetty.*"
|
||||
"org.mongodb.driver.cluster"]})
|
||||
|
||||
;; load configuration
|
||||
(reset! config (conf/load-config
|
||||
(or (System/getenv "toaster_conf") "toaster")
|
||||
conf/default-settings))
|
||||
|
||||
(let [justauth-conf (get-in @config [:toaster :just-auth])]
|
||||
;; connect database (TODO: take parameters from configuration)
|
||||
(reset! db (get-mongo-db (:mongo-url justauth-conf)))
|
||||
|
||||
;; create authentication stores in db
|
||||
(f/attempt-all
|
||||
[auth-conf (get-in @config [:toaster :just-auth])
|
||||
auth-stores (auth-db/create-auth-stores @db)]
|
||||
|
||||
[(trans/init "lang/auth-en.yml" "lang/english.yaml")
|
||||
(reset! accts auth-stores)
|
||||
(reset! auth (auth/email-based-authentication
|
||||
auth-stores
|
||||
;; TODO: replace with email taken from config
|
||||
(dissoc (:just-auth (:toaster (conf/load-config
|
||||
"toaster" conf/default-settings)))
|
||||
:mongo-url :mongo-user :mongo-pass)
|
||||
{:criteria #{:email :ip-address}
|
||||
:type :block
|
||||
:time-window-secs 10
|
||||
:threshold 5}))]
|
||||
;; (select-keys auth-stores [:account-store
|
||||
;; :password-recovery-store])
|
||||
(f/when-failed [e]
|
||||
(log/error (str (trans/locale [:init :failure])
|
||||
" - " (f/message e))))))
|
||||
(log/info (str (trans/locale [:init :success])))
|
||||
(log/debug @auth))
|
||||
|
||||
(def app-defaults
|
||||
(-> site-defaults
|
||||
(assoc-in [:cookies] true)
|
||||
(assoc-in [:security :anti-forgery]
|
||||
(get-in @config [:webserver :anti-forgery]))
|
||||
(assoc-in [:security :ssl-redirect]
|
||||
(get-in @config [:webserver :ssl-redirect]))
|
||||
(assoc-in [:security :hsts] true)))
|
||||
60
clojure_frontend/src/toaster/session.clj
Normal file
60
clojure_frontend/src/toaster/session.clj
Normal file
@@ -0,0 +1,60 @@
|
||||
(ns toaster.session
|
||||
(:refer-clojure :exclude [get])
|
||||
(:require
|
||||
[toaster.config :as conf]
|
||||
[taoensso.timbre :as log]
|
||||
[failjure.core :as f]
|
||||
[just-auth.core :as auth]
|
||||
[toaster.ring :as ring]
|
||||
[toaster.webpage :as web]))
|
||||
|
||||
(defn param [request param]
|
||||
(let [value
|
||||
(get-in request
|
||||
(conj [:params] param))]
|
||||
(if (nil? value)
|
||||
(f/fail (str "Parameter not found: " param))
|
||||
value)))
|
||||
|
||||
;; TODO: not working?
|
||||
(defn get [req arrk]
|
||||
{:pre (coll? arrk)}
|
||||
(if-let [value (get-in req (conj [:session] arrk))]
|
||||
value
|
||||
(f/fail (str "Value not found in session: " (str arrk)))))
|
||||
|
||||
(defn check-config [request]
|
||||
;; reload configuration from file all the time if in debug mode
|
||||
(if-let [session (:session request)]
|
||||
(if (contains? session :config)
|
||||
(:config session)
|
||||
(conf/load-config "toaster" conf/default-settings))
|
||||
(f/fail "Session not found. ")))
|
||||
|
||||
(defn check-account [request]
|
||||
;; check if login is present in session
|
||||
(f/attempt-all
|
||||
[login (get-in request [:session :auth :email])
|
||||
user (auth/get-account @ring/auth login)]
|
||||
user
|
||||
(f/when-failed [e]
|
||||
(->> e f/message
|
||||
(str "Unauthorized access. ")
|
||||
f/fail))))
|
||||
|
||||
(defn check-database []
|
||||
(if-let [db @ring/db]
|
||||
db
|
||||
(f/fail "No connection to database. ")))
|
||||
|
||||
(defn check [request fun]
|
||||
(f/attempt-all
|
||||
[db (check-database)
|
||||
config (check-config request)
|
||||
account (check-account request)]
|
||||
(fun request config account)
|
||||
(f/when-failed [e]
|
||||
(web/render
|
||||
[:div
|
||||
(web/render-error (f/message e))
|
||||
web/login-form]))))
|
||||
343
clojure_frontend/src/toaster/webpage.clj
Normal file
343
clojure_frontend/src/toaster/webpage.clj
Normal file
@@ -0,0 +1,343 @@
|
||||
;; Copyright (C) 2015-2018 Dyne.org foundation
|
||||
|
||||
;; Sourcecode designed, written and maintained by
|
||||
;; Denis Roio <jaromil@dyne.org>
|
||||
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU Affero General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU Affero General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU Affero General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
(ns toaster.webpage
|
||||
(:require [clojure.java.io :as io]
|
||||
[clojure.data.json :as json]
|
||||
[clojure.data.csv :as csv]
|
||||
[yaml.core :as yaml]
|
||||
[toaster.config :as conf]
|
||||
[taoensso.timbre :as log]
|
||||
[failjure.core :as f]
|
||||
[toaster.ring :as ring]
|
||||
[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)
|
||||
(declare render-error)
|
||||
(declare render-error-page)
|
||||
(declare render-static)
|
||||
|
||||
(defn q [req]
|
||||
"wrapper to retrieve parameters"
|
||||
;; TODO: sanitise and check for irregular chars
|
||||
(get-in req (conj [:params] req)))
|
||||
|
||||
(defn button
|
||||
([url text] (button url text [:p]))
|
||||
|
||||
([url text field] (button url text field "btn-secondary btn-lg"))
|
||||
|
||||
([url text field type]
|
||||
(hf/form-to [:post url]
|
||||
field ;; can be an hidden key/value field (project,
|
||||
;; person, etc using hf/hidden-field)
|
||||
(hf/submit-button {:class (str "btn " type)} text))))
|
||||
|
||||
(defn button-cancel-submit [argmap]
|
||||
[:div
|
||||
{:class
|
||||
(str "row col-md-6 btn-group btn-group-lg "
|
||||
(:btn-group-class argmap))
|
||||
:role "group"}
|
||||
(button
|
||||
(:cancel-url argmap) "Cancel"
|
||||
(:cancel-params argmap)
|
||||
"btn-primary btn-lg btn-danger col-md-3")
|
||||
(button
|
||||
(:submit-url argmap) "Submit"
|
||||
(:submit-params argmap)
|
||||
"btn-primary btn-lg btn-success col-md-3")])
|
||||
|
||||
|
||||
(defn reload-session [request]
|
||||
;; TODO: validation of all data loaded via prismatic schema
|
||||
(conf/load-config "toaster" conf/default-settings)
|
||||
|
||||
)
|
||||
|
||||
(defn render
|
||||
([body]
|
||||
{:headers {"Content-Type"
|
||||
"text/html; charset=utf-8"}
|
||||
:body (page/html5
|
||||
(render-head)
|
||||
[:body ;; {:class "static"}
|
||||
navbar-guest
|
||||
[:div {:class "container-fluid"} body]
|
||||
(render-footer)])})
|
||||
([account body]
|
||||
{:headers {"Content-Type"
|
||||
"text/html; charset=utf-8"}
|
||||
:body (page/html5
|
||||
(render-head)
|
||||
[:body (if (empty? account)
|
||||
navbar-guest
|
||||
navbar-account)
|
||||
[:div {:class "container-fluid"} body]
|
||||
(render-footer)])}))
|
||||
|
||||
|
||||
(defn render-error
|
||||
"render an error message without ending the page"
|
||||
[err]
|
||||
[:div {:class "alert alert-danger" :role "alert"}
|
||||
[:span {:class "far fa-meh"
|
||||
:aria-hidden "true" :style "padding: .5em"}]
|
||||
[:span {:class "sr-only"} "Error:" ]
|
||||
err])
|
||||
|
||||
(defn render-error-page
|
||||
([] (render-error-page {} "Unknown"))
|
||||
([err] (render-error-page {} err))
|
||||
([session error]
|
||||
(render
|
||||
[:div {:class "container-fluid"}
|
||||
(render-error error)
|
||||
(if-not (empty? session)
|
||||
[:div {:class "config"}
|
||||
[:h2 "Environment dump:"]
|
||||
(render-yaml session)])])))
|
||||
|
||||
|
||||
(defn render-head
|
||||
([] (render-head
|
||||
"toaster" ;; default title
|
||||
"toaster"
|
||||
"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, maximum-scale=1"}]
|
||||
|
||||
[:title title]
|
||||
|
||||
;; javascript scripts
|
||||
(page/include-js "/static/js/jquery-3.2.1.min.js")
|
||||
(page/include-js "/static/js/bootstrap.min.js")
|
||||
|
||||
;; cascade style sheets
|
||||
(page/include-css "/static/css/bootstrap.min.css")
|
||||
(page/include-css "/static/css/json-html.css")
|
||||
(page/include-css "/static/css/highlight-tomorrow.css")
|
||||
(page/include-css "/static/css/formatters-styles/html.css")
|
||||
(page/include-css "/static/css/formatters-styles/annotated.css")
|
||||
(page/include-css "/static/css/fa-regular.min.css")
|
||||
(page/include-css "/static/css/fontawesome.min.css")
|
||||
(page/include-css "/static/css/toaster.css")]))
|
||||
|
||||
(def navbar-guest
|
||||
[:nav
|
||||
{:class "navbar navbar-default navbar-fixed-top navbar-expand-md navbar-expand-lg"}
|
||||
[:div {:class "navbar-header"}
|
||||
[:button {:class "navbar-toggle" :type "button"
|
||||
:data-toggle "collapse"
|
||||
:data-target "#navbarResponsive"
|
||||
:aria-controls "navbarResponsive"
|
||||
:aria-expanded "false"
|
||||
:aria-label "Toggle navigation"}
|
||||
[:span {:class "sr-only"} "Toggle navigation"]
|
||||
[:span {:class "icon-bar"}]
|
||||
[:span {:class "icon-bar"}]
|
||||
[:span {:class "icon-bar"}]]
|
||||
[:a {:class "navbar-brand far fa-handshake" :href "/"} "toaster"]]
|
||||
[:div {:class "collapse navbar-collapse" :id "navbarResponsive"}
|
||||
[:ul {:class "nav navbar-nav hidden-sm hidden-md ml-auto"}
|
||||
;; --
|
||||
[:li {:class "divider" :role "separator"}]
|
||||
[:li {:class "nav-item"}
|
||||
[:a {:class "nav-link far fa-address-card"
|
||||
:href "/login"} " Login"]]
|
||||
]]])
|
||||
|
||||
(def navbar-account
|
||||
[:nav {:class "navbar navbar-default navbar-fixed-top navbar-expand-lg"}
|
||||
|
||||
[:div {:class "navbar-header"}
|
||||
[:button {:class "navbar-toggle" :type "button"
|
||||
:data-toggle "collapse" :data-target "#navbarResponsive"
|
||||
:aria-controls "navbarResponsive" :aria-expanded "false"
|
||||
:aria-label "Toggle navigation"}
|
||||
[:span {:class "sr-only"} "Toggle navigation"]
|
||||
[:span {:class "icon-bar"}]
|
||||
[:span {:class "icon-bar"}]
|
||||
[:span {:class "icon-bar"}]]
|
||||
[:a {:class "navbar-brand far fa-handshake" :href "/"} "toaster"]]
|
||||
|
||||
[:div {:class "collapse navbar-collapse" :id "navbarResponsive"}
|
||||
[:ul {:class "nav navbar-nav hidden-sm hidden-md ml-auto"}
|
||||
;; --
|
||||
[:li {:class "divider" :role "separator"}]
|
||||
;; LIST OF RELEVANT LINKS AFTER LOGIN
|
||||
;; [:li {:class "nav-item"}
|
||||
;; [:a {:class "nav-link far fa-address-card"
|
||||
;; :href "/persons/list"} " Persons"]]
|
||||
;; [:li {:class "nav-item"}
|
||||
;; [:a {:class "nav-link far fa-paper-plane"
|
||||
;; :href "/projects/list"} " Projects"]]
|
||||
;; [:li {:class "nav-item"}
|
||||
;; [:a {:class "nav-link far fa-plus-square"
|
||||
;; :href "/timesheets"} " Upload"]]
|
||||
;; [:li {:class "nav-item"}
|
||||
;; [:a {:class "nav-link far fa-save"
|
||||
;; :href "/reload"} " Reload"]]
|
||||
;; --
|
||||
[:li {:role "separator" :class "divider"} ]
|
||||
[:li {:class "nav-item"}
|
||||
[:a {:class "nav-link far fa-file-code"
|
||||
:href "/config"} " Configuration"]]
|
||||
]]])
|
||||
|
||||
(defn render-footer []
|
||||
[:footer {:class "row" :style "margin: 20px"}
|
||||
[:hr]
|
||||
|
||||
[:div {:class "footer col-lg-3"}
|
||||
[:img {:src "/static/img/AGPLv3.png" :style "margin-top: 2.5em"
|
||||
:alt "Affero GPLv3 License"
|
||||
:title "Affero GPLv3 License"} ]]
|
||||
|
||||
[:div {:class "footer col-lg-3"}
|
||||
[:a {:href "https://www.dyne.org"}
|
||||
[:img {:src "/static/img/swbydyne.png"
|
||||
:alt "Software by Dyne.org"
|
||||
:title "Software by Dyne.org"}]]]
|
||||
])
|
||||
|
||||
|
||||
(defn render-static [body]
|
||||
(page/html5 (render-head)
|
||||
[:body {:class "fxc static"}
|
||||
|
||||
navbar-guest
|
||||
|
||||
[:div {:class "container"} body]
|
||||
|
||||
(render-footer)
|
||||
]))
|
||||
|
||||
|
||||
;; 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
|
||||
;; edit functions all take an edn and present an editor in the target format
|
||||
|
||||
|
||||
(defn render-yaml
|
||||
"renders an edn into an highlighted yaml"
|
||||
[data]
|
||||
[:span
|
||||
[:pre [:code {:class "yaml"}
|
||||
(yaml/generate-string data)]]
|
||||
[:script "hljs.initHighlightingOnLoad();"]])
|
||||
|
||||
(defn highlight-yaml
|
||||
"renders a yaml text in highlighted html"
|
||||
[data]
|
||||
[:span
|
||||
[:pre [:code {:class "yaml"}
|
||||
data]]
|
||||
[:script "hljs.initHighlightingOnLoad();"]])
|
||||
|
||||
|
||||
(defn highlight-json
|
||||
"renders a json text in highlighted html"
|
||||
[data]
|
||||
[:span
|
||||
[:pre [:code {:class "json"}
|
||||
data]]
|
||||
[:script "hljs.initHighlightingOnLoad();"]])
|
||||
|
||||
(defn download-csv
|
||||
"takes an edn, returns a csv plain/text for download"
|
||||
[data]
|
||||
{:headers {"Content-Type"
|
||||
"text/plain; charset=utf-8"}
|
||||
:body (with-out-str (csv/write-csv *out* data))})
|
||||
|
||||
(defn edit-edn
|
||||
"renders an editor for the edn in yaml format"
|
||||
[data]
|
||||
[:div;; {:class "form-group"}
|
||||
[:textarea {:class "form-control"
|
||||
:rows "20" :data-editor "yaml"
|
||||
:id "config" :name "editor"}
|
||||
(yaml/generate-string data)]
|
||||
[:script {:src "/static/js/ace.js"
|
||||
:type "text/javascript" :charset "utf-8"}]
|
||||
[:script {:type "text/javascript"}
|
||||
(slurp (io/resource "public/static/js/ace-embed.js"))]
|
||||
;; code to embed the ace editor on all elements in page
|
||||
;; that contain the attribute "data-editor" set to the
|
||||
;; mode language of choice
|
||||
[:input {:class "btn btn-success btn-lg pull-top"
|
||||
:type "submit" :value "submit"}]])
|
||||
|
||||
;; (defonce readme
|
||||
;; (slurp (io/resource "public/static/README.html")))
|
||||
|
||||
(defonce login-form
|
||||
[:div
|
||||
[:h1 "Login for your account"
|
||||
[:form {:action "/login"
|
||||
:method "post"}
|
||||
[:input {:type "text" :name "username"
|
||||
:placeholder "Username"
|
||||
:class "form-control"
|
||||
:style "margin-top: 1em"}]
|
||||
[:input {:type "password" :name "password"
|
||||
:placeholder "Password"
|
||||
:class "form-control"
|
||||
:style "margin-top: 1em"}]
|
||||
[:input {:type "submit" :value "Login"
|
||||
:class "btn btn-primary btn-lg btn-block"
|
||||
:style "margin-top: 1em"}]]]])
|
||||
|
||||
(defonce signup-form
|
||||
[:div
|
||||
[:h1 "Sign Up for a toaster account"
|
||||
[:form {:action "/signup"
|
||||
:method "post"}
|
||||
[:input {:type "text" :name "name"
|
||||
:placeholder "Name"
|
||||
:class "form-control"
|
||||
:style "margin-top: 1em"}]
|
||||
[:input {:type "text" :name "email"
|
||||
:placeholder "Email"
|
||||
:class "form-control"
|
||||
:style "margin-top: 1em"}]
|
||||
[:input {:type "password" :name "password"
|
||||
:placeholder "Password"
|
||||
:class "form-control"
|
||||
:style "margin-top: 1em"}]
|
||||
[:input {:type "password" :name "repeat-password"
|
||||
:placeholder "Repeat password"
|
||||
:class "form-control"
|
||||
:style "margin-top: 1em"}]
|
||||
[:input {:type "submit" :value "Sign In"
|
||||
:class "btn btn-primary btn-lg btn-block"
|
||||
:style "margin-top: 1em"}]]]])
|
||||
Reference in New Issue
Block a user