Compare commits
20 Commits
c216f578ce
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| f77763887c | |||
| 420b230978 | |||
| d8c1ed992f | |||
| 1835ef8be1 | |||
| 032d8beae7 | |||
| 965e277948 | |||
| 30fb7ae1a2 | |||
| f28dcb2888 | |||
| 687dec948e | |||
| 527201ad89 | |||
| fa63f76791 | |||
| 81e0954a71 | |||
| de76c4887d | |||
| d162d16116 | |||
| b1bb0a28ec | |||
| 4da1117a3f | |||
| 2be199a214 | |||
| fe944519ac | |||
| 1e60140a6c | |||
| 3ab620a588 |
16
clojure_frontend/package-lock.json
generated
16
clojure_frontend/package-lock.json
generated
@@ -4,13 +4,23 @@
|
|||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"bulma": {
|
||||||
|
"version": "0.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.2.tgz",
|
||||||
|
"integrity": "sha512-6JHEu8U/1xsyOst/El5ImLcZIiE2JFXgvrz8GGWbnDLwTNRPJzdAM0aoUM1Ns0avALcVb6KZz9NhzmU53dGDcQ=="
|
||||||
|
},
|
||||||
|
"codemirror": {
|
||||||
|
"version": "5.40.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.40.2.tgz",
|
||||||
|
"integrity": "sha512-yoWuvEiD3v5vTwdoMc/wu/Ld6dh9K/yEiEBTKOPGM+/pN0gTAqFNtrLHv1IJ1UJvzFpNRvMi92XCi3+8/iIaEw=="
|
||||||
|
},
|
||||||
"dockerlint": {
|
"dockerlint": {
|
||||||
"version": "0.3.9",
|
"version": "0.3.9",
|
||||||
"resolved": "https://registry.npmjs.org/dockerlint/-/dockerlint-0.3.9.tgz",
|
"resolved": "https://registry.npmjs.org/dockerlint/-/dockerlint-0.3.9.tgz",
|
||||||
"integrity": "sha512-gps1IlRWx0hqhG7qZNYoF/Ae8wpnnPDGV0eYC60FdH2UscS4hZ+NFYX3Pusj/GImjLD/Pxkp/wib7CBb63yzZw==",
|
"integrity": "sha512-gps1IlRWx0hqhG7qZNYoF/Ae8wpnnPDGV0eYC60FdH2UscS4hZ+NFYX3Pusj/GImjLD/Pxkp/wib7CBb63yzZw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"sty": "0.6.1",
|
"sty": "^0.6.1",
|
||||||
"subarg": "1.0.0"
|
"subarg": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
@@ -28,7 +38,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
|
||||||
"integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=",
|
"integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"minimist": "1.2.0"
|
"minimist": "^1.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,9 @@
|
|||||||
[me.raynes/fs "1.4.6"]
|
[me.raynes/fs "1.4.6"]
|
||||||
[me.raynes/conch "0.8.0"]
|
[me.raynes/conch "0.8.0"]
|
||||||
;; time from joda-time
|
;; time from joda-time
|
||||||
[clj-time "0.14.4"]
|
[clj-time "0.15.0"]
|
||||||
[clojure-humanize "0.2.2"]]
|
[clojure-humanize "0.2.2"]
|
||||||
|
[de.ubercode.clostache/clostache "1.4.0"]]
|
||||||
:aliases {"test" "midje"}
|
:aliases {"test" "midje"}
|
||||||
:source-paths ["src"]
|
:source-paths ["src"]
|
||||||
:resource-paths ["resources"]
|
:resource-paths ["resources"]
|
||||||
@@ -39,10 +40,12 @@
|
|||||||
:uberwar {:init toaster.ring/init
|
:uberwar {:init toaster.ring/init
|
||||||
:handler toaster.handler/app}
|
:handler toaster.handler/app}
|
||||||
:mail toaster.handler
|
:mail toaster.handler
|
||||||
:npm {:dependencies [[dockerlint "0.3.9"]]}
|
:npm {:dependencies [[dockerlint "0.3.9"]
|
||||||
|
[bulma "0.7.2"]
|
||||||
|
[codemirror "5.40.2"]]}
|
||||||
:profiles { :dev {:dependencies [[javax.servlet/servlet-api "2.5"]
|
:profiles { :dev {:dependencies [[javax.servlet/servlet-api "2.5"]
|
||||||
[ring/ring-mock "0.3.2"]
|
[ring/ring-mock "0.3.2"]
|
||||||
[midje "1.9.2"]]
|
[midje "1.9.4"]]
|
||||||
:plugins [[lein-midje "3.1.3"]
|
:plugins [[lein-midje "3.1.3"]
|
||||||
[lein-npm "0.6.2"]]
|
[lein-npm "0.6.2"]]
|
||||||
:aot :all
|
:aot :all
|
||||||
|
|||||||
1
clojure_frontend/resources/public/static/css/bulma.min.css
vendored
Normal file
1
clojure_frontend/resources/public/static/css/bulma.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
346
clojure_frontend/resources/public/static/css/codemirror.css
Normal file
346
clojure_frontend/resources/public/static/css/codemirror.css
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
/* BASICS */
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
/* Set height, width, borders, and global font properties here */
|
||||||
|
font-family: monospace;
|
||||||
|
height: 300px;
|
||||||
|
color: black;
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PADDING */
|
||||||
|
|
||||||
|
.CodeMirror-lines {
|
||||||
|
padding: 4px 0; /* Vertical padding around content */
|
||||||
|
}
|
||||||
|
.CodeMirror pre {
|
||||||
|
padding: 0 4px; /* Horizontal padding of content */
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||||
|
background-color: white; /* The little square between H and V scrollbars */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GUTTER */
|
||||||
|
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.CodeMirror-linenumbers {}
|
||||||
|
.CodeMirror-linenumber {
|
||||||
|
padding: 0 3px 0 5px;
|
||||||
|
min-width: 20px;
|
||||||
|
text-align: right;
|
||||||
|
color: #999;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-guttermarker { color: black; }
|
||||||
|
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||||
|
|
||||||
|
/* CURSOR */
|
||||||
|
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
border-left: 1px solid black;
|
||||||
|
border-right: none;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
/* Shown when moving in bi-directional text */
|
||||||
|
.CodeMirror div.CodeMirror-secondarycursor {
|
||||||
|
border-left: 1px solid silver;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor .CodeMirror-cursor {
|
||||||
|
width: auto;
|
||||||
|
border: 0 !important;
|
||||||
|
background: #7e7;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor div.CodeMirror-cursors {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor-mark {
|
||||||
|
background-color: rgba(20, 255, 20, 0.5);
|
||||||
|
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||||
|
-moz-animation: blink 1.06s steps(1) infinite;
|
||||||
|
animation: blink 1.06s steps(1) infinite;
|
||||||
|
}
|
||||||
|
.cm-animate-fat-cursor {
|
||||||
|
width: auto;
|
||||||
|
border: 0;
|
||||||
|
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||||
|
-moz-animation: blink 1.06s steps(1) infinite;
|
||||||
|
animation: blink 1.06s steps(1) infinite;
|
||||||
|
background-color: #7e7;
|
||||||
|
}
|
||||||
|
@-moz-keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
@keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Can style cursor different in overwrite (non-insert) mode */
|
||||||
|
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||||
|
|
||||||
|
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||||
|
|
||||||
|
.CodeMirror-rulers {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0; top: -50px; bottom: -20px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.CodeMirror-ruler {
|
||||||
|
border-left: 1px solid #ccc;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DEFAULT THEME */
|
||||||
|
|
||||||
|
.cm-s-default .cm-header {color: blue;}
|
||||||
|
.cm-s-default .cm-quote {color: #090;}
|
||||||
|
.cm-negative {color: #d44;}
|
||||||
|
.cm-positive {color: #292;}
|
||||||
|
.cm-header, .cm-strong {font-weight: bold;}
|
||||||
|
.cm-em {font-style: italic;}
|
||||||
|
.cm-link {text-decoration: underline;}
|
||||||
|
.cm-strikethrough {text-decoration: line-through;}
|
||||||
|
|
||||||
|
.cm-s-default .cm-keyword {color: #708;}
|
||||||
|
.cm-s-default .cm-atom {color: #219;}
|
||||||
|
.cm-s-default .cm-number {color: #164;}
|
||||||
|
.cm-s-default .cm-def {color: #00f;}
|
||||||
|
.cm-s-default .cm-variable,
|
||||||
|
.cm-s-default .cm-punctuation,
|
||||||
|
.cm-s-default .cm-property,
|
||||||
|
.cm-s-default .cm-operator {}
|
||||||
|
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||||
|
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
|
||||||
|
.cm-s-default .cm-comment {color: #a50;}
|
||||||
|
.cm-s-default .cm-string {color: #a11;}
|
||||||
|
.cm-s-default .cm-string-2 {color: #f50;}
|
||||||
|
.cm-s-default .cm-meta {color: #555;}
|
||||||
|
.cm-s-default .cm-qualifier {color: #555;}
|
||||||
|
.cm-s-default .cm-builtin {color: #30a;}
|
||||||
|
.cm-s-default .cm-bracket {color: #997;}
|
||||||
|
.cm-s-default .cm-tag {color: #170;}
|
||||||
|
.cm-s-default .cm-attribute {color: #00c;}
|
||||||
|
.cm-s-default .cm-hr {color: #999;}
|
||||||
|
.cm-s-default .cm-link {color: #00c;}
|
||||||
|
|
||||||
|
.cm-s-default .cm-error {color: #f00;}
|
||||||
|
.cm-invalidchar {color: #f00;}
|
||||||
|
|
||||||
|
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||||
|
|
||||||
|
/* Default styles for common addons */
|
||||||
|
|
||||||
|
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
|
||||||
|
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
|
||||||
|
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||||
|
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||||
|
|
||||||
|
/* STOP */
|
||||||
|
|
||||||
|
/* The rest of this file contains styles related to the mechanics of
|
||||||
|
the editor. You probably shouldn't touch them. */
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scroll {
|
||||||
|
overflow: scroll !important; /* Things will break if this is overridden */
|
||||||
|
/* 30px is the magic margin used to hide the element's real scrollbars */
|
||||||
|
/* See overflow: hidden in .CodeMirror */
|
||||||
|
margin-bottom: -30px; margin-right: -30px;
|
||||||
|
padding-bottom: 30px;
|
||||||
|
height: 100%;
|
||||||
|
outline: none; /* Prevent dragging from highlighting the element */
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.CodeMirror-sizer {
|
||||||
|
position: relative;
|
||||||
|
border-right: 30px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||||
|
before actual scrolling happens, thus preventing shaking and
|
||||||
|
flickering artifacts. */
|
||||||
|
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 6;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.CodeMirror-vscrollbar {
|
||||||
|
right: 0; top: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
.CodeMirror-hscrollbar {
|
||||||
|
bottom: 0; left: 0;
|
||||||
|
overflow-y: hidden;
|
||||||
|
overflow-x: scroll;
|
||||||
|
}
|
||||||
|
.CodeMirror-scrollbar-filler {
|
||||||
|
right: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-filler {
|
||||||
|
left: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
position: absolute; left: 0; top: 0;
|
||||||
|
min-height: 100%;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter {
|
||||||
|
white-space: normal;
|
||||||
|
height: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-bottom: -30px;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 4;
|
||||||
|
background: none !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-elt {
|
||||||
|
position: absolute;
|
||||||
|
cursor: default;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||||
|
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||||
|
|
||||||
|
.CodeMirror-lines {
|
||||||
|
cursor: text;
|
||||||
|
min-height: 1px; /* prevents collapsing before first draw */
|
||||||
|
}
|
||||||
|
.CodeMirror pre {
|
||||||
|
/* Reset some styles that the rest of the page might have set */
|
||||||
|
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||||
|
border-width: 0;
|
||||||
|
background: transparent;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
z-index: 2;
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
-webkit-font-variant-ligatures: contextual;
|
||||||
|
font-variant-ligatures: contextual;
|
||||||
|
}
|
||||||
|
.CodeMirror-wrap pre {
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-linebackground {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0; top: 0; bottom: 0;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-linewidget {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 0.1px; /* Force widget margins to stay inside of the container */
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-widget {}
|
||||||
|
|
||||||
|
.CodeMirror-rtl pre { direction: rtl; }
|
||||||
|
|
||||||
|
.CodeMirror-code {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force content-box sizing for the elements where we expect it */
|
||||||
|
.CodeMirror-scroll,
|
||||||
|
.CodeMirror-sizer,
|
||||||
|
.CodeMirror-gutter,
|
||||||
|
.CodeMirror-gutters,
|
||||||
|
.CodeMirror-linenumber {
|
||||||
|
-moz-box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-measure {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.CodeMirror-measure pre { position: static; }
|
||||||
|
|
||||||
|
div.CodeMirror-cursors {
|
||||||
|
visibility: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
div.CodeMirror-dragcursors {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-focused div.CodeMirror-cursors {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-selected { background: #d9d9d9; }
|
||||||
|
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||||
|
.CodeMirror-crosshair { cursor: crosshair; }
|
||||||
|
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||||
|
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||||
|
|
||||||
|
.cm-searching {
|
||||||
|
background-color: #ffa;
|
||||||
|
background-color: rgba(255, 255, 0, .4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Used to force a border model for a node */
|
||||||
|
.cm-force-border { padding-right: .1px; }
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
/* Hide the cursor when printing */
|
||||||
|
.CodeMirror div.CodeMirror-cursors {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See issue #2901 */
|
||||||
|
.cm-tab-wrap-hack:after { content: ''; }
|
||||||
|
|
||||||
|
/* Help users use markselection to safely style text background */
|
||||||
|
span.CodeMirror-selectedtext { background: none; }
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,9 +1,10 @@
|
|||||||
body {
|
body {
|
||||||
font-family: "arial";
|
/* padding-top: 65px; */
|
||||||
font-size: 22px;
|
}
|
||||||
font-smooth: always;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
/* horizontal margins on wide screens */
|
||||||
padding-top: 65px;
|
@media (min-width: 768px) {
|
||||||
|
.container { width: 36em; }
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
@@ -31,93 +32,102 @@ code {
|
|||||||
|
|
||||||
h1, h2, h3, h4 { }
|
h1, h2, h3, h4 { }
|
||||||
|
|
||||||
|
html,body {
|
||||||
|
font-family: 'Ubuntu', sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 300;
|
||||||
|
font-smooth: always;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|
||||||
|
}
|
||||||
|
.hero.is-success {
|
||||||
|
background: #F2F6FA;
|
||||||
|
}
|
||||||
|
.hero .nav, .hero.is-success .nav {
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.box {
|
||||||
|
margin-top: 5rem;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
margin-top: -70px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
}
|
||||||
|
.avatar img {
|
||||||
|
padding: 5px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
-webkit-box-shadow: 0 2px 3px rgba(10,10,10,.1), 0 0 0 1px rgba(10,10,10,.1);
|
||||||
|
box-shadow: 0 2px 3px rgba(10,10,10,.1), 0 0 0 1px rgba(10,10,10,.1);
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
p {
|
p {
|
||||||
letter-spacing: 0.01rem;
|
font-weight: 700;
|
||||||
font-style: normal;
|
}
|
||||||
line-height: 1.5;
|
p.subtitle {
|
||||||
|
padding-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
/* inputfile js for bulma from https://jsfiddle.net/chintanbanugaria/uzva5byy/ */
|
||||||
width: auto;
|
|
||||||
height: auto;
|
.inputfile {
|
||||||
max-width: 100%;
|
width: 0.1px;
|
||||||
vertical-align: middle;
|
height: 0.1px;
|
||||||
overflow: hidden;
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
z-index: -1;
|
||||||
|
margin-top:10px;
|
||||||
}
|
}
|
||||||
|
.inputfile + label {
|
||||||
/* Sortable tables */
|
|
||||||
table.sortable thead {
|
/* 20px */
|
||||||
background-color:#eee;
|
width: 50%;
|
||||||
color:#666666;
|
text-overflow: ellipsis;
|
||||||
font-weight: bold;
|
white-space: nowrap;
|
||||||
cursor: default;
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-left: 0.75em;
|
||||||
|
padding-right: 0.75em;
|
||||||
|
border: 1px solid #dbdbdb;
|
||||||
|
height: 2.285em;
|
||||||
|
box-shadow: inset 0 1px 2px rgba(10, 10, 10, 0.1);
|
||||||
|
line-height: 1.5rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding-top: 0.3em;
|
||||||
|
|
||||||
|
/* 10px 20px */
|
||||||
}
|
}
|
||||||
|
.no-js .inputfile + label {
|
||||||
table.sortable td {
|
display: none;
|
||||||
font-size: .8em;
|
|
||||||
font-family: "arial"
|
|
||||||
}
|
}
|
||||||
|
.inputfile:focus + label,
|
||||||
.input-form {
|
.inputfile.has-focus + label {
|
||||||
clear: right;
|
border-color: #00d1b2;
|
||||||
}
|
}
|
||||||
|
.inputfile + label * {
|
||||||
li {
|
/* pointer-events: none; */
|
||||||
/* padding: .5em; */
|
/* in case of FastClick lib use */
|
||||||
}
|
}
|
||||||
|
.inputfile + label svg {
|
||||||
.secrets {
|
width: 1em;
|
||||||
padding: 1em;
|
height: 1em;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-top: -0.25em;
|
||||||
|
/* 4px */
|
||||||
|
margin-right: 0.25em;
|
||||||
|
/* 4px */
|
||||||
}
|
}
|
||||||
|
.inputfile-2 + label {
|
||||||
.password {
|
border: 1px solid #dbdbdb;
|
||||||
font-size: 1.5;
|
|
||||||
padding: 1em;
|
|
||||||
/* display: inline-block; */
|
|
||||||
}
|
}
|
||||||
|
.inputfile-2:focus + label,
|
||||||
|
.inputfile-2.has-focus + label,
|
||||||
.navbar-text {
|
.inputfile-2 + label:hover {
|
||||||
margin-top: 22px;
|
border-color: #00d1b2;
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
clojure_frontend/resources/public/static/img/cafudda.jpg
Normal file
BIN
clojure_frontend/resources/public/static/img/cafudda.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
BIN
clojure_frontend/resources/public/static/img/decode_logo.png
Normal file
BIN
clojure_frontend/resources/public/static/img/decode_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
clojure_frontend/resources/public/static/img/devuan_logo.png
Normal file
BIN
clojure_frontend/resources/public/static/img/devuan_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
clojure_frontend/resources/public/static/img/ec_logo.png
Normal file
BIN
clojure_frontend/resources/public/static/img/ec_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,211 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"), require("../../addon/mode/simple"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror", "../../addon/mode/simple"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var from = "from";
|
||||||
|
var fromRegex = new RegExp("^(\\s*)\\b(" + from + ")\\b", "i");
|
||||||
|
|
||||||
|
var shells = ["run", "cmd", "entrypoint", "shell"];
|
||||||
|
var shellsAsArrayRegex = new RegExp("^(\\s*)(" + shells.join('|') + ")(\\s+\\[)", "i");
|
||||||
|
|
||||||
|
var expose = "expose";
|
||||||
|
var exposeRegex = new RegExp("^(\\s*)(" + expose + ")(\\s+)", "i");
|
||||||
|
|
||||||
|
var others = [
|
||||||
|
"arg", "from", "maintainer", "label", "env",
|
||||||
|
"add", "copy", "volume", "user",
|
||||||
|
"workdir", "onbuild", "stopsignal", "healthcheck", "shell"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Collect all Dockerfile directives
|
||||||
|
var instructions = [from, expose].concat(shells).concat(others),
|
||||||
|
instructionRegex = "(" + instructions.join('|') + ")",
|
||||||
|
instructionOnlyLine = new RegExp("^(\\s*)" + instructionRegex + "(\\s*)(#.*)?$", "i"),
|
||||||
|
instructionWithArguments = new RegExp("^(\\s*)" + instructionRegex + "(\\s+)", "i");
|
||||||
|
|
||||||
|
CodeMirror.defineSimpleMode("dockerfile", {
|
||||||
|
start: [
|
||||||
|
// Block comment: This is a line starting with a comment
|
||||||
|
{
|
||||||
|
regex: /^\s*#.*$/,
|
||||||
|
sol: true,
|
||||||
|
token: "comment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: fromRegex,
|
||||||
|
token: [null, "keyword"],
|
||||||
|
sol: true,
|
||||||
|
next: "from"
|
||||||
|
},
|
||||||
|
// Highlight an instruction without any arguments (for convenience)
|
||||||
|
{
|
||||||
|
regex: instructionOnlyLine,
|
||||||
|
token: [null, "keyword", null, "error"],
|
||||||
|
sol: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: shellsAsArrayRegex,
|
||||||
|
token: [null, "keyword", null],
|
||||||
|
sol: true,
|
||||||
|
next: "array"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: exposeRegex,
|
||||||
|
token: [null, "keyword", null],
|
||||||
|
sol: true,
|
||||||
|
next: "expose"
|
||||||
|
},
|
||||||
|
// Highlight an instruction followed by arguments
|
||||||
|
{
|
||||||
|
regex: instructionWithArguments,
|
||||||
|
token: [null, "keyword", null],
|
||||||
|
sol: true,
|
||||||
|
next: "arguments"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /./,
|
||||||
|
token: null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
from: [
|
||||||
|
{
|
||||||
|
regex: /\s*$/,
|
||||||
|
token: null,
|
||||||
|
next: "start"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Line comment without instruction arguments is an error
|
||||||
|
regex: /(\s*)(#.*)$/,
|
||||||
|
token: [null, "error"],
|
||||||
|
next: "start"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /(\s*\S+\s+)(as)/i,
|
||||||
|
token: [null, "keyword"],
|
||||||
|
next: "start"
|
||||||
|
},
|
||||||
|
// Fail safe return to start
|
||||||
|
{
|
||||||
|
token: null,
|
||||||
|
next: "start"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
single: [
|
||||||
|
{
|
||||||
|
regex: /(?:[^\\']|\\.)/,
|
||||||
|
token: "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /'/,
|
||||||
|
token: "string",
|
||||||
|
pop: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
double: [
|
||||||
|
{
|
||||||
|
regex: /(?:[^\\"]|\\.)/,
|
||||||
|
token: "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /"/,
|
||||||
|
token: "string",
|
||||||
|
pop: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
array: [
|
||||||
|
{
|
||||||
|
regex: /\]/,
|
||||||
|
token: null,
|
||||||
|
next: "start"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /"(?:[^\\"]|\\.)*"?/,
|
||||||
|
token: "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
expose: [
|
||||||
|
{
|
||||||
|
regex: /\d+$/,
|
||||||
|
token: "number",
|
||||||
|
next: "start"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /[^\d]+$/,
|
||||||
|
token: null,
|
||||||
|
next: "start"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /\d+/,
|
||||||
|
token: "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /[^\d]+/,
|
||||||
|
token: null
|
||||||
|
},
|
||||||
|
// Fail safe return to start
|
||||||
|
{
|
||||||
|
token: null,
|
||||||
|
next: "start"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
regex: /^\s*#.*$/,
|
||||||
|
sol: true,
|
||||||
|
token: "comment"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /"(?:[^\\"]|\\.)*"?$/,
|
||||||
|
token: "string",
|
||||||
|
next: "start"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /"/,
|
||||||
|
token: "string",
|
||||||
|
push: "double"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /'(?:[^\\']|\\.)*'?$/,
|
||||||
|
token: "string",
|
||||||
|
next: "start"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /'/,
|
||||||
|
token: "string",
|
||||||
|
push: "single"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /[^#"']+[\\`]$/,
|
||||||
|
token: null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /[^#"']+$/,
|
||||||
|
token: null,
|
||||||
|
next: "start"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
regex: /[^#"']+/,
|
||||||
|
token: null
|
||||||
|
},
|
||||||
|
// Fail safe return to start
|
||||||
|
{
|
||||||
|
token: null,
|
||||||
|
next: "start"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
meta: {
|
||||||
|
lineComment: "#"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineMIME("text/x-dockerfile", "dockerfile");
|
||||||
|
});
|
||||||
216
clojure_frontend/resources/public/static/js/codemirror-simple.js
Normal file
216
clojure_frontend/resources/public/static/js/codemirror-simple.js
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
||||||
|
// Distributed under an MIT license: https://codemirror.net/LICENSE
|
||||||
|
|
||||||
|
(function(mod) {
|
||||||
|
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
||||||
|
mod(require("../../lib/codemirror"));
|
||||||
|
else if (typeof define == "function" && define.amd) // AMD
|
||||||
|
define(["../../lib/codemirror"], mod);
|
||||||
|
else // Plain browser env
|
||||||
|
mod(CodeMirror);
|
||||||
|
})(function(CodeMirror) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
CodeMirror.defineSimpleMode = function(name, states) {
|
||||||
|
CodeMirror.defineMode(name, function(config) {
|
||||||
|
return CodeMirror.simpleMode(config, states);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
CodeMirror.simpleMode = function(config, states) {
|
||||||
|
ensureState(states, "start");
|
||||||
|
var states_ = {}, meta = states.meta || {}, hasIndentation = false;
|
||||||
|
for (var state in states) if (state != meta && states.hasOwnProperty(state)) {
|
||||||
|
var list = states_[state] = [], orig = states[state];
|
||||||
|
for (var i = 0; i < orig.length; i++) {
|
||||||
|
var data = orig[i];
|
||||||
|
list.push(new Rule(data, states));
|
||||||
|
if (data.indent || data.dedent) hasIndentation = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var mode = {
|
||||||
|
startState: function() {
|
||||||
|
return {state: "start", pending: null,
|
||||||
|
local: null, localState: null,
|
||||||
|
indent: hasIndentation ? [] : null};
|
||||||
|
},
|
||||||
|
copyState: function(state) {
|
||||||
|
var s = {state: state.state, pending: state.pending,
|
||||||
|
local: state.local, localState: null,
|
||||||
|
indent: state.indent && state.indent.slice(0)};
|
||||||
|
if (state.localState)
|
||||||
|
s.localState = CodeMirror.copyState(state.local.mode, state.localState);
|
||||||
|
if (state.stack)
|
||||||
|
s.stack = state.stack.slice(0);
|
||||||
|
for (var pers = state.persistentStates; pers; pers = pers.next)
|
||||||
|
s.persistentStates = {mode: pers.mode,
|
||||||
|
spec: pers.spec,
|
||||||
|
state: pers.state == state.localState ? s.localState : CodeMirror.copyState(pers.mode, pers.state),
|
||||||
|
next: s.persistentStates};
|
||||||
|
return s;
|
||||||
|
},
|
||||||
|
token: tokenFunction(states_, config),
|
||||||
|
innerMode: function(state) { return state.local && {mode: state.local.mode, state: state.localState}; },
|
||||||
|
indent: indentFunction(states_, meta)
|
||||||
|
};
|
||||||
|
if (meta) for (var prop in meta) if (meta.hasOwnProperty(prop))
|
||||||
|
mode[prop] = meta[prop];
|
||||||
|
return mode;
|
||||||
|
};
|
||||||
|
|
||||||
|
function ensureState(states, name) {
|
||||||
|
if (!states.hasOwnProperty(name))
|
||||||
|
throw new Error("Undefined state " + name + " in simple mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
function toRegex(val, caret) {
|
||||||
|
if (!val) return /(?:)/;
|
||||||
|
var flags = "";
|
||||||
|
if (val instanceof RegExp) {
|
||||||
|
if (val.ignoreCase) flags = "i";
|
||||||
|
val = val.source;
|
||||||
|
} else {
|
||||||
|
val = String(val);
|
||||||
|
}
|
||||||
|
return new RegExp((caret === false ? "" : "^") + "(?:" + val + ")", flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
function asToken(val) {
|
||||||
|
if (!val) return null;
|
||||||
|
if (val.apply) return val
|
||||||
|
if (typeof val == "string") return val.replace(/\./g, " ");
|
||||||
|
var result = [];
|
||||||
|
for (var i = 0; i < val.length; i++)
|
||||||
|
result.push(val[i] && val[i].replace(/\./g, " "));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Rule(data, states) {
|
||||||
|
if (data.next || data.push) ensureState(states, data.next || data.push);
|
||||||
|
this.regex = toRegex(data.regex);
|
||||||
|
this.token = asToken(data.token);
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokenFunction(states, config) {
|
||||||
|
return function(stream, state) {
|
||||||
|
if (state.pending) {
|
||||||
|
var pend = state.pending.shift();
|
||||||
|
if (state.pending.length == 0) state.pending = null;
|
||||||
|
stream.pos += pend.text.length;
|
||||||
|
return pend.token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.local) {
|
||||||
|
if (state.local.end && stream.match(state.local.end)) {
|
||||||
|
var tok = state.local.endToken || null;
|
||||||
|
state.local = state.localState = null;
|
||||||
|
return tok;
|
||||||
|
} else {
|
||||||
|
var tok = state.local.mode.token(stream, state.localState), m;
|
||||||
|
if (state.local.endScan && (m = state.local.endScan.exec(stream.current())))
|
||||||
|
stream.pos = stream.start + m.index;
|
||||||
|
return tok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var curState = states[state.state];
|
||||||
|
for (var i = 0; i < curState.length; i++) {
|
||||||
|
var rule = curState[i];
|
||||||
|
var matches = (!rule.data.sol || stream.sol()) && stream.match(rule.regex);
|
||||||
|
if (matches) {
|
||||||
|
if (rule.data.next) {
|
||||||
|
state.state = rule.data.next;
|
||||||
|
} else if (rule.data.push) {
|
||||||
|
(state.stack || (state.stack = [])).push(state.state);
|
||||||
|
state.state = rule.data.push;
|
||||||
|
} else if (rule.data.pop && state.stack && state.stack.length) {
|
||||||
|
state.state = state.stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.data.mode)
|
||||||
|
enterLocalMode(config, state, rule.data.mode, rule.token);
|
||||||
|
if (rule.data.indent)
|
||||||
|
state.indent.push(stream.indentation() + config.indentUnit);
|
||||||
|
if (rule.data.dedent)
|
||||||
|
state.indent.pop();
|
||||||
|
var token = rule.token
|
||||||
|
if (token && token.apply) token = token(matches)
|
||||||
|
if (matches.length > 2 && rule.token && typeof rule.token != "string") {
|
||||||
|
state.pending = [];
|
||||||
|
for (var j = 2; j < matches.length; j++)
|
||||||
|
if (matches[j])
|
||||||
|
state.pending.push({text: matches[j], token: rule.token[j - 1]});
|
||||||
|
stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0));
|
||||||
|
return token[0];
|
||||||
|
} else if (token && token.join) {
|
||||||
|
return token[0];
|
||||||
|
} else {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.next();
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmp(a, b) {
|
||||||
|
if (a === b) return true;
|
||||||
|
if (!a || typeof a != "object" || !b || typeof b != "object") return false;
|
||||||
|
var props = 0;
|
||||||
|
for (var prop in a) if (a.hasOwnProperty(prop)) {
|
||||||
|
if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) return false;
|
||||||
|
props++;
|
||||||
|
}
|
||||||
|
for (var prop in b) if (b.hasOwnProperty(prop)) props--;
|
||||||
|
return props == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function enterLocalMode(config, state, spec, token) {
|
||||||
|
var pers;
|
||||||
|
if (spec.persistent) for (var p = state.persistentStates; p && !pers; p = p.next)
|
||||||
|
if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode == p.mode) pers = p;
|
||||||
|
var mode = pers ? pers.mode : spec.mode || CodeMirror.getMode(config, spec.spec);
|
||||||
|
var lState = pers ? pers.state : CodeMirror.startState(mode);
|
||||||
|
if (spec.persistent && !pers)
|
||||||
|
state.persistentStates = {mode: mode, spec: spec.spec, state: lState, next: state.persistentStates};
|
||||||
|
|
||||||
|
state.localState = lState;
|
||||||
|
state.local = {mode: mode,
|
||||||
|
end: spec.end && toRegex(spec.end),
|
||||||
|
endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false),
|
||||||
|
endToken: token && token.join ? token[token.length - 1] : token};
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexOf(val, arr) {
|
||||||
|
for (var i = 0; i < arr.length; i++) if (arr[i] === val) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function indentFunction(states, meta) {
|
||||||
|
return function(state, textAfter, line) {
|
||||||
|
if (state.local && state.local.mode.indent)
|
||||||
|
return state.local.mode.indent(state.localState, textAfter, line);
|
||||||
|
if (state.indent == null || state.local || meta.dontIndentStates && indexOf(state.state, meta.dontIndentStates) > -1)
|
||||||
|
return CodeMirror.Pass;
|
||||||
|
|
||||||
|
var pos = state.indent.length - 1, rules = states[state.state];
|
||||||
|
scan: for (;;) {
|
||||||
|
for (var i = 0; i < rules.length; i++) {
|
||||||
|
var rule = rules[i];
|
||||||
|
if (rule.data.dedent && rule.data.dedentIfLineStart !== false) {
|
||||||
|
var m = rule.regex.exec(textAfter);
|
||||||
|
if (m && m[0]) {
|
||||||
|
pos--;
|
||||||
|
if (rule.next || rule.push) rules = states[rule.next || rule.push];
|
||||||
|
textAfter = textAfter.slice(m[0].length);
|
||||||
|
continue scan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return pos < 0 ? 0 : state.indent[pos];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
9705
clojure_frontend/resources/public/static/js/codemirror.js
Normal file
9705
clojure_frontend/resources/public/static/js/codemirror.js
Normal file
File diff suppressed because it is too large
Load Diff
24
clojure_frontend/resources/templates/body_addjob.html
Normal file
24
clojure_frontend/resources/templates/body_addjob.html
Normal 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" enctype="multipart/form-data" method="post">
|
||||||
|
<div class="has-label is-fullwidth">
|
||||||
|
<label class="file-label">
|
||||||
|
<input name="file" 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>
|
||||||
28
clojure_frontend/resources/templates/body_footer.html
Normal file
28
clojure_frontend/resources/templates/body_footer.html
Normal 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>
|
||||||
27
clojure_frontend/resources/templates/body_loginform.html
Normal file
27
clojure_frontend/resources/templates/body_loginform.html
Normal 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>
|
||||||
33
clojure_frontend/resources/templates/body_signupform.html
Normal file
33
clojure_frontend/resources/templates/body_signupform.html
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<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>
|
||||||
|
<p class="subtitle has-text-grey">...or <a href="/login">login with an existing account</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
15
clojure_frontend/resources/templates/html_head.html
Normal file
15
clojure_frontend/resources/templates/html_head.html
Normal 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 :: rapid integration for decentralized services, from embedded to the cloud</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="/static/css/font-awesome.min.css" rel="stylesheet" type="text/css" />
|
||||||
|
<link href="/static/css/toaster.css" rel="stylesheet" type="text/css" />
|
||||||
|
</head>
|
||||||
110
clojure_frontend/src/toaster/bulma.clj
Normal file
110
clojure_frontend/src/toaster/bulma.clj
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
;; 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.bulma
|
||||||
|
(:require [clojure.java.io :as io]
|
||||||
|
[clojure.data.csv :as csv]
|
||||||
|
[taoensso.timbre :as log]
|
||||||
|
[yaml.core :as yaml]
|
||||||
|
[hiccup.page :as page]
|
||||||
|
[hiccup.form :as hf]))
|
||||||
|
|
||||||
|
(defn button
|
||||||
|
([url text] (button url text [:p]))
|
||||||
|
|
||||||
|
([url text field] (button url text field "button"))
|
||||||
|
|
||||||
|
([url text field type]
|
||||||
|
(hf/form-to [:post url]
|
||||||
|
field ;; can be an hidden key/value field (project,
|
||||||
|
;; person, etc using hf/hidden-field)
|
||||||
|
[:p {:class "control"}
|
||||||
|
(hf/submit-button {:class (str "button " 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")])
|
||||||
|
|
||||||
|
;; 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"}]])
|
||||||
|
|
||||||
|
(def brand-img "/static/img/cafudda.jpg")
|
||||||
@@ -18,152 +18,167 @@
|
|||||||
|
|
||||||
(ns toaster.handler
|
(ns toaster.handler
|
||||||
(:require
|
(:require
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[clojure.data.json :as json]
|
[clojure.data.json :as json]
|
||||||
[compojure.core :refer :all]
|
[compojure.core :refer :all]
|
||||||
[compojure.handler :refer :all]
|
[compojure.handler :refer :all]
|
||||||
[compojure.route :as route]
|
[compojure.route :as route]
|
||||||
[compojure.response :as response]
|
[compojure.response :as response]
|
||||||
|
|
||||||
[ring.adapter.jetty :refer :all]
|
[ring.adapter.jetty :refer :all]
|
||||||
[ring.middleware.session :refer :all]
|
[ring.middleware.session :refer :all]
|
||||||
[ring.middleware.accept :refer [wrap-accept]]
|
[ring.middleware.accept :refer [wrap-accept]]
|
||||||
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
|
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]
|
||||||
|
|
||||||
[failjure.core :as f]
|
[failjure.core :as f]
|
||||||
[taoensso.timbre :as log]
|
[taoensso.timbre :as log]
|
||||||
[just-auth.core :as auth]
|
[just-auth.core :as auth]
|
||||||
|
|
||||||
[toaster.session :as s]
|
[toaster.session :as s]
|
||||||
[toaster.config :as conf]
|
[toaster.config :as conf]
|
||||||
[toaster.webpage :as web]
|
[toaster.ring :as ring]
|
||||||
[toaster.ring :as ring]
|
[toaster.views :as views]
|
||||||
[toaster.views :as views]
|
[toaster.profiles :as profile])
|
||||||
[toaster.jobs :as job])
|
|
||||||
(:gen-class))
|
(:gen-class))
|
||||||
|
|
||||||
(defonce config (conf/load-config "toaster" conf/default-settings))
|
(defonce config (conf/load-config "toaster" conf/default-settings))
|
||||||
|
|
||||||
|
(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)
|
||||||
|
account (if (conf/q config [:webserver :mock-auth])
|
||||||
|
{:email "mock@dyne.org"
|
||||||
|
:name "MockUser"
|
||||||
|
:activated true}
|
||||||
|
;; else
|
||||||
|
(s/check-account request))]
|
||||||
|
(fun request config account)
|
||||||
|
(f/when-failed [e]
|
||||||
|
(s/render [:body
|
||||||
|
(s/notify (f/message e) "is-error")
|
||||||
|
(s/resource s/login)]))))
|
||||||
|
|
||||||
(defroutes
|
(defroutes
|
||||||
app-routes
|
app-routes
|
||||||
|
|
||||||
(GET "/" request (web/render "Hello World!")) ;; web/readme))
|
(GET "/" request
|
||||||
|
(f/attempt-all
|
||||||
|
[db (s/check-database)
|
||||||
|
conf (s/check-config request)]
|
||||||
|
(f/if-let-ok? [account (s/check-account request)]
|
||||||
|
(s/render [:body (views/dashboard account)])
|
||||||
|
;; else
|
||||||
|
(s/render [:body (s/resource s/login)]))
|
||||||
|
(f/when-failed[e]
|
||||||
|
(s/render [:body (s/error "backend missing" e)]))))
|
||||||
|
|
||||||
;; NEW ROUTES HERE
|
;; NEW ROUTES HERE
|
||||||
(GET "/upload" request
|
|
||||||
(->> (fn [req conf acct]
|
|
||||||
(web/render acct views/dockerfile-upload-form))
|
|
||||||
(s/check request)))
|
|
||||||
|
|
||||||
(POST "/dockerfile" request
|
(POST "/dockerfile" request
|
||||||
(->> views/dockerfile-upload-post
|
(->> (fn [req conf acct]
|
||||||
(s/check request)))
|
(s/render acct
|
||||||
|
[:body
|
||||||
|
(s/upload req conf acct views/add-job)
|
||||||
|
(views/dashboard acct)]))
|
||||||
|
(auth-wrap request)))
|
||||||
|
|
||||||
(GET "/edit" request
|
(POST "/remove" request
|
||||||
(->> (fn [req conf acct]
|
(->> (fn [req conf acct]
|
||||||
(web/render acct views/dockerfile-edit-form))
|
(s/render acct [:body
|
||||||
(s/check request)))
|
(views/remove-job req conf acct)
|
||||||
|
(views/dashboard acct)]))
|
||||||
|
(auth-wrap request)))
|
||||||
|
|
||||||
(GET "/list" request
|
(POST "/start" request
|
||||||
(->> views/list-jobs
|
(->> (fn [req conf acct]
|
||||||
(s/check request)))
|
(s/render acct [:body
|
||||||
|
(views/start-job req conf acct)
|
||||||
|
(views/dashboard acct)]))
|
||||||
|
(auth-wrap request)))
|
||||||
|
|
||||||
|
(POST "/view" request
|
||||||
|
(->> (fn [req conf acct]
|
||||||
|
(s/render acct [:body
|
||||||
|
(views/view-job req conf acct)
|
||||||
|
(views/dashboard acct)]))
|
||||||
|
(auth-wrap request)))
|
||||||
|
|
||||||
|
(GET "/error" request
|
||||||
|
(->> (fn [req conf acct]
|
||||||
|
(s/render acct [:body
|
||||||
|
(s/notify "Generic Error Page" "is-error")
|
||||||
|
(views/dashboard acct)]))
|
||||||
|
(auth-wrap request)))
|
||||||
|
|
||||||
;; JUST-AUTH ROUTES
|
;; JUST-AUTH ROUTES
|
||||||
(GET "/login" request
|
(GET "/login" request (s/render-template s/login))
|
||||||
(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
|
(POST "/login" request
|
||||||
(f/attempt-all
|
(f/attempt-all
|
||||||
[username (s/param request :username)
|
[username (s/param request :username)
|
||||||
password (s/param request :password)
|
password (s/param request :password)
|
||||||
logged (auth/sign-in
|
logged (auth/sign-in
|
||||||
@ring/auth username password {})]
|
@ring/auth username password {})]
|
||||||
;; TODO: pass :ip-address in last argument map
|
;; TODO: pass :ip-address in last argument map
|
||||||
(let [session {:session {:config config
|
(let [session {:session {:config config
|
||||||
:auth logged}}]
|
:auth logged}}]
|
||||||
(conj session
|
(conj session
|
||||||
(views/list-jobs request config logged)))
|
(s/render logged [:body (views/dashboard logged)])))
|
||||||
;; (web/render
|
(f/when-failed [e]
|
||||||
;; logged
|
(s/render
|
||||||
;; [:div
|
[:body
|
||||||
;; [:h1 "Logged in: " username]
|
(s/error "Login failed" e)]))))
|
||||||
;; views/welcome-menu])))
|
|
||||||
(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
|
(GET "/logout" request
|
||||||
(conj {:session {:config config}}
|
(conj {:session {:config config}}
|
||||||
(web/render [:h1 "Logged out."])))
|
(s/render [:body
|
||||||
(GET "/signup" request
|
[:h1 {:class "title"} "Logged out."]])))
|
||||||
(web/render web/signup-form))
|
|
||||||
|
(GET "/signup" request (s/render-template s/signup))
|
||||||
(POST "/signup" request
|
(POST "/signup" request
|
||||||
(f/attempt-all
|
(s/adduser request
|
||||||
[name (s/param request :name)
|
(fn [name email]
|
||||||
email (s/param request :email)
|
(s/render
|
||||||
password (s/param request :password)
|
[:body
|
||||||
repeat-password (s/param request :repeat-password)
|
(s/notify (str "Account created: "
|
||||||
activation {:activation-uri
|
name " <" email ">") "is-success")
|
||||||
(get-in request [:headers "host"])}]
|
[:h1 {:class "title"} "Check email for activation."]]))))
|
||||||
(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"
|
(GET "/activate/:email/:activation-id"
|
||||||
[email activation-id :as request]
|
[email activation-id :as request]
|
||||||
(let [activation-uri
|
(let [activation-uri
|
||||||
(str "http://"
|
(str "http://"
|
||||||
(get-in request [:headers "host"])
|
(get-in request [:headers "host"])
|
||||||
"/activate/" email "/" activation-id)]
|
"/activate/" email "/" activation-id)]
|
||||||
(web/render
|
(f/attempt-all
|
||||||
[:div
|
[act (auth/activate-account
|
||||||
(f/if-let-failed?
|
@ring/auth email
|
||||||
[act (auth/activate-account
|
{:activation-link activation-uri})]
|
||||||
@ring/auth email
|
(s/render email
|
||||||
{:activation-link activation-uri})]
|
[:body
|
||||||
(web/render-error
|
(s/notify "Account succesfully activated" "is-success")
|
||||||
[:div
|
(views/dashboard email)])
|
||||||
[:h1 "Failure activating account"]
|
(f/when-failed [e]
|
||||||
[:h2 (f/message act)]
|
(s/render
|
||||||
[:p (str "Email: " email " activation-id: " activation-id)]])
|
[:body
|
||||||
[:h1 (str "Account activated - " email)])])))
|
(s/error "Failure activating account" e)])))))
|
||||||
|
|
||||||
;; -- end of JUST-AUTH
|
;; -- end of JUST-AUTH
|
||||||
|
|
||||||
(POST "/" request
|
(POST "/" request
|
||||||
;; generic endpoint for canceled operations
|
;; generic endpoint for canceled operations
|
||||||
(web/render (s/check-account request)
|
(s/render (s/check-account request)
|
||||||
[:div {:class (str "alert alert-danger") :role "alert"}
|
(s/notify
|
||||||
(s/param request :message)]))
|
(s/param request :message) "is-error")))
|
||||||
|
|
||||||
(route/resources "/")
|
(route/resources "/")
|
||||||
(route/not-found (web/render-error-page "Page Not Found"))
|
(route/not-found (s/render [:body (s/notify "Page Not Found" "is-error")]))
|
||||||
|
|
||||||
) ;; end of routes
|
) ;; end of routes
|
||||||
|
|
||||||
(def app
|
(def app
|
||||||
@@ -180,7 +195,7 @@
|
|||||||
(defn -main []
|
(defn -main []
|
||||||
(println "Starting ring server")
|
(println "Starting ring server")
|
||||||
(ring/init ring/app-defaults)
|
(ring/init ring/app-defaults)
|
||||||
;(run-jetty app {:port 6060
|
;(run-jetty app {:port 6060
|
||||||
; :host "localhost"
|
; :host "localhost"
|
||||||
; :join? true})
|
; :join? true})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,24 +1,39 @@
|
|||||||
(ns toaster.jobs
|
(ns toaster.jobs
|
||||||
(:require
|
(:require
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[clj-time.core :as time]
|
[clj-time.core :as time]
|
||||||
[clj-time.coerce :as tc]
|
[clj-time.coerce :as tc]
|
||||||
[clj-storage.db.mongo :refer [create-mongo-store]]
|
[clj-storage.core :as db]
|
||||||
[clj-storage.core :as db]
|
[failjure.core :as f]
|
||||||
[failjure.core :as f]
|
[taoensso.timbre :as log :refer [debug]]
|
||||||
[taoensso.timbre :as log :refer [debug]]
|
[me.raynes.conch :as sh :refer [with-programs]]
|
||||||
[me.raynes.conch :as sh :refer [with-programs]]
|
[toaster.config :refer :all]
|
||||||
[toaster.webpage :as web]
|
[toaster.ring :refer [jobs]]
|
||||||
[toaster.config :refer :all]
|
[toaster.profiles :as profile]
|
||||||
[toaster.ring :refer [jobs]]
|
[hiccup.form :as hf]))
|
||||||
[hiccup.form :as hf]))
|
|
||||||
|
;; list of arm targets to chose from
|
||||||
|
;"beagleboneblack"
|
||||||
|
;"chromeacer"
|
||||||
|
;"chromeveyron"
|
||||||
|
;"droid4"
|
||||||
|
;"n900"
|
||||||
|
;"odroidxu4"
|
||||||
|
;"odroidxu"
|
||||||
|
;"ouya"
|
||||||
|
;"raspi1"
|
||||||
|
;"raspi2"
|
||||||
|
;"raspi3"
|
||||||
|
;"rock64"
|
||||||
|
;"sunxi"
|
||||||
|
;"turbox-twister"
|
||||||
|
|
||||||
(defn- ssh-host [config]
|
(defn- ssh-host [config]
|
||||||
(str (q config [:jenkins :user]) "@" (q config [:jenkins :host])))
|
(str (q config [:jenkins :user]) "@" (q config [:jenkins :host])))
|
||||||
|
|
||||||
|
|
||||||
(defn- sync_jobs [config arg1 arg2]
|
(defn sync_jobs [config arg1 arg2]
|
||||||
(with-programs
|
(with-programs
|
||||||
[ssh]
|
[ssh]
|
||||||
(try
|
(try
|
||||||
@@ -26,46 +41,50 @@
|
|||||||
(ssh-host config) "sync_jobs.py"
|
(ssh-host config) "sync_jobs.py"
|
||||||
arg1 arg2)
|
arg1 arg2)
|
||||||
(str/split #"\n"))
|
(str/split #"\n"))
|
||||||
(catch Exception e (f/fail (str "ERROR in sync_jobs.py - " (.getMessage e)))))))
|
(catch Exception e (f/fail (str "ERROR in sync_jobs.py "
|
||||||
|
arg1 " " arg2 " - " (.getMessage e)))))))
|
||||||
|
|
||||||
(defn- dockerlint [path]
|
(defn- dockerlint [path]
|
||||||
(with-programs [node]
|
(with-programs [node]
|
||||||
(try
|
(try
|
||||||
(node "node_modules/dockerlint/bin/dockerlint.js" path)
|
(node "node_modules/dockerlint/bin/dockerlint.js" path)
|
||||||
(catch Exception e
|
(catch Exception e
|
||||||
(f/fail (str "ERROR in dockerlint - " (.getMessage e)))))))
|
(f/fail (str "ERROR in dockerlint - " (.getMessage e)))))))
|
||||||
|
|
||||||
|
(defn count [id]
|
||||||
|
(f/attempt-all
|
||||||
|
[joblist (db/query @jobs {:email id})]
|
||||||
|
(clojure.core/count joblist)
|
||||||
|
(f/when-failed [e]
|
||||||
|
(f/fail "jobs/count :: " (f/message e)))))
|
||||||
|
|
||||||
(defn add [path config account]
|
(defn add [path config account]
|
||||||
(with-programs [ssh scp node]
|
(with-programs
|
||||||
(let [tstamp (tc/to-long (time/now))
|
[ssh scp node]
|
||||||
jobname (str (:email account) "-vm_amd64-" tstamp)
|
(let [tstamp (tc/to-long (time/now))
|
||||||
jobdir (str "/srv/toaster/" jobname)]
|
jobname (str (:email account) "-vm_amd64_ascii-" tstamp)
|
||||||
(f/attempt-all
|
jobdir (str "/srv/toaster/" jobname)]
|
||||||
[r_lint (dockerlint path)
|
(f/if-let-ok? [joblimit (profile/get-joblimit (:email account))]
|
||||||
r_mkdir (ssh "-i" (q config [:jenkins :key])
|
(if (>= (count (:email account)) joblimit)
|
||||||
(ssh-host config) "mkdir" "-p" jobdir)
|
(f/fail "Job limit is reached, trash some to free slots")
|
||||||
r_scp (scp "-i" (q config [:jenkins :key])
|
(f/attempt-all
|
||||||
path (str (ssh-host config) ":" jobdir))
|
[ ;; r_lint (dockerlint path)
|
||||||
r_job (log/spy (sync_jobs config "-a" jobname))
|
r_mkdir (ssh "-i" (q config [:jenkins :key])
|
||||||
r_store (log/spy (db/store!
|
(ssh-host config) "mkdir" "-p" jobdir)
|
||||||
@jobs :jobid
|
r_scp (scp "-i" (q config [:jenkins :key])
|
||||||
(log/spy {:jobid jobname
|
path (str (ssh-host config) ":" jobdir "/Dockerfile"))
|
||||||
:email (:email account)
|
r_job (sync_jobs config "-a" jobname)
|
||||||
:account (dissoc account :password :activation-link)
|
r_store (db/store! @jobs :jobid
|
||||||
:lint (if (.contains r_lint "is OK") true false)
|
{:jobid jobname
|
||||||
:timestamp tstamp
|
:email (:email account)
|
||||||
:type "vm_amd64"})))]
|
:account (dissoc account :password :activation-link)
|
||||||
{:lint r_lint
|
;; :lint (if (.contains r_lint "is OK") true false)
|
||||||
:job r_job}
|
:timestamp tstamp
|
||||||
(f/when-failed [e]
|
:type "vm_amd64_ascii"
|
||||||
(web/render-error-page
|
:dockerfile (slurp path)})]
|
||||||
(str "Job add failure: " (f/message e))))))))
|
{;; :lint r_lint
|
||||||
|
:job r_job}
|
||||||
|
(f/when-failed [e]
|
||||||
(defn listall [config account]
|
(f/fail (str "Job add '" jobname "' failure :: " (f/message e))))))
|
||||||
(sync_jobs config "-l" (:email account))
|
(f/fail (str "Cannot find profile " (:email account) " :: "
|
||||||
;;(db/query @jobs {:email (:email account)})
|
(f/message joblimit)))))))
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
38
clojure_frontend/src/toaster/profiles.clj
Normal file
38
clojure_frontend/src/toaster/profiles.clj
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
(ns toaster.profiles
|
||||||
|
(:require
|
||||||
|
[clojure.string :as str]
|
||||||
|
[clojure.java.io :as io]
|
||||||
|
[clj-time.core :as time]
|
||||||
|
[clj-time.coerce :as tc]
|
||||||
|
[clj-storage.db.mongo :refer [create-mongo-store]]
|
||||||
|
[clj-storage.core :as db]
|
||||||
|
[failjure.core :as f]
|
||||||
|
[taoensso.timbre :as log :refer [debug]]
|
||||||
|
[me.raynes.conch :as sh :refer [with-programs]]
|
||||||
|
[toaster.config :refer :all]
|
||||||
|
[toaster.ring :refer [jobs profiles]]
|
||||||
|
[toaster.bulma :refer [render-yaml]]
|
||||||
|
[hiccup.form :as hf]))
|
||||||
|
|
||||||
|
(defn create [id]
|
||||||
|
(let [profile {:email id
|
||||||
|
:joblimit 3 ; default
|
||||||
|
:roles ["noob"]
|
||||||
|
:lastlogin (tc/to-long (time/now))}]
|
||||||
|
(db/store! @profiles :email profile)
|
||||||
|
(render-yaml profile)))
|
||||||
|
|
||||||
|
(defn- fetch-profile [id]
|
||||||
|
(f/if-let-ok? [profile (db/fetch @profiles id)]
|
||||||
|
profile
|
||||||
|
(f/fail (str "Profile not found " id " :: " (f/message profile)))))
|
||||||
|
|
||||||
|
(defn get-joblimit [id]
|
||||||
|
;; TODO: quick fix for default joblimit but needs better solution
|
||||||
|
(let [jl (f/ok-> id fetch-profile (get :joblimit))]
|
||||||
|
(if (nil? jl) 3 jl)))
|
||||||
|
|
||||||
|
(defn has-role [id role]
|
||||||
|
(f/ok-> id fetch-profile (get :roles)
|
||||||
|
clojure.core/set (contains? role)))
|
||||||
|
|
||||||
@@ -33,11 +33,14 @@
|
|||||||
[ring.middleware.accept :refer [wrap-accept]]
|
[ring.middleware.accept :refer [wrap-accept]]
|
||||||
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
|
[ring.middleware.defaults :refer [wrap-defaults site-defaults]]))
|
||||||
|
|
||||||
|
;; generic webapp stores
|
||||||
(def config (atom {}))
|
(def config (atom {}))
|
||||||
(def db (atom {}))
|
(def db (atom {}))
|
||||||
(def accts (atom {}))
|
(def accts (atom {}))
|
||||||
(def auth (atom {}))
|
(def auth (atom {}))
|
||||||
(def jobs (atom {}))
|
;; app specific stores
|
||||||
|
(def jobs (atom {}))
|
||||||
|
(def profiles (atom {}))
|
||||||
|
|
||||||
(defn init []
|
(defn init []
|
||||||
(log/merge-config!
|
(log/merge-config!
|
||||||
@@ -86,6 +89,8 @@
|
|||||||
;; ----------------------
|
;; ----------------------
|
||||||
;; initialize jobs stores
|
;; initialize jobs stores
|
||||||
(reset! jobs (create-mongo-store @db :job-store))
|
(reset! jobs (create-mongo-store @db :job-store))
|
||||||
|
;; initialize profile stores
|
||||||
|
(reset! profiles (create-mongo-store @db :profile-store))
|
||||||
|
|
||||||
;; ------------------------------
|
;; ------------------------------
|
||||||
;; log all results worth noticing
|
;; log all results worth noticing
|
||||||
|
|||||||
@@ -1,19 +1,32 @@
|
|||||||
(ns toaster.session
|
(ns toaster.session
|
||||||
(:refer-clojure :exclude [get])
|
(:refer-clojure :exclude [get])
|
||||||
(:require
|
(:require
|
||||||
|
[clojure.java.io :as io]
|
||||||
[toaster.config :as conf]
|
[toaster.config :as conf]
|
||||||
[taoensso.timbre :as log]
|
[taoensso.timbre :as log]
|
||||||
[failjure.core :as f]
|
[failjure.core :as f]
|
||||||
[just-auth.core :as auth]
|
[just-auth.core :as auth]
|
||||||
[toaster.ring :as ring]
|
[hiccup.page :as page :refer [html5]]
|
||||||
[toaster.webpage :as web]))
|
[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]
|
(defn param [request param]
|
||||||
(let [value
|
(let [p (if (coll? param)
|
||||||
(get-in request
|
(into [:params] param)
|
||||||
(conj [:params] param))]
|
(conj [:params] param))
|
||||||
|
value (get-in request p)]
|
||||||
(if (nil? value)
|
(if (nil? value)
|
||||||
(f/fail (str "Parameter not found: " param))
|
(f/fail (str "Parameter not found: " p))
|
||||||
value)))
|
value)))
|
||||||
|
|
||||||
;; TODO: not working?
|
;; TODO: not working?
|
||||||
@@ -29,7 +42,7 @@
|
|||||||
(if (contains? session :config)
|
(if (contains? session :config)
|
||||||
(:config session)
|
(:config session)
|
||||||
(conf/load-config "toaster" conf/default-settings))
|
(conf/load-config "toaster" conf/default-settings))
|
||||||
(f/fail "Session not found. ")))
|
(f/fail "Session not found.")))
|
||||||
|
|
||||||
(defn check-account [request]
|
(defn check-account [request]
|
||||||
;; check if login is present in session
|
;; check if login is present in session
|
||||||
@@ -39,22 +52,122 @@
|
|||||||
user
|
user
|
||||||
(f/when-failed [e]
|
(f/when-failed [e]
|
||||||
(->> e f/message
|
(->> e f/message
|
||||||
(str "Unauthorized access. ")
|
(str "Unauthorized access: ")
|
||||||
f/fail))))
|
f/fail))))
|
||||||
|
|
||||||
(defn check-database []
|
(defn check-database []
|
||||||
(if-let [db @ring/db]
|
(if-let [db @ring/db]
|
||||||
db
|
db
|
||||||
(f/fail "No connection to database. ")))
|
(f/fail "No connection to database.")))
|
||||||
|
|
||||||
(defn check [request fun]
|
|
||||||
|
(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]
|
||||||
|
)))
|
||||||
|
;; shortcut
|
||||||
|
(defn error
|
||||||
|
([msg] (notify msg "is-danger"))
|
||||||
|
([msg fail] (notify (str msg " :: " (f/message fail)) "is-danger")))
|
||||||
|
|
||||||
|
(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-template
|
||||||
|
"render an html from resources using headers, navbar, body and footer"
|
||||||
|
([body] (render-template nil body))
|
||||||
|
([account body]
|
||||||
|
{:headers {"Content-Type"
|
||||||
|
"text/html; charset=utf-8"}
|
||||||
|
:body (str "<!DOCTYPE html>\n<html>"
|
||||||
|
(resource head)
|
||||||
|
"\n<!-- body -->\n"
|
||||||
|
(resource body)
|
||||||
|
"\n<!-- end of body -->\n"
|
||||||
|
(resource footer))}))
|
||||||
|
|
||||||
|
(defn render-html
|
||||||
|
"render an html from resources using headers, navbar, body and footer"
|
||||||
|
([body] (render-template nil body))
|
||||||
|
([account body]
|
||||||
|
{:headers {"Content-Type"
|
||||||
|
"text/html; charset=utf-8"}
|
||||||
|
:body (str "<!DOCTYPE html>\n<html>"
|
||||||
|
(resource head)
|
||||||
|
"\n<!-- body -->\n"
|
||||||
|
body ;; html text string
|
||||||
|
"\n<!-- end of body -->\n"
|
||||||
|
(resource footer))}))
|
||||||
|
|
||||||
|
(defn render-error [err] (->> "is-danger" (notify err) render))
|
||||||
|
|
||||||
|
(defn fail [msg err] (f/fail (str msg " :: " (f/message err))))
|
||||||
|
|
||||||
|
(defn upload
|
||||||
|
"manages the upload of a file and calls a function with its path.
|
||||||
|
(callback) is called with 3 args: path, config and account"
|
||||||
|
[request config account callback]
|
||||||
(f/attempt-all
|
(f/attempt-all
|
||||||
[db (check-database)
|
[tempfile (param request [:file :tempfile])
|
||||||
config (check-config request)
|
filename (param request [:file :filename])
|
||||||
account (check-account request)]
|
filesize (param request [:file :size])]
|
||||||
(fun request config account)
|
(if (> filesize 64000)
|
||||||
(f/when-failed [e]
|
;; TODO: put filesize limit in config
|
||||||
(web/render
|
(f/fail "file too big to upload (64KB limit)")
|
||||||
[:div
|
;; else
|
||||||
(web/render-error (f/message e))
|
(let [file (io/copy tempfile (io/file "/tmp" filename))
|
||||||
web/login-form]))))
|
path (str "/tmp/" filename)]
|
||||||
|
(io/delete-file tempfile)
|
||||||
|
(if (not (.exists (io/file path)))
|
||||||
|
(f/fail (str "uploaded file not found: " filename))
|
||||||
|
;; file is now in 'tmp' var
|
||||||
|
(callback path config account))))
|
||||||
|
(f/when-failed [e]
|
||||||
|
(error "Upload file error" e))))
|
||||||
|
|
||||||
|
(defn adduser
|
||||||
|
"manages the creation of a user (pending activation) and calls a fun callback.
|
||||||
|
(fun) takes 2 args: the name and email of the user."
|
||||||
|
[request fun]
|
||||||
|
(f/attempt-all
|
||||||
|
[name (param request :name)
|
||||||
|
email (param request :email)
|
||||||
|
password (param request :password)
|
||||||
|
repeat-password (param request :repeat-password)
|
||||||
|
activation {:activation-uri
|
||||||
|
(get-in request [:headers "host"])}]
|
||||||
|
|
||||||
|
(if (not= password repeat-password)
|
||||||
|
(f/fail "repeated password did not match")
|
||||||
|
(f/try*
|
||||||
|
(f/attempt-all
|
||||||
|
[signup (auth/sign-up @ring/auth
|
||||||
|
name
|
||||||
|
email
|
||||||
|
password
|
||||||
|
activation
|
||||||
|
[])]
|
||||||
|
(fun name email)
|
||||||
|
(f/when-failed [e]
|
||||||
|
(fail (str "failure creating account '"email"'") e)))))
|
||||||
|
(f/when-failed [e]
|
||||||
|
(render
|
||||||
|
[:body
|
||||||
|
(error "Sign-up failure" e)
|
||||||
|
(resource signup)]))))
|
||||||
|
|||||||
@@ -1,113 +1,117 @@
|
|||||||
|
;; Copyright (C) 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.views
|
(ns toaster.views
|
||||||
(:require
|
(:require
|
||||||
[clojure.java.io :as io]
|
[clojure.string :as str :refer [replace]]
|
||||||
[clojure.string :as str]
|
[clojure.contrib.humanize :as humanize :refer [datetime]]
|
||||||
[clojure.contrib.humanize :as humanize :refer [datetime]]
|
;; [clojure.data.json :as json :refer [read-str]]
|
||||||
;; [clojure.data.json :as json :refer [read-str]]
|
[toaster.bulma :as web :refer [button render-yaml]]
|
||||||
[toaster.webpage :as web]
|
[toaster.session :as s :refer [notify resource param]]
|
||||||
[toaster.session :as s]
|
[toaster.ring :as ring :refer [jobs]]
|
||||||
[toaster.ring :as ring]
|
[toaster.jobs :as job :refer [add sync_jobs]]
|
||||||
[toaster.jobs :as job]
|
[failjure.core :as f :refer [attempt-all when-failed if-let-ok?]]
|
||||||
[failjure.core :as f]
|
[auxiliary.string :refer [strcasecmp]]
|
||||||
[auxiliary.string :refer [strcasecmp]]
|
[toaster.config :refer [q]]
|
||||||
[toaster.config :as conf]
|
[taoensso.timbre :as log]
|
||||||
[taoensso.timbre :as log :refer [debug]]
|
;; [clj-time.core :as time]
|
||||||
[me.raynes.conch :as sh :refer [with-programs]]
|
[clj-time.coerce :as tc]
|
||||||
[clj-time.core :as time]
|
[clj-storage.core :as db]
|
||||||
[clj-time.coerce :as tc]
|
[hiccup.form :as hf]))
|
||||||
[clj-time.local :as tl]
|
|
||||||
[hiccup.form :as hf]))
|
|
||||||
|
|
||||||
(def dockerfile-upload-form
|
;; TODO: templated
|
||||||
[:div {:class "container-fluid"}
|
(defn- box-list [account joblist]
|
||||||
[:h1 "Upload a Dockerfile to toast"]
|
[:div {:class "box"}
|
||||||
[:p " Choose the file in your computer and click 'Submit' to
|
[:h1 {:class "title"} (str "List all toaster jobs for " (:name account))]
|
||||||
proceed to validation."]
|
[:table {:class "table is-fullwidth is-hoverable"}
|
||||||
[:div {:class "form-group"}
|
[:thead nil
|
||||||
[:form {:action "dockerfile" :method "post"
|
[:tr nil [:th nil "Date"] [:th nil "Type"] [:th nil "Status"] [:th nil "Actions"]]]
|
||||||
:class "form-shell"
|
[:tbody nil
|
||||||
:enctype "multipart/form-data"}
|
(for [j joblist]
|
||||||
[:fieldset {:class "fieldset btn btn-default btn-file btn-lg"}
|
(let [type (:type j) ;; (-> j (str/split #"-") second)
|
||||||
[:input {:name "file" :type "file"}]]
|
tstamp (:timestamp j) ;; (-> j (str/split #"-") last)
|
||||||
;; [:fieldset {:class "fieldset-submit"}
|
jobid (:jobid j)
|
||||||
[:input {:class "btn btn-primary btn-lg"
|
joburl (str/replace jobid #"@" "AT")]
|
||||||
:id "field-submit" :type "submit"
|
[:tr nil
|
||||||
:name "submit" :value "submit"}]]]])
|
[:td {:class "date"} (-> tstamp Long/valueOf tc/from-long humanize/datetime)]
|
||||||
|
[:td {:class "job"} [:a {:href (str "https://sdk.dyne.org:4443/view/web-sdk-builds/job/" joburl)} type]]
|
||||||
|
[:td {:class "status"} [:a {:href (str "https://sdk.dyne.org:4443/job/" joburl "/lastBuild/console")}
|
||||||
|
[:img {:src (str "https://sdk.dyne.org:4443/job/" joburl "/badge/icon")}]]]
|
||||||
|
[:td {:class "actions"}
|
||||||
|
[:div {:class "field is-grouped"}
|
||||||
|
(web/button "/view" "\uD83D\uDC41" (hf/hidden-field "jobid" jobid))
|
||||||
|
(web/button "/start" "▶" (hf/hidden-field "jobid" jobid))
|
||||||
|
(web/button "/remove" "\uD83D\uDDD1" (hf/hidden-field "jobid" jobid))]]]
|
||||||
|
))]]])
|
||||||
|
|
||||||
(def dockerfile-edit-form
|
(defn add-job [path config account]
|
||||||
[:div {:class "container-fluid"}
|
|
||||||
[:h1 "Edit your Dockerfile to toast"]
|
|
||||||
[:div {:class "form-group"}
|
|
||||||
[:form {:action "dockerfile" :method "post"
|
|
||||||
:class "form-shell"
|
|
||||||
:enctype "multipart/form-data"}
|
|
||||||
[:fieldset {:class "fieldset btn btn-default btn-file btn-lg"}
|
|
||||||
[:textarea {:name "editor" :id "editor"
|
|
||||||
:rows 30 :cols 72 } "FROM: dyne/devuan:ascii"]
|
|
||||||
[:input {:class "btn btn-primary btn-lg"
|
|
||||||
:id "field-submit" :type "submit"
|
|
||||||
:name "submit" :value "submit"}]]]]
|
|
||||||
[:script "var editor = ace.edit(\"editor\");
|
|
||||||
editor.setTheme(\"ace/theme/monokai\");
|
|
||||||
editor.session.setMode(\"ace/mode/dockerfile\");"]])
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(def welcome-menu
|
|
||||||
[:div {:class "container-fluid"}
|
|
||||||
[:div {:class "row-fluid"}
|
|
||||||
[:div {:class "row"} [:a {:href "/list"} "List all your toaster jobs"]]
|
|
||||||
[:div {:class "row"} [:a {:href "/upload"} "Upload a new toaster job"]]]])
|
|
||||||
|
|
||||||
(defn dockerfile-upload-post [request config account]
|
|
||||||
(web/render
|
|
||||||
account
|
|
||||||
(let
|
|
||||||
[tempfile (get-in request [:params :file :tempfile])
|
|
||||||
filename (get-in request [:params :file :filename])
|
|
||||||
params (:params request)]
|
|
||||||
(cond
|
|
||||||
(> (get-in params [:file :size]) 64000)
|
|
||||||
;; max upload size in bytes
|
|
||||||
;; TODO: put in config
|
|
||||||
(web/render-error-page params "File too big in upload (64KB limit).")
|
|
||||||
:else
|
|
||||||
(let [file (io/copy tempfile (io/file "/tmp" filename))
|
|
||||||
path (str "/tmp/" filename)]
|
|
||||||
(io/delete-file tempfile)
|
|
||||||
(if (not (.exists (io/file path)))
|
|
||||||
(web/render-error-page
|
|
||||||
(log/spy :error
|
|
||||||
[:h1 (str "Uploaded file not found: " filename)]))
|
|
||||||
;; file is now in 'tmp' var
|
|
||||||
[:div {:class "container-fluid"}
|
|
||||||
[:h1 "Job uploaded and added"]
|
|
||||||
[:p "Log messages:"]
|
|
||||||
(web/render-yaml (job/add path config account))]))))))
|
|
||||||
|
|
||||||
(defn list-jobs [request config account]
|
|
||||||
(f/attempt-all
|
(f/attempt-all
|
||||||
[joblist (job/listall config account)]
|
[newjob (job/add path config account)]
|
||||||
(web/render
|
(s/notify "New toaster job succesfully added" "is-success")
|
||||||
account
|
;; else when job/add is not-ok
|
||||||
[:div {:class "container-fluid"}
|
(f/when-failed [e]
|
||||||
[:h1 (str "List all toaster jobs for " (:name account))]
|
(s/error "Error adding job" e))))
|
||||||
[:table {:class "sortable table"}
|
|
||||||
[:thead nil
|
(defn dashboard
|
||||||
[:tr nil [:th nil "Date"] [:th nil "Type"] [:th nil "Actions"]]]
|
([account] (dashboard {} {} account))
|
||||||
[:tbody nil
|
([request config account]
|
||||||
(for [j joblist]
|
(f/attempt-all
|
||||||
(let [type (-> j (str/split #"-") second)
|
[joblist (db/query @ring/jobs {:email (:email account)})]
|
||||||
tstamp (-> j (str/split #"-") last)]
|
[:div {:class "container has-text-centered"}
|
||||||
[:tr nil
|
|
||||||
[:td {:class "date"} (-> tstamp Long/valueOf tc/from-long tl/to-local-date-time
|
[:span
|
||||||
humanize/datetime)]
|
;(if (> 0 (count joblist))
|
||||||
[:td {:class "job"} [:a {:href (str "https://sdk.dyne.org:4443/view/web-sdk-builds/job/"
|
(box-list account joblist)
|
||||||
(str/replace j #"@" "AT"))} type]]
|
;)
|
||||||
[:td {:class "start-job"} (web/button "/start" "Start" (hf/hidden-field "job" j))
|
(s/resource "templates/body_addjob.html") ]]
|
||||||
(web/button "/remove" "Remove" (hf/hidden-field "job" j))]]
|
|
||||||
))]]])
|
|
||||||
(f/when-failed [e]
|
(f/when-failed [e]
|
||||||
(web/render-error-page
|
(s/error "Job list failure" e)))))
|
||||||
(str "Job list failure: " (f/message e))))
|
|
||||||
))
|
(defn remove-job [request config account]
|
||||||
|
(f/attempt-all
|
||||||
|
[jobid (s/param request :jobid)
|
||||||
|
jobfound (db/query @ring/jobs {:jobid jobid})
|
||||||
|
r_rmjob (db/delete! @ring/jobs jobid)
|
||||||
|
r_sync (job/sync_jobs config "-d" jobid)
|
||||||
|
;; TODO: rm -rf the jobdir created via ssh in jobs/add
|
||||||
|
]
|
||||||
|
(s/notify (str "Job removed :: " jobid) "is-primary")
|
||||||
|
(f/when-failed [e]
|
||||||
|
(s/error "Failure removing job" e))))
|
||||||
|
|
||||||
|
(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)]
|
||||||
|
(s/notify (str "Job started: " jobid) "is-success")
|
||||||
|
(f/when-failed [e]
|
||||||
|
(s/error "Failure starting job" e))))
|
||||||
|
|
||||||
|
(defn view-job [request config account]
|
||||||
|
(f/attempt-all
|
||||||
|
[jobid (s/param request :jobid)
|
||||||
|
jobfound (db/fetch @ring/jobs jobid)
|
||||||
|
dockerfile (-> jobfound :dockerfile)]
|
||||||
|
[:div {:class "box"}
|
||||||
|
[:h1 {:class "title"} (str "Viewing job: " jobid)]
|
||||||
|
[:form [:textarea {:id "code" :name "code" } dockerfile]]
|
||||||
|
[:script "var editor = CodeMirror.fromTextArea(document.getElementById(\"code\"),
|
||||||
|
{ lineNumbers: true, mode: \"dockerfile\" });"]]
|
||||||
|
(f/when-failed [e]
|
||||||
|
(s/error "Failure viewing job" e))))
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
(declare render-error)
|
(declare render-error)
|
||||||
(declare render-error-page)
|
(declare render-error-page)
|
||||||
(declare render-static)
|
(declare render-static)
|
||||||
|
(declare render-error)
|
||||||
|
|
||||||
(defn q [req]
|
(defn q [req]
|
||||||
"wrapper to retrieve parameters"
|
"wrapper to retrieve parameters"
|
||||||
@@ -98,6 +99,14 @@
|
|||||||
[:div {:class "container-fluid"} body]
|
[:div {:class "container-fluid"} body]
|
||||||
(render-footer)])}))
|
(render-footer)])}))
|
||||||
|
|
||||||
|
(defn render-success
|
||||||
|
"render a successful message without ending the page"
|
||||||
|
[succ]
|
||||||
|
[:div {:class "alert alert-success" :role "alert"}
|
||||||
|
[:span {:class "far fa-check-circle"
|
||||||
|
:aria-hidden "true" :style "padding: .5em"}]
|
||||||
|
[:span {:class "sr-only"} "Success:" ]
|
||||||
|
succ])
|
||||||
|
|
||||||
(defn render-error
|
(defn render-error
|
||||||
"render an error message without ending the page"
|
"render an error message without ending the page"
|
||||||
@@ -293,7 +302,7 @@
|
|||||||
;; (defonce readme
|
;; (defonce readme
|
||||||
;; (slurp (io/resource "public/static/README.html")))
|
;; (slurp (io/resource "public/static/README.html")))
|
||||||
|
|
||||||
(defonce login-form
|
(def login-form
|
||||||
[:div
|
[:div
|
||||||
[:h1 "Login for your account"
|
[:h1 "Login for your account"
|
||||||
[:form {:action "/login"
|
[:form {:action "/login"
|
||||||
@@ -310,7 +319,7 @@
|
|||||||
:class "btn btn-primary btn-lg btn-block"
|
:class "btn btn-primary btn-lg btn-block"
|
||||||
:style "margin-top: 1em"}]]]])
|
:style "margin-top: 1em"}]]]])
|
||||||
|
|
||||||
(defonce signup-form
|
(def signup-form
|
||||||
[:div
|
[:div
|
||||||
[:h1 "Sign Up for a toaster account"
|
[:h1 "Sign Up for a toaster account"
|
||||||
[:form {:action "/signup"
|
[:form {:action "/signup"
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
webserver:
|
webserver:
|
||||||
anti-forgery: false
|
anti-forgery: false
|
||||||
ssl-redirect: false
|
ssl-redirect: false
|
||||||
|
mock-auth: false
|
||||||
|
|
||||||
jenkins:
|
jenkins:
|
||||||
host: "bridge.toaster"
|
host: "bridge.toaster"
|
||||||
user: "jenkins"
|
user: "jenkins"
|
||||||
key: "../id_ed25519"
|
key: "../id_ed25519"
|
||||||
|
url: "https://sdk.dyne.org:4443"
|
||||||
|
|
||||||
just-auth:
|
just-auth:
|
||||||
email-server: "mail.dyne.org"
|
email-server: "mail.dyne.org"
|
||||||
|
|||||||
@@ -43,26 +43,26 @@ optional arguments:
|
|||||||
|
|
||||||
The `jobname` argument should be in a specific format. It should contain
|
The `jobname` argument should be in a specific format. It should contain
|
||||||
the requester's email, which sdk was chosen, the requested architecture,
|
the requester's email, which sdk was chosen, the requested architecture,
|
||||||
and a timestamp.
|
codename, and a timestamp.
|
||||||
|
|
||||||
In case of vm-sdk or live-sdk, these would look like:
|
In case of vm-sdk or live-sdk, these would look like:
|
||||||
|
|
||||||
```
|
```
|
||||||
parazyd@dyne.org-vm_amd64-1537977964
|
parazyd@dyne.org-vm_amd64_ascii-1537977964
|
||||||
parazyd@dyne.org-live_amd64-1537977964
|
parazyd@dyne.org-live_amd64_beowulf-1537977964
|
||||||
```
|
```
|
||||||
|
|
||||||
In case of arm-sdk, we also need to know the board we're building for:
|
In case of arm-sdk, we also need to know the board we're building for:
|
||||||
|
|
||||||
```
|
```
|
||||||
parazyd@dyne.org-arm_armhf_sunxi-1537977964
|
parazyd@dyne.org-arm_armhf_ascii_sunxi-1537977964
|
||||||
```
|
```
|
||||||
|
|
||||||
All of this combined, the required command to add a new job to Jenkins
|
All of this combined, the required command to add a new job to Jenkins
|
||||||
would look something like the following:
|
would look something like the following:
|
||||||
|
|
||||||
```
|
```
|
||||||
sync_jobs.py -a parazyd@dyne.org-vm_amd64-1537977964
|
sync_jobs.py -a parazyd@dyne.org-vm_amd64_ascii-1537977964
|
||||||
```
|
```
|
||||||
|
|
||||||
In case of removing or building an existing job, all of the above applies the
|
In case of removing or building an existing job, all of the above applies the
|
||||||
|
|||||||
@@ -20,23 +20,38 @@ def add_job(jobname):
|
|||||||
desc = 'WebSDK build for: %s\nStarted: %s' % (info[0], info[2])
|
desc = 'WebSDK build for: %s\nStarted: %s' % (info[0], info[2])
|
||||||
sdk = info[1].split('_')[0]
|
sdk = info[1].split('_')[0]
|
||||||
arch = info[1].split('_')[1]
|
arch = info[1].split('_')[1]
|
||||||
|
codename = info[1].split('_')[2]
|
||||||
blenddir = join(jobpath, jobname)
|
blenddir = join(jobpath, jobname)
|
||||||
blendfile = join(blenddir, 'Dockerfile')
|
blendfile = join(blenddir, 'Dockerfile')
|
||||||
|
|
||||||
if sdk == 'arm':
|
if codename == 'ascii':
|
||||||
board = info[1].split('_')[2]
|
relvars = 'release=ascii && version=2.0.0'
|
||||||
zshcmd = 'load devuan %s %s' % (board, blendfile)
|
elif codename == 'beowulf':
|
||||||
elif sdk == 'live':
|
relvars = 'release=beowulf && version=3.0.0'
|
||||||
zshcmd = 'load devuan %s %s' % (arch, blendfile)
|
else:
|
||||||
elif sdk == 'vm':
|
# Default to Ascii
|
||||||
zshcmd = 'load devuan %s' % (blendfile)
|
relvars = 'release=ascii && version=2.0.0'
|
||||||
|
|
||||||
command = "zsh -f -c 'source sdk && %s && build_image_dist'" % zshcmd
|
if sdk == 'arm':
|
||||||
|
board = info[1].split('_')[3]
|
||||||
|
zshcmd = '\
|
||||||
|
load devuan %s %s && %s && build_image_dist' % (board, blendfile, relvars)
|
||||||
|
|
||||||
|
elif sdk == 'live':
|
||||||
|
zshcmd = '\
|
||||||
|
load devuan %s %s && %s && build_iso_dist' % (arch, blendfile, relvars)
|
||||||
|
|
||||||
|
elif sdk == 'vm':
|
||||||
|
zshcmd = '\
|
||||||
|
load devuan %s && %s && build_vagrant_dist' % (blendfile, relvars)
|
||||||
|
|
||||||
|
command = "zsh -f -c 'source sdk && %s'" % zshcmd
|
||||||
command = html.escape(command)
|
command = html.escape(command)
|
||||||
|
|
||||||
replacements = [('DESC', desc),
|
replacements = [('DESC', desc),
|
||||||
('SDK', sdk),
|
('SDK', sdk),
|
||||||
('ARCH', arch),
|
('ARCH', arch),
|
||||||
|
('CODENAME', codename),
|
||||||
('COMMAND', command),
|
('COMMAND', command),
|
||||||
('BLENDDIR', blenddir)]
|
('BLENDDIR', blenddir)]
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,16 @@
|
|||||||
<builders>
|
<builders>
|
||||||
<hudson.tasks.Shell>
|
<hudson.tasks.Shell>
|
||||||
<command>
|
<command>
|
||||||
scp -r sdk:{{{BLENDDIR}}} {{{BLENDDIR}}}
|
scp -r sdk:{{{BLENDDIR}}} {{{BLENDDIR}}} || exit 1
|
||||||
git submodule update --init --recursive --checkout
|
git submodule update --init --recursive --checkout || exit 1
|
||||||
{{{COMMAND}}}
|
|
||||||
|
mkdir -p tmp
|
||||||
|
cd tmp
|
||||||
|
wget https://sdk.dyne.org:4443/job/devuan-{{{CODENAME}}}-{{{ARCH}}}-stage3/lastSuccessfulBuild/artifact/tmp/bootstrap-devuan-{{{ARCH}}}-stage3.tgz
|
||||||
|
cd -
|
||||||
|
|
||||||
|
{{{COMMAND}}} || exit 1
|
||||||
|
rm -rf {{{BLENDDIR}}}
|
||||||
</command>
|
</command>
|
||||||
</hudson.tasks.Shell>
|
</hudson.tasks.Shell>
|
||||||
</builders>
|
</builders>
|
||||||
|
|||||||
Reference in New Issue
Block a user