From 0e8a14df1bb33b33267dedc3c81bf34afdaaa5fa Mon Sep 17 00:00:00 2001 From: Julien WITTOUCK Date: Fri, 24 May 2019 17:03:43 +0200 Subject: [PATCH] :sparkles: : add stack mgmt --- pom.xml | 23 ++ src/main/java/io/codeka/gaia/Gaia.java | 25 +- src/main/java/io/codeka/gaia/bo/Stack.java | 80 +++++ .../io/codeka/gaia/bo/TerraformBackend.java | 20 ++ .../io/codeka/gaia/bo/backend/Backend.java | 16 + .../java/io/codeka/gaia/bo/backend/Http.java | 13 + .../io/codeka/gaia/bo/backend/Terraform.java | 16 + .../codeka/gaia/config/RepositoryConfig.java | 16 + .../gaia/controller/StackController.java | 45 +++ .../gaia/repository/StackRepository.java | 12 + .../gaia/service/RunTerraformService.java | 16 + src/main/resources/static/css/prism.css | 287 ++++++++++++++++++ src/main/resources/static/js/prism.js | 5 + src/main/resources/templates/empty_page.tpl | 72 +++++ .../resources/templates/layout/header.html | 2 + .../resources/templates/layout/sidebar.html | 2 +- src/main/resources/templates/stack.html | 217 +++++++++++++ src/main/resources/templates/stacks.html | 65 ++++ 18 files changed, 930 insertions(+), 2 deletions(-) create mode 100644 src/main/java/io/codeka/gaia/bo/Stack.java create mode 100644 src/main/java/io/codeka/gaia/bo/TerraformBackend.java create mode 100644 src/main/java/io/codeka/gaia/bo/backend/Backend.java create mode 100644 src/main/java/io/codeka/gaia/bo/backend/Http.java create mode 100644 src/main/java/io/codeka/gaia/bo/backend/Terraform.java create mode 100644 src/main/java/io/codeka/gaia/config/RepositoryConfig.java create mode 100644 src/main/java/io/codeka/gaia/controller/StackController.java create mode 100644 src/main/java/io/codeka/gaia/repository/StackRepository.java create mode 100644 src/main/java/io/codeka/gaia/service/RunTerraformService.java create mode 100644 src/main/resources/static/css/prism.css create mode 100644 src/main/resources/static/js/prism.js create mode 100644 src/main/resources/templates/empty_page.tpl create mode 100644 src/main/resources/templates/stack.html create mode 100644 src/main/resources/templates/stacks.html diff --git a/pom.xml b/pom.xml index fdd5c989a..5a82a0c62 100644 --- a/pom.xml +++ b/pom.xml @@ -64,11 +64,34 @@ font-awesome 5.8.2 + + org.webjars.npm + ace-builds + 1.4.4 + org.springframework.boot spring-boot-devtools + + + com.spotify + docker-client + 8.16.0 + + + org.glassfish.jersey.inject + jersey-hk2 + 2.27 + + + + org.apache.commons + commons-compress + 1.18 + + diff --git a/src/main/java/io/codeka/gaia/Gaia.java b/src/main/java/io/codeka/gaia/Gaia.java index 90fea7e28..9ec450dfc 100644 --- a/src/main/java/io/codeka/gaia/Gaia.java +++ b/src/main/java/io/codeka/gaia/Gaia.java @@ -1,7 +1,9 @@ package io.codeka.gaia; +import io.codeka.gaia.bo.Stack; import io.codeka.gaia.bo.TerraformModule; import io.codeka.gaia.bo.TerraformVariable; +import io.codeka.gaia.repository.StackRepository; import io.codeka.gaia.repository.TerraformModuleRepository; import io.codeka.gaia.repository.TerraformStateRepository; import org.springframework.beans.factory.annotation.Autowired; @@ -12,6 +14,7 @@ import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; import java.util.List; +import java.util.UUID; @SpringBootApplication @EnableMongoRepositories @@ -23,10 +26,13 @@ public static void main(String[] args) { @Bean @Autowired - CommandLineRunner cli(TerraformModuleRepository repository, TerraformStateRepository terraformStateRepository){ + CommandLineRunner cli(TerraformModuleRepository repository, + TerraformStateRepository terraformStateRepository, + StackRepository stackRepository){ return args -> { repository.deleteAll(); terraformStateRepository.deleteAll(); + stackRepository.deleteAll(); // create dummy module for tests var module = new TerraformModule(); @@ -49,6 +55,23 @@ CommandLineRunner cli(TerraformModuleRepository repository, TerraformStateReposi module.setVariables(List.of(tvar, tvar2)); repository.saveAll(List.of(module)); + + + var stack = new Stack(); + stack.setId("5a215b6b-fe53-4afa-85f0-a10175a7f264"); + stack.setName("mongo-instance-1"); + stack.setModuleId("e01f9925-a559-45a2-8a55-f93dc434c676"); + stack.getVariableValues().put("mongo_exposed_port", "27117"); + stack.setProviderSpec("provider \"poulp\" {\n" + + " host = \"unix:///var/run/docker.sock\"\n" + + "}"); + stackRepository.save(stack); + + var stack2 = new Stack(); + stack2.setId("143773fa-4c2e-4baf-a7fb-79d23e01c5ca"); + stack2.setName("mongo-instance-2"); + stack2.setModuleId("e01f9925-a559-45a2-8a55-f93dc434c676"); + stackRepository.save(stack2); }; } diff --git a/src/main/java/io/codeka/gaia/bo/Stack.java b/src/main/java/io/codeka/gaia/bo/Stack.java new file mode 100644 index 000000000..40b386d11 --- /dev/null +++ b/src/main/java/io/codeka/gaia/bo/Stack.java @@ -0,0 +1,80 @@ +package io.codeka.gaia.bo; + +import io.codeka.gaia.bo.backend.Backend; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class represents a terraform modules instances. + * + * It references the module it is based on, with all the values of its variables. + * It also has a backend configuration, and a provider configuration (in terraform terms). + */ +public class Stack { + + /** + * This stack's id + */ + private String id; + + /** + * The id of the referenced module + */ + private String moduleId; + + /** + * The variable values of the module + */ + private Map variableValues = new HashMap<>(); + + /** + * The name of the stack + */ + private String name; + + /** + * The provider spec + */ + private String providerSpec; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getModuleId() { + return moduleId; + } + + public void setModuleId(String moduleId) { + this.moduleId = moduleId; + } + + public Map getVariableValues() { + return variableValues; + } + + public void setVariableValues(Map variableValues) { + this.variableValues = variableValues; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public String getProviderSpec() { + return providerSpec; + } + + public void setProviderSpec(String providerSpec) { + this.providerSpec = providerSpec; + } +} diff --git a/src/main/java/io/codeka/gaia/bo/TerraformBackend.java b/src/main/java/io/codeka/gaia/bo/TerraformBackend.java new file mode 100644 index 000000000..20d7228a8 --- /dev/null +++ b/src/main/java/io/codeka/gaia/bo/TerraformBackend.java @@ -0,0 +1,20 @@ +package io.codeka.gaia.bo; + +import io.codeka.gaia.bo.backend.Terraform; + +public class TerraformBackend { + + private Terraform terraform; + + public Terraform getTerraform() { + if(terraform == null){ + terraform = new Terraform(); + } + return terraform; + } + + public void setTerraform(Terraform terraform) { + this.terraform = terraform; + } + +} diff --git a/src/main/java/io/codeka/gaia/bo/backend/Backend.java b/src/main/java/io/codeka/gaia/bo/backend/Backend.java new file mode 100644 index 000000000..367e9c6dd --- /dev/null +++ b/src/main/java/io/codeka/gaia/bo/backend/Backend.java @@ -0,0 +1,16 @@ +package io.codeka.gaia.bo.backend; + +public class Backend { + private Http http; + + public Http getHttp() { + if(http == null){ + http = new Http(); + } + return http; + } + + public void setHttp(Http http) { + this.http = http; + } +} diff --git a/src/main/java/io/codeka/gaia/bo/backend/Http.java b/src/main/java/io/codeka/gaia/bo/backend/Http.java new file mode 100644 index 000000000..e705595cb --- /dev/null +++ b/src/main/java/io/codeka/gaia/bo/backend/Http.java @@ -0,0 +1,13 @@ +package io.codeka.gaia.bo.backend; + +public class Http { + private String address; + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } +} diff --git a/src/main/java/io/codeka/gaia/bo/backend/Terraform.java b/src/main/java/io/codeka/gaia/bo/backend/Terraform.java new file mode 100644 index 000000000..0c66c4271 --- /dev/null +++ b/src/main/java/io/codeka/gaia/bo/backend/Terraform.java @@ -0,0 +1,16 @@ +package io.codeka.gaia.bo.backend; + +public class Terraform { + private Backend backend; + + public Backend getBackend() { + if(backend == null){ + backend = new Backend(); + } + return backend; + } + + public void setBackend(Backend backend) { + this.backend = backend; + } +} diff --git a/src/main/java/io/codeka/gaia/config/RepositoryConfig.java b/src/main/java/io/codeka/gaia/config/RepositoryConfig.java new file mode 100644 index 000000000..3ebaddc4e --- /dev/null +++ b/src/main/java/io/codeka/gaia/config/RepositoryConfig.java @@ -0,0 +1,16 @@ +package io.codeka.gaia.config; + +import io.codeka.gaia.bo.Stack; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.rest.core.config.RepositoryRestConfiguration; + +@Configuration +public class RepositoryConfig { + + @Autowired + public void configureRest(RepositoryRestConfiguration repositoryRestConfiguration){ + repositoryRestConfiguration.exposeIdsFor(Stack.class); + + } +} diff --git a/src/main/java/io/codeka/gaia/controller/StackController.java b/src/main/java/io/codeka/gaia/controller/StackController.java new file mode 100644 index 000000000..d7871541d --- /dev/null +++ b/src/main/java/io/codeka/gaia/controller/StackController.java @@ -0,0 +1,45 @@ +package io.codeka.gaia.controller; + +import io.codeka.gaia.repository.StackRepository; +import io.codeka.gaia.repository.TerraformModuleRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + + +@Controller +public class StackController { + + private TerraformModuleRepository terraformModuleRepository; + + private StackRepository stackRepository; + + private Logger log = LoggerFactory.getLogger(StackController.class); + + @Autowired + public StackController(TerraformModuleRepository terraformModuleRepository, StackRepository stackRepository) { + this.terraformModuleRepository = terraformModuleRepository; + this.stackRepository = stackRepository; + } + + @GetMapping("/stacks") + public String listStacks(Model model){ + model.addAttribute("stacks", stackRepository.findAll()); + + return "stacks"; + } + + @GetMapping("/stacks/{stackId}") + public String editStack(@PathVariable String stackId, Model model){ + // checking if the stack exists + // TODO throw an exception (404) if not + if(stackRepository.existsById(stackId)){ + model.addAttribute("stackId", stackId); + } + return "stack"; + } + +} \ No newline at end of file diff --git a/src/main/java/io/codeka/gaia/repository/StackRepository.java b/src/main/java/io/codeka/gaia/repository/StackRepository.java new file mode 100644 index 000000000..4bcb2a45a --- /dev/null +++ b/src/main/java/io/codeka/gaia/repository/StackRepository.java @@ -0,0 +1,12 @@ +package io.codeka.gaia.repository; + +import io.codeka.gaia.bo.Stack; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +/** + * Repository for stacks + */ +@RepositoryRestResource +public interface StackRepository extends MongoRepository{ +} diff --git a/src/main/java/io/codeka/gaia/service/RunTerraformService.java b/src/main/java/io/codeka/gaia/service/RunTerraformService.java new file mode 100644 index 000000000..d91c9d1dc --- /dev/null +++ b/src/main/java/io/codeka/gaia/service/RunTerraformService.java @@ -0,0 +1,16 @@ +package io.codeka.gaia.service; + +import com.spotify.docker.client.DefaultDockerClient; +import com.spotify.docker.client.DockerClient; +import com.spotify.docker.client.exceptions.DockerCertificateException; +import com.spotify.docker.client.exceptions.DockerException; +import org.springframework.stereotype.Service; + +@Service +public class RunTerraformService { + + + public void runTerraform() throws DockerException, InterruptedException, DockerCertificateException { + + } +} diff --git a/src/main/resources/static/css/prism.css b/src/main/resources/static/css/prism.css new file mode 100644 index 000000000..72ab02c49 --- /dev/null +++ b/src/main/resources/static/css/prism.css @@ -0,0 +1,287 @@ +/* PrismJS 1.16.0 +https://prismjs.com/download.html#themes=prism-coy&languages=hcl+json&plugins=toolbar+copy-to-clipboard */ +/** + * prism.js Coy theme for JavaScript, CoffeeScript, CSS and HTML + * Based on https://github.com/tshedor/workshop-wp-theme (Example: http://workshop.kansan.com/category/sessions/basics or http://workshop.timshedor.com/category/sessions/basics); + * @author Tim Shedor + */ + +code[class*="language-"], +pre[class*="language-"] { + color: black; + background: none; + font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} + +/* Code blocks */ +pre[class*="language-"] { + position: relative; + margin: .5em 0; + overflow: visible; + padding: 0; +} +pre[class*="language-"]>code { + position: relative; + border-left: 10px solid #358ccb; + box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf; + background-color: #fdfdfd; + background-image: linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%); + background-size: 3em 3em; + background-origin: content-box; + background-attachment: local; +} + +code[class*="language"] { + max-height: inherit; + height: inherit; + padding: 0 1em; + display: block; + overflow: auto; +} + +/* Margin bottom to accommodate shadow */ +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background-color: #fdfdfd; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + margin-bottom: 1em; +} + +/* Inline code */ +:not(pre) > code[class*="language-"] { + position: relative; + padding: .2em; + border-radius: 0.3em; + color: #c92c2c; + border: 1px solid rgba(0, 0, 0, 0.1); + display: inline; + white-space: normal; +} + +pre[class*="language-"]:before, +pre[class*="language-"]:after { + content: ''; + z-index: -2; + display: block; + position: absolute; + bottom: 0.75em; + left: 0.18em; + width: 40%; + height: 20%; + max-height: 13em; + box-shadow: 0px 13px 8px #979797; + -webkit-transform: rotate(-2deg); + -moz-transform: rotate(-2deg); + -ms-transform: rotate(-2deg); + -o-transform: rotate(-2deg); + transform: rotate(-2deg); +} + +:not(pre) > code[class*="language-"]:after, +pre[class*="language-"]:after { + right: 0.75em; + left: auto; + -webkit-transform: rotate(2deg); + -moz-transform: rotate(2deg); + -ms-transform: rotate(2deg); + -o-transform: rotate(2deg); + transform: rotate(2deg); +} + +.token.comment, +.token.block-comment, +.token.prolog, +.token.doctype, +.token.cdata { + color: #7D8B99; +} + +.token.punctuation { + color: #5F6364; +} + +.token.property, +.token.tag, +.token.boolean, +.token.number, +.token.function-name, +.token.constant, +.token.symbol, +.token.deleted { + color: #c92c2c; +} + +.token.selector, +.token.attr-name, +.token.string, +.token.char, +.token.function, +.token.builtin, +.token.inserted { + color: #2f9c0a; +} + +.token.operator, +.token.entity, +.token.url, +.token.variable { + color: #a67f59; + background: rgba(255, 255, 255, 0.5); +} + +.token.atrule, +.token.attr-value, +.token.keyword, +.token.class-name { + color: #1990b8; +} + +.token.regex, +.token.important { + color: #e90; +} + +.language-css .token.string, +.style .token.string { + color: #a67f59; + background: rgba(255, 255, 255, 0.5); +} + +.token.important { + font-weight: normal; +} + +.token.bold { + font-weight: bold; +} +.token.italic { + font-style: italic; +} + +.token.entity { + cursor: help; +} + +.namespace { + opacity: .7; +} + +@media screen and (max-width: 767px) { + pre[class*="language-"]:before, + pre[class*="language-"]:after { + bottom: 14px; + box-shadow: none; + } + +} + +/* Plugin styles */ +.token.tab:not(:empty):before, +.token.cr:before, +.token.lf:before { + color: #e0d7d1; +} + +/* Plugin styles: Line Numbers */ +pre[class*="language-"].line-numbers.line-numbers { + padding-left: 0; +} + +pre[class*="language-"].line-numbers.line-numbers code { + padding-left: 3.8em; +} + +pre[class*="language-"].line-numbers.line-numbers .line-numbers-rows { + left: 0; +} + +/* Plugin styles: Line Highlight */ +pre[class*="language-"][data-line] { + padding-top: 0; + padding-bottom: 0; + padding-left: 0; +} +pre[data-line] code { + position: relative; + padding-left: 4em; +} +pre .line-highlight { + margin-top: 0; +} + +div.code-toolbar { + position: relative; +} + +div.code-toolbar > .toolbar { + position: absolute; + top: .3em; + right: .2em; + transition: opacity 0.3s ease-in-out; + opacity: 0; +} + +div.code-toolbar:hover > .toolbar { + opacity: 1; +} + +div.code-toolbar > .toolbar .toolbar-item { + display: inline-block; +} + +div.code-toolbar > .toolbar a { + cursor: pointer; +} + +div.code-toolbar > .toolbar button { + background: none; + border: 0; + color: inherit; + font: inherit; + line-height: normal; + overflow: visible; + padding: 0; + -webkit-user-select: none; /* for button */ + -moz-user-select: none; + -ms-user-select: none; +} + +div.code-toolbar > .toolbar a, +div.code-toolbar > .toolbar button, +div.code-toolbar > .toolbar span { + color: #bbb; + font-size: .8em; + padding: 0 .5em; + background: #f5f2f0; + background: rgba(224, 224, 224, 0.2); + box-shadow: 0 2px 0 0 rgba(0,0,0,0.2); + border-radius: .5em; +} + +div.code-toolbar > .toolbar a:hover, +div.code-toolbar > .toolbar a:focus, +div.code-toolbar > .toolbar button:hover, +div.code-toolbar > .toolbar button:focus, +div.code-toolbar > .toolbar span:hover, +div.code-toolbar > .toolbar span:focus { + color: inherit; + text-decoration: none; +} diff --git a/src/main/resources/static/js/prism.js b/src/main/resources/static/js/prism.js new file mode 100644 index 000000000..b9a8db951 --- /dev/null +++ b/src/main/resources/static/js/prism.js @@ -0,0 +1,5 @@ +var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(g){var c=/\blang(?:uage)?-([\w-]+)\b/i,a=0,C={manual:g.Prism&&g.Prism.manual,disableWorkerMessageHandler:g.Prism&&g.Prism.disableWorkerMessageHandler,util:{encode:function(e){return e instanceof M?new M(e.type,C.util.encode(e.content),e.alias):Array.isArray(e)?e.map(C.util.encode):e.replace(/&/g,"&").replace(/e.length)return;if(!(k instanceof M)){if(f&&y!=a.length-1){if(c.lastIndex=v,!(x=c.exec(e)))break;for(var b=x.index+(h?x[1].length:0),w=x.index+x[0].length,A=y,P=v,O=a.length;A"+n.content+""},!g.document)return g.addEventListener&&(C.disableWorkerMessageHandler||g.addEventListener("message",function(e){var a=JSON.parse(e.data),n=a.language,t=a.code,r=a.immediateClose;g.postMessage(C.highlight(t,C.languages[n],n)),r&&g.close()},!1)),C;var e=document.currentScript||[].slice.call(document.getElementsByTagName("script")).pop();return e&&(C.filename=e.src,C.manual||e.hasAttribute("data-manual")||("loading"!==document.readyState?window.requestAnimationFrame?window.requestAnimationFrame(C.highlightAll):window.setTimeout(C.highlightAll,16):document.addEventListener("DOMContentLoaded",C.highlightAll))),C}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); +Prism.languages.hcl={comment:/(?:\/\/|#).*|\/\*[\s\S]*?(?:\*\/|$)/,heredoc:{pattern:/<<-?(\w+)[\s\S]*?^\s*\1/m,greedy:!0,alias:"string"},keyword:[{pattern:/(?:resource|data)\s+(?:"(?:\\[\s\S]|[^\\"])*")(?=\s+"[\w-]+"\s+{)/i,inside:{type:{pattern:/(resource|data|\s+)(?:"(?:\\[\s\S]|[^\\"])*")/i,lookbehind:!0,alias:"variable"}}},{pattern:/(?:provider|provisioner|variable|output|module|backend)\s+(?:[\w-]+|"(?:\\[\s\S]|[^\\"])*")\s+(?={)/i,inside:{type:{pattern:/(provider|provisioner|variable|output|module|backend)\s+(?:[\w-]+|"(?:\\[\s\S]|[^\\"])*")\s+/i,lookbehind:!0,alias:"variable"}}},{pattern:/[\w-]+(?=\s+{)/}],property:[/[\w-\.]+(?=\s*=(?!=))/,/"(?:\\[\s\S]|[^\\"])+"(?=\s*[:=])/],string:{pattern:/"(?:[^\\$"]|\\[\s\S]|\$(?:(?=")|\$+|[^"${])|\$\{(?:[^{}"]|"(?:[^\\"]|\\[\s\S])*")*\})*"/,greedy:!0,inside:{interpolation:{pattern:/(^|[^$])\$\{(?:[^{}"]|"(?:[^\\"]|\\[\s\S])*")*\}/,lookbehind:!0,inside:{type:{pattern:/(\b(?:terraform|var|self|count|module|path|data|local)\b\.)[\w\*]+/i,lookbehind:!0,alias:"variable"},keyword:/\b(?:terraform|var|self|count|module|path|data|local)\b/i,function:/\w+(?=\()/,string:{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0},number:/\b0x[\da-f]+|\d+\.?\d*(?:e[+-]?\d+)?/i,punctuation:/[!\$#%&'()*+,.\/;<=>@\[\\\]^`{|}~?:]/}}}},number:/\b0x[\da-f]+|\d+\.?\d*(?:e[+-]?\d+)?/i,boolean:/\b(?:true|false)\b/i,punctuation:/[=\[\]{}]/}; +Prism.languages.json={property:{pattern:/"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,greedy:!0},string:{pattern:/"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,greedy:!0},comment:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,number:/-?\d+\.?\d*(e[+-]?\d+)?/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:true|false)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}}; +!function(){if("undefined"!=typeof self&&self.Prism&&self.document){var r=[],i={},n=function(){};Prism.plugins.toolbar={};var t=Prism.plugins.toolbar.registerButton=function(t,n){var e;e="function"==typeof n?n:function(t){var e;return"function"==typeof n.onClick?((e=document.createElement("button")).type="button",e.addEventListener("click",function(){n.onClick.call(this,t)})):"string"==typeof n.url?(e=document.createElement("a")).href=n.url:e=document.createElement("span"),e.textContent=n.text,e},t in i?console.warn('There is a button with the key "'+t+'" registered already.'):r.push(i[t]=e)},e=Prism.plugins.toolbar.hook=function(a){var t=a.element.parentNode;if(t&&/pre/i.test(t.nodeName)&&!t.parentNode.classList.contains("code-toolbar")){var e=document.createElement("div");e.classList.add("code-toolbar"),t.parentNode.insertBefore(e,t),e.appendChild(t);var o=document.createElement("div");o.classList.add("toolbar"),document.body.hasAttribute("data-toolbar-order")&&(r=document.body.getAttribute("data-toolbar-order").split(",").map(function(t){return i[t]||n})),r.forEach(function(t){var e=t(a);if(e){var n=document.createElement("div");n.classList.add("toolbar-item"),n.appendChild(e),o.appendChild(n)}}),e.appendChild(o)}};t("label",function(t){var e=t.element.parentNode;if(e&&/pre/i.test(e.nodeName)&&e.hasAttribute("data-label")){var n,a,o=e.getAttribute("data-label");try{a=document.querySelector("template#"+o)}catch(t){}return a?n=a.content:(e.hasAttribute("data-url")?(n=document.createElement("a")).href=e.getAttribute("data-url"):n=document.createElement("span"),n.textContent=o),n}}),Prism.hooks.add("complete",e)}}(); +!function(){if("undefined"!=typeof self&&self.Prism&&self.document)if(Prism.plugins.toolbar){var r=window.ClipboardJS||void 0;r||"function"!=typeof require||(r=require("clipboard"));var i=[];if(!r){var o=document.createElement("script"),e=document.querySelector("head");o.onload=function(){if(r=window.ClipboardJS)for(;i.length;)i.pop()()},o.src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.0/clipboard.min.js",e.appendChild(o)}Prism.plugins.toolbar.registerButton("copy-to-clipboard",function(e){var t=document.createElement("a");return t.textContent="Copy",r?o():i.push(o),t;function o(){var o=new r(t,{text:function(){return e.code}});o.on("success",function(){t.textContent="Copied!",n()}),o.on("error",function(){t.textContent="Press Ctrl+C to copy",n()})}function n(){setTimeout(function(){t.textContent="Copy"},5e3)}})}else console.warn("Copy to Clipboard plugin loaded before Toolbar plugin.")}(); diff --git a/src/main/resources/templates/empty_page.tpl b/src/main/resources/templates/empty_page.tpl new file mode 100644 index 000000000..6cc4f5010 --- /dev/null +++ b/src/main/resources/templates/empty_page.tpl @@ -0,0 +1,72 @@ + + + + + + + + + + + + Gaia + + + + + + + + + + + + + + + + +
+
+ +
+ + +
+ +
+ +
+
+
+
+
+

+
+
+
+
+ + +
+
+
+ +
+
+
+ + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/layout/header.html b/src/main/resources/templates/layout/header.html index 196dd6fbe..85b575b98 100644 --- a/src/main/resources/templates/layout/header.html +++ b/src/main/resources/templates/layout/header.html @@ -21,6 +21,8 @@ + + \ No newline at end of file diff --git a/src/main/resources/templates/layout/sidebar.html b/src/main/resources/templates/layout/sidebar.html index efc684cc7..914311a28 100644 --- a/src/main/resources/templates/layout/sidebar.html +++ b/src/main/resources/templates/layout/sidebar.html @@ -22,7 +22,7 @@

Menu

diff --git a/src/main/resources/templates/stack.html b/src/main/resources/templates/stack.html new file mode 100644 index 000000000..b9525f166 --- /dev/null +++ b/src/main/resources/templates/stack.html @@ -0,0 +1,217 @@ + + + + + Gaia - Edit stack + + + +
+
+ +
+ + +
+ +
+ +
+
+
+
+
+ +
+
+
+ +
+ +
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/stacks.html b/src/main/resources/templates/stacks.html new file mode 100644 index 000000000..d0e7d367d --- /dev/null +++ b/src/main/resources/templates/stacks.html @@ -0,0 +1,65 @@ + + + + + Gaia - Stacks + + + +
+
+ +
+ + +
+ +
+ +
+
+
+
+
+

Stacks

+
+
+
+
+ +
+ +
+
+
Card title
+ +
+
+ +
+
+
+
+ +
+
+
+ + + + + + + + + \ No newline at end of file