base frontend website with authentication
This commit is contained in:
parent
507e941540
commit
4c033ac86c
|
@ -0,0 +1,45 @@
|
|||
(defproject toaster "0.1.0-SNAPSHOT"
|
||||
:description "Basic compojure based authenticated website"
|
||||
:url "http://dyne.org"
|
||||
:min-lein-version "2.0.0"
|
||||
:dependencies [[org.clojure/clojure "1.9.0"]
|
||||
[org.clojure/data.csv "0.1.4"]
|
||||
[compojure "1.6.1"]
|
||||
[ring/ring-defaults "0.3.2"]
|
||||
[ring-middleware-accept "2.0.3"]
|
||||
;; mustache templates
|
||||
[de.ubercode.clostache/clostache "1.4.0"]
|
||||
;; error handling
|
||||
[failjure "1.3.0"]
|
||||
;; logging done right with timbre
|
||||
[com.taoensso/timbre "4.10.0"]
|
||||
;; authentication library
|
||||
[org.clojars.dyne/just-auth "0.4.0"]
|
||||
;; web forms made easy
|
||||
[formidable "0.1.10"]
|
||||
;; parsing configs if any
|
||||
[io.forward/yaml "1.0.9"]
|
||||
;; Data validation
|
||||
[prismatic/schema "1.1.9"]
|
||||
;; filesystem utilities
|
||||
[me.raynes/fs "1.4.6"]
|
||||
;; time from joda-time
|
||||
[clj-time "0.14.4"]]
|
||||
:aliases {"test" "midje"}
|
||||
:source-paths ["src"]
|
||||
:resource-paths ["resources"]
|
||||
:plugins [[lein-ring "0.12.4"]]
|
||||
:ring {:init toaster.ring/init
|
||||
:handler toaster.handler/app}
|
||||
:uberwar {:init toaster.ring/init
|
||||
:handler toaster.handler/app}
|
||||
:mail toaster.handler
|
||||
:profiles { :dev {:dependencies [[javax.servlet/servlet-api "2.5"]
|
||||
[ring/ring-mock "0.3.2"]
|
||||
[midje "1.9.2"]]
|
||||
:plugins [[lein-midje "3.1.3"]]
|
||||
:aot :all
|
||||
:main toaster.handler}
|
||||
:uberjar {:aot :all
|
||||
:main toaster.handler}}
|
||||
)
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
|
||||
init:
|
||||
success: "Initialisation completed"
|
||||
failure: "Initialization failed"
|
||||
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,5 @@
|
|||
/*!
|
||||
* Font Awesome Free 5.0.6 by @fontawesome - http://fontawesome.com
|
||||
* License - http://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||
*/
|
||||
@font-face{font-family:Font Awesome\ 5 Free;font-style:normal;font-weight:400;src:url(/static/fonts/fa-regular-400.eot);src:url(/static/fonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(/static/fonts/fa-regular-400.woff2) format("woff2"),url(/static/fonts/fa-regular-400.woff) format("woff"),url(/static/fonts/fa-regular-400.ttf) format("truetype"),url(/static/fonts/fa-regular-400.svg#fontawesome) format("svg")}.far{font-family:Font Awesome\ 5 Free;font-weight:400}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,46 @@
|
|||
.jsondiffpatch-annotated-delta {
|
||||
font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Monaco, Courier, monospace;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
padding: 0 0 0 12px;
|
||||
display: inline-block;
|
||||
}
|
||||
.jsondiffpatch-annotated-delta pre {
|
||||
font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Monaco, Courier, monospace;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
.jsondiffpatch-annotated-delta td {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.jsondiffpatch-annotated-delta td pre:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
td.jsondiffpatch-delta-note {
|
||||
font-style: italic;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.jsondiffpatch-delta-note > div {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.jsondiffpatch-delta-note pre {
|
||||
font-style: normal;
|
||||
}
|
||||
.jsondiffpatch-annotated-delta .jsondiffpatch-delta-note {
|
||||
color: #777;
|
||||
}
|
||||
.jsondiffpatch-annotated-delta tr:hover {
|
||||
background: #ffc;
|
||||
}
|
||||
.jsondiffpatch-annotated-delta tr:hover > td.jsondiffpatch-delta-note {
|
||||
color: black;
|
||||
}
|
||||
.jsondiffpatch-error {
|
||||
background: red;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
.jsondiffpatch-delta {
|
||||
font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Monaco, Courier, monospace;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
padding: 0 0 0 12px;
|
||||
display: inline-block;
|
||||
}
|
||||
.jsondiffpatch-delta pre {
|
||||
font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', Monaco, Courier, monospace;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
ul.jsondiffpatch-delta {
|
||||
list-style-type: none;
|
||||
padding: 0 0 0 20px;
|
||||
margin: 0;
|
||||
}
|
||||
.jsondiffpatch-delta ul {
|
||||
list-style-type: none;
|
||||
padding: 0 0 0 20px;
|
||||
margin: 0;
|
||||
}
|
||||
.jsondiffpatch-added .jsondiffpatch-property-name,
|
||||
.jsondiffpatch-added .jsondiffpatch-value pre,
|
||||
.jsondiffpatch-modified .jsondiffpatch-right-value pre,
|
||||
.jsondiffpatch-textdiff-added {
|
||||
background: #bbffbb;
|
||||
}
|
||||
.jsondiffpatch-deleted .jsondiffpatch-property-name,
|
||||
.jsondiffpatch-deleted pre,
|
||||
.jsondiffpatch-modified .jsondiffpatch-left-value pre,
|
||||
.jsondiffpatch-textdiff-deleted {
|
||||
background: #ffbbbb;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.jsondiffpatch-unchanged,
|
||||
.jsondiffpatch-movedestination {
|
||||
color: gray;
|
||||
}
|
||||
.jsondiffpatch-unchanged,
|
||||
.jsondiffpatch-movedestination > .jsondiffpatch-value {
|
||||
transition: all 0.5s;
|
||||
-webkit-transition: all 0.5s;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.jsondiffpatch-unchanged-showing .jsondiffpatch-unchanged,
|
||||
.jsondiffpatch-unchanged-showing .jsondiffpatch-movedestination > .jsondiffpatch-value {
|
||||
max-height: 100px;
|
||||
}
|
||||
.jsondiffpatch-unchanged-hidden .jsondiffpatch-unchanged,
|
||||
.jsondiffpatch-unchanged-hidden .jsondiffpatch-movedestination > .jsondiffpatch-value {
|
||||
max-height: 0;
|
||||
}
|
||||
.jsondiffpatch-unchanged-hiding .jsondiffpatch-movedestination > .jsondiffpatch-value,
|
||||
.jsondiffpatch-unchanged-hidden .jsondiffpatch-movedestination > .jsondiffpatch-value {
|
||||
display: block;
|
||||
}
|
||||
.jsondiffpatch-unchanged-visible .jsondiffpatch-unchanged,
|
||||
.jsondiffpatch-unchanged-visible .jsondiffpatch-movedestination > .jsondiffpatch-value {
|
||||
max-height: 100px;
|
||||
}
|
||||
.jsondiffpatch-unchanged-hiding .jsondiffpatch-unchanged,
|
||||
.jsondiffpatch-unchanged-hiding .jsondiffpatch-movedestination > .jsondiffpatch-value {
|
||||
max-height: 0;
|
||||
}
|
||||
.jsondiffpatch-unchanged-showing .jsondiffpatch-arrow,
|
||||
.jsondiffpatch-unchanged-hiding .jsondiffpatch-arrow {
|
||||
display: none;
|
||||
}
|
||||
.jsondiffpatch-value {
|
||||
display: inline-block;
|
||||
}
|
||||
.jsondiffpatch-property-name {
|
||||
display: inline-block;
|
||||
padding-right: 5px;
|
||||
vertical-align: top;
|
||||
}
|
||||
.jsondiffpatch-property-name:after {
|
||||
content: ': ';
|
||||
}
|
||||
.jsondiffpatch-child-node-type-array > .jsondiffpatch-property-name:after {
|
||||
content: ': [';
|
||||
}
|
||||
.jsondiffpatch-child-node-type-array:after {
|
||||
content: '],';
|
||||
}
|
||||
div.jsondiffpatch-child-node-type-array:before {
|
||||
content: '[';
|
||||
}
|
||||
div.jsondiffpatch-child-node-type-array:after {
|
||||
content: ']';
|
||||
}
|
||||
.jsondiffpatch-child-node-type-object > .jsondiffpatch-property-name:after {
|
||||
content: ': {';
|
||||
}
|
||||
.jsondiffpatch-child-node-type-object:after {
|
||||
content: '},';
|
||||
}
|
||||
div.jsondiffpatch-child-node-type-object:before {
|
||||
content: '{';
|
||||
}
|
||||
div.jsondiffpatch-child-node-type-object:after {
|
||||
content: '}';
|
||||
}
|
||||
.jsondiffpatch-value pre:after {
|
||||
content: ',';
|
||||
}
|
||||
li:last-child > .jsondiffpatch-value pre:after,
|
||||
.jsondiffpatch-modified > .jsondiffpatch-left-value pre:after {
|
||||
content: '';
|
||||
}
|
||||
.jsondiffpatch-modified .jsondiffpatch-value {
|
||||
display: inline-block;
|
||||
}
|
||||
.jsondiffpatch-modified .jsondiffpatch-right-value {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.jsondiffpatch-moved .jsondiffpatch-value {
|
||||
display: none;
|
||||
}
|
||||
.jsondiffpatch-moved .jsondiffpatch-moved-destination {
|
||||
display: inline-block;
|
||||
background: #ffffbb;
|
||||
color: #888;
|
||||
}
|
||||
.jsondiffpatch-moved .jsondiffpatch-moved-destination:before {
|
||||
content: ' => ';
|
||||
}
|
||||
ul.jsondiffpatch-textdiff {
|
||||
padding: 0;
|
||||
}
|
||||
.jsondiffpatch-textdiff-location {
|
||||
color: #bbb;
|
||||
display: inline-block;
|
||||
min-width: 60px;
|
||||
}
|
||||
.jsondiffpatch-textdiff-line {
|
||||
display: inline-block;
|
||||
}
|
||||
.jsondiffpatch-textdiff-line-number:after {
|
||||
content: ',';
|
||||
}
|
||||
.jsondiffpatch-error {
|
||||
background: red;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
|
||||
|
||||
/* Tomorrow Comment */
|
||||
.hljs-comment,
|
||||
.hljs-quote {
|
||||
color: #8e908c;
|
||||
}
|
||||
|
||||
/* Tomorrow Red */
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-tag,
|
||||
.hljs-name,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-regexp,
|
||||
.hljs-deletion {
|
||||
color: #c82829;
|
||||
}
|
||||
|
||||
/* Tomorrow Orange */
|
||||
.hljs-number,
|
||||
.hljs-built_in,
|
||||
.hljs-builtin-name,
|
||||
.hljs-literal,
|
||||
.hljs-type,
|
||||
.hljs-params,
|
||||
.hljs-meta,
|
||||
.hljs-link {
|
||||
color: #f5871f;
|
||||
}
|
||||
|
||||
/* Tomorrow Yellow */
|
||||
.hljs-attribute {
|
||||
color: #eab700;
|
||||
}
|
||||
|
||||
/* Tomorrow Green */
|
||||
.hljs-string,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-addition {
|
||||
color: #718c00;
|
||||
}
|
||||
|
||||
/* Tomorrow Blue */
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: #4271ae;
|
||||
}
|
||||
|
||||
/* Tomorrow Purple */
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag {
|
||||
color: #8959a8;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
background: white;
|
||||
color: #4d4d4c;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/* css for [json-html.core] */
|
||||
|
||||
|
||||
.jh-type-string, .jh-type-bool, .jh-type-number{
|
||||
font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
line-height: 20px;
|
||||
color: #333;
|
||||
float: left;
|
||||
width: 140px;
|
||||
padding-top: 5px;
|
||||
text-align: right;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.jh-root, .jh-type-object, .jh-type-array, .jh-key, .jh-value, .jh-root tr{
|
||||
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
|
||||
-moz-box-sizing: border-box; /* Firefox, other Gecko */
|
||||
box-sizing: border-box; /* Opera/IE 8+ */
|
||||
}
|
||||
|
||||
.jh-key, .jh-value{
|
||||
margin: 0;
|
||||
padding: 0.2em;
|
||||
}
|
||||
|
||||
.jh-empty-collection:before {
|
||||
content: '[]';
|
||||
}
|
||||
|
||||
.jh-empty-map:before {
|
||||
content: '{}';
|
||||
}
|
||||
|
||||
.jh-empty-set:before {
|
||||
content: '#{}';
|
||||
}
|
||||
|
||||
.jh-empty-string:before {
|
||||
content: '""';
|
||||
}
|
||||
|
||||
.jh-value{
|
||||
border-left: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.jh-type-bool, .jh-type-number{
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.jh-type-string{
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.jh-type-date{
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.jh-array-key{
|
||||
font-size: small;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jh-object-key, .jh-array-key{
|
||||
color: #444;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.jh-type-object > tbody > tr:nth-child(odd), .jh-type-array > tbody > tr:nth-child(odd){
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.jh-type-object > tbody > tr:nth-child(even), .jh-type-array > tbody > tr:nth-child(even){
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.jh-type-object, .jh-type-array{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.jh-root{
|
||||
border: 1px solid #ccc;
|
||||
margin: 0.2em;
|
||||
}
|
||||
|
||||
th.jh-key{
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.jh-type-object > tbody > tr, .jh-type-array > tr{
|
||||
border: 1px solid #ddd;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.jh-type-object > tbody > tr:last-child, .jh-type-array > tbody > tr:last-child{
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.jh-type-object > tbody > tr:hover, .jh-type-array > tbody > tr:hover{
|
||||
border: 1px solid #F99927;
|
||||
}
|
||||
|
||||
.jh-empty{
|
||||
color: #999;
|
||||
font-size: small;
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
body {
|
||||
font-family: "arial";
|
||||
font-size: 22px;
|
||||
font-smooth: always;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
padding-top: 65px;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: .8em;
|
||||
/* display: block; */
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.editor {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* .btn { */
|
||||
/* margin: 0 1.5em 0 1.5em; */
|
||||
/* } */
|
||||
|
||||
.edit-project {
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 { }
|
||||
|
||||
p {
|
||||
letter-spacing: 0.01rem;
|
||||
font-style: normal;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
img {
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Sortable tables */
|
||||
table.sortable thead {
|
||||
background-color:#eee;
|
||||
color:#666666;
|
||||
font-weight: bold;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
table.sortable td {
|
||||
font-size: .8em;
|
||||
font-family: "arial"
|
||||
}
|
||||
|
||||
.input-form {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
li {
|
||||
/* padding: .5em; */
|
||||
}
|
||||
|
||||
.secrets {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.password {
|
||||
font-size: 1.5;
|
||||
padding: 1em;
|
||||
/* display: inline-block; */
|
||||
}
|
||||
|
||||
|
||||
.navbar-text {
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.password .content {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.slices {
|
||||
|
||||
}
|
||||
|
||||
.slices ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.slices .content {
|
||||
font-size: .5em;
|
||||
}
|
||||
|
||||
.qrcode {
|
||||
float: left;
|
||||
margin-left: -19px;
|
||||
}
|
||||
|
||||
|
||||
.card {
|
||||
border: solid 3px #888;
|
||||
border-radius: 5px;
|
||||
padding: 1.5em 2.2em .8em 2.2em;
|
||||
}
|
||||
|
||||
.card .gravatar {
|
||||
margin-top: 19px;
|
||||
}
|
||||
|
||||
.balance {
|
||||
font-size: 2em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.wallet-details {
|
||||
display: inline-block;
|
||||
}
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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)))
|
||||
|
|
@ -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}))
|
|
@ -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)))
|
|
@ -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]))))
|
|
@ -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"}]]]])
|
|
@ -0,0 +1,16 @@
|
|||
webserver:
|
||||
anti-forgery: false
|
||||
ssl-redirect: false
|
||||
|
||||
admin:
|
||||
email: jaromil@dyne.org
|
||||
|
||||
just-auth:
|
||||
email-server: "mail.dyne.org"
|
||||
email-user: "xxxxxxxxxxxxxxxx"
|
||||
email-pass: "xxxxxxxxxxxxxxxx"
|
||||
email-address: "xxxxxxxxxxxxxxxx"
|
||||
email-admin: "xxxxxxxxxxxxxxxx"
|
||||
mongo-url: mongodb://localhost:27017/toaster
|
||||
mongo-user: toaster
|
||||
mongo-pass: toaster
|
Loading…
Reference in New Issue