Browse code

initial import

devnewton authored on 14/12/2015 at 19:47:59
Showing 25 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+/target/
1
+/.settings
2
+/.classpath
3
+/.project
0 4
new file mode 100644
... ...
@@ -0,0 +1,30 @@
0
+# Source code
1
+
2
+The MIT License (MIT)
3
+
4
+Copyright (c) 2014 devnewton <devnewton@bci.im>
5
+
6
+Permission is hereby granted, free of charge, to any person obtaining a copy
7
+of this software and associated documentation files (the "Software"), to deal
8
+in the Software without restriction, including without limitation the rights
9
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+copies of the Software, and to permit persons to whom the Software is
11
+furnished to do so, subject to the following conditions:
12
+
13
+The above copyright notice and this permission notice shall be included in
14
+all copies or substantial portions of the Software.
15
+
16
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+THE SOFTWARE.
23
+
24
+# Data
25
+
26
+## favicon.png
27
+
28
+License: this is a free work, you can copy, distribute, and modify it under the terms of the Free Art License 1.3 (FAL1.3.txt file).
29
+Authors: Geeky Goblin Productions (http://geekygoblin.org/), devnewton <devnewton@bci.im>
0 30
\ No newline at end of file
1 31
new file mode 100644
... ...
@@ -0,0 +1,109 @@
0
+# jb3
1
+
2
+jb3 is a KISS tribune powered by java, spring boot and mongodb.
3
+
4
+## Features
5
+
6
+- archives
7
+- fortunes
8
+- bots
9
+- last id
10
+- XPost
11
+- X-Post-Id
12
+- rooms
13
+
14
+# Demo
15
+
16
+A live demo is accessible [here](http://b3.bci.im).
17
+
18
+## Coincoin configuration
19
+
20
+You can also configure your favorite coincoin using these parameters:
21
+
22
+- backend URL: http://b3.bci.im/legacy/xml
23
+- post URL: http://b3.bci.im/legacy/post
24
+- post data: message=%m
25
+- tags: encoded
26
+
27
+## Advanced coincoin configuration
28
+
29
+- backend using last id: http://b3.bci.im/legacy/xml?last=%last
30
+- post returns backend: post request to http://b3.bci.im/legacy/post include backend data (same as GET http://b3.bci.im/legacy/xml response).
31
+- post message using last id: message=%m&last=%last
32
+- XPOST: post reply body contains last messages
33
+- X-Post-Id: post reply returns posted message id in X-Post-Id header
34
+
35
+## Rooms
36
+
37
+Rooms are like IRC channel. From a coincoin, they can be seen as independent tribune.
38
+
39
+- room backend : http://b3.bci.im/legacy/xml?room=%room
40
+- post message in a room: message=%m&room=%room
41
+
42
+## Gateway rooms
43
+
44
+Gateway are used to receive and post on external tribunes or other chat systems.
45
+
46
+Implemented gateways:
47
+
48
+- euromussels
49
+- sveetch
50
+
51
+# How to use
52
+
53
+## Skill check
54
+
55
+Please note that a thorough knowledge of Java web application development and hosting is required.
56
+
57
+## Requirements
58
+
59
+- JDK 7+
60
+- Maven 3+
61
+- mongodb 2+
62
+
63
+## Build
64
+
65
+    mvn package
66
+
67
+## Run locally
68
+
69
+Ensure that mongodb is running and listening on 127.0.0.1 then run:
70
+
71
+    java -jar target/jb3-*.jar
72
+
73
+The frontend is now accessible on [locahost:8080](http://localhost:8080).
74
+
75
+## Deploy and hosting on a production server
76
+
77
+There is several options to deploy and host jb3. Here is one that requires:
78
+
79
+- a domain name (example: mydomain.me).
80
+- a web server with http proxy capabilities (example [Cherokee](http://cherokee-project.com/)).
81
+
82
+### Application configuration
83
+
84
+Edit the application.properties file and change the jb3.host property:
85
+
86
+    jb3.host=http://mydomain.me
87
+
88
+### Security
89
+
90
+In production environnement, please add
91
+
92
+    jb3.secure=true
93
+
94
+to your application.properties configuration. If you don't do it, jb3 will do
95
+VERY INSECURE operations like:
96
+
97
+- not verifying self signed HTTPS certificates.
98
+- not verifying untrusted HTTPS certificates (like linuxfr.org CACERT certificate).
99
+
100
+It is VERY RECOMMENDED, to use jb3.secure=true and add certificates to your
101
+keystore. For example to add CACERT, use this guide:
102
+
103
+http://wiki.cacert.org/FAQ/ImportRootCert?action=show&redirect=ImportRootCert#Java
104
+
105
+### Deployment
106
+
107
+1. Build and launch jb3 on the server.
108
+2. Configure your web server to act as reverse proxy on http://localhost:8080
0 109
new file mode 100644
... ...
@@ -0,0 +1,69 @@
0
+<?xml version="1.0" encoding="UTF-8"?>
1
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2
+    <modelVersion>4.0.0</modelVersion>
3
+    <parent>
4
+        <groupId>org.springframework.boot</groupId>
5
+        <artifactId>spring-boot-starter-parent</artifactId>
6
+        <version>1.3.0.RELEASE</version>
7
+    </parent>
8
+    <artifactId>passgrid</artifactId>
9
+    <groupId>im.bci</groupId>
10
+    <name>passgrid</name>
11
+    <description>passgrid generator and storage</description>
12
+    <version>1.0-SNAPSHOT</version>
13
+    <url>http://devnewton.bci.im</url>
14
+    <properties>
15
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
16
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
17
+    </properties>
18
+    <dependencies>
19
+        <dependency>
20
+            <groupId>org.springframework.boot</groupId>
21
+            <artifactId>spring-boot-starter</artifactId>
22
+        </dependency>
23
+        <dependency>
24
+            <groupId>org.springframework.data</groupId>
25
+            <artifactId>spring-data-mongodb</artifactId>
26
+        </dependency>
27
+        <dependency>
28
+            <groupId>org.springframework.boot</groupId>
29
+            <artifactId>spring-boot-starter-web</artifactId>
30
+        </dependency>
31
+        <dependency>
32
+            <groupId>org.springframework.boot</groupId>
33
+            <artifactId>spring-boot-starter-security</artifactId>
34
+        </dependency>
35
+        <dependency>
36
+            <groupId>pl.allegro.tech.boot</groupId>
37
+            <artifactId>handlebars-spring-boot-starter</artifactId>
38
+            <version>0.2.8</version>
39
+        </dependency>
40
+        <dependency>
41
+            <groupId>org.springframework.boot</groupId>
42
+            <artifactId>spring-boot-starter-test</artifactId>
43
+            <scope>test</scope>
44
+        </dependency>
45
+    </dependencies>
46
+    <build>
47
+        <resources>
48
+            <resource>
49
+                <directory>src/main/resources</directory>
50
+                <filtering>false</filtering>
51
+                <includes>
52
+                    <include>**/*.html</include>
53
+                    <include>**/*.properties</include>
54
+                    <include>**/*.xml</include>
55
+                    <include>**/*.js</include>
56
+                    <include>**/*.css</include>
57
+                    <include>**/*.png</include>
58
+                </includes>
59
+            </resource>
60
+        </resources>
61
+        <plugins>
62
+            <plugin>
63
+                <groupId>org.springframework.boot</groupId>
64
+                <artifactId>spring-boot-maven-plugin</artifactId>
65
+            </plugin>
66
+        </plugins>
67
+    </build>
68
+</project>
0 69
new file mode 100644
... ...
@@ -0,0 +1,40 @@
0
+package im.bci.passgrid;
1
+
2
+import org.springframework.beans.factory.annotation.Autowired;
3
+import org.springframework.boot.CommandLineRunner;
4
+import org.springframework.boot.SpringApplication;
5
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
6
+import org.springframework.context.annotation.Bean;
7
+import org.springframework.context.annotation.ComponentScan;
8
+import org.springframework.context.annotation.Configuration;
9
+import org.springframework.data.web.config.EnableSpringDataWebSupport;
10
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
11
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
12
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
13
+import org.springframework.web.filter.CharacterEncodingFilter;
14
+
15
+@Configuration
16
+@EnableAutoConfiguration
17
+@ComponentScan
18
+@EnableSpringDataWebSupport
19
+@EnableWebSecurity
20
+@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
21
+public class PassgridApplication implements CommandLineRunner {
22
+
23
+    @Override
24
+    public void run(String... args) throws Exception {
25
+        System.out.println("Launch passgrid.");
26
+    }
27
+
28
+    @Bean
29
+    CharacterEncodingFilter characterEncodingFilter() {
30
+        CharacterEncodingFilter filter = new CharacterEncodingFilter();
31
+        filter.setEncoding("UTF-8");
32
+        filter.setForceEncoding(true);
33
+        return filter;
34
+    }
35
+
36
+    public static void main(String[] args) throws Exception {
37
+        SpringApplication.run(PassgridApplication.class, args);
38
+    }
39
+}
0 40
new file mode 100644
... ...
@@ -0,0 +1,19 @@
0
+package im.bci.passgrid.controllers;
1
+
2
+import javax.servlet.http.HttpServletRequest;
3
+import org.springframework.ui.Model;
4
+import org.springframework.web.bind.annotation.ControllerAdvice;
5
+import org.springframework.web.bind.annotation.ModelAttribute;
6
+
7
+/**
8
+ *
9
+ * @author devnewton
10
+ */
11
+@ControllerAdvice
12
+public class CsrfControllerAdvice {
13
+
14
+    @ModelAttribute
15
+    public void addAttributes(Model model, HttpServletRequest request) {
16
+        model.addAttribute("_csrf", request.getAttribute("_csrf"));
17
+    }
18
+}
0 19
new file mode 100644
... ...
@@ -0,0 +1,47 @@
0
+package im.bci.passgrid.controllers;
1
+
2
+import im.bci.passgrid.frontend.ErrorMV;
3
+import java.util.Map;
4
+import javax.servlet.http.HttpServletRequest;
5
+import javax.servlet.http.HttpServletResponse;
6
+import org.springframework.beans.factory.annotation.Autowired;
7
+import org.springframework.boot.autoconfigure.web.ErrorAttributes;
8
+import org.springframework.boot.autoconfigure.web.ErrorController;
9
+import org.springframework.stereotype.Controller;
10
+import org.springframework.ui.Model;
11
+import org.springframework.web.bind.annotation.RequestMapping;
12
+import org.springframework.web.bind.annotation.RestController;
13
+import org.springframework.web.context.request.RequestAttributes;
14
+import org.springframework.web.context.request.ServletRequestAttributes;
15
+
16
+/**
17
+ *
18
+ * @author devnewton
19
+ */
20
+@Controller
21
+public class CustomErrorController implements ErrorController {
22
+    
23
+    @Autowired
24
+    private ErrorAttributes errorAttributes;
25
+
26
+    @RequestMapping(value = "/error")
27
+    public String error(Model model, HttpServletRequest request, HttpServletResponse response) {
28
+        model.addAttribute("error", buildErrorMV(request, response));
29
+        return "error";
30
+    }
31
+
32
+    public ErrorMV buildErrorMV(HttpServletRequest request, HttpServletResponse response) {
33
+        return new ErrorMV(response.getStatus(), getErrorAttributes(request, true));
34
+    }
35
+
36
+    private Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
37
+        RequestAttributes requestAttributes = new ServletRequestAttributes(request);
38
+        return errorAttributes.getErrorAttributes(requestAttributes, includeStackTrace);
39
+    }
40
+
41
+    @Override
42
+    public String getErrorPath() {
43
+        return "error";
44
+    }
45
+
46
+}
0 47
new file mode 100644
... ...
@@ -0,0 +1,62 @@
0
+package im.bci.passgrid.controllers;
1
+
2
+import im.bci.passgrid.data.Passgrid;
3
+import im.bci.passgrid.data.PassgridRepository;
4
+import im.bci.passgrid.frontend.CreatePassgridRQ;
5
+import java.util.Random;
6
+import org.springframework.beans.factory.annotation.Autowired;
7
+import org.springframework.stereotype.Controller;
8
+import org.springframework.ui.Model;
9
+import org.springframework.web.bind.annotation.PathVariable;
10
+import org.springframework.web.bind.annotation.RequestMapping;
11
+import org.springframework.web.bind.annotation.RequestMethod;
12
+import org.springframework.web.servlet.mvc.support.RedirectAttributes;
13
+
14
+@Controller
15
+@RequestMapping("/")
16
+public class FrontendController {
17
+    
18
+    @Autowired
19
+    private PassgridRepository passgridRepository;
20
+    
21
+    @RequestMapping("")
22
+    public String index(Model model) {
23
+        return "index";
24
+    }
25
+    
26
+    @RequestMapping("search")
27
+    public String search(String name, Model model) {
28
+        model.addAttribute("passgrids", passgridRepository.searchByName(name));
29
+        return "search";
30
+    }
31
+    
32
+    @RequestMapping("view/{id}")
33
+    public String passgrid(@PathVariable("id") String id, Model model) {
34
+        model.addAttribute("passgrid", passgridRepository.findOne(id));
35
+        return "view";
36
+    }
37
+    
38
+    @RequestMapping(value = "create", method = RequestMethod.POST)
39
+    public String create(CreatePassgridRQ rq, Model model, RedirectAttributes attributes) {
40
+        Passgrid passgrid = new Passgrid();
41
+        passgrid.setName(rq.getName());
42
+        passgrid.setGrid(generateGrid(rq));
43
+        passgridRepository.save(passgrid);
44
+        attributes.addAttribute("id", passgrid.getId());
45
+        return "redirect:/view/{id}";
46
+    }
47
+    
48
+    private Character[][] generateGrid(CreatePassgridRQ rq) {
49
+        final int lines = rq.getLines();
50
+        final int columns = rq.getColumns();
51
+        final Character[][] grid = new Character[lines][columns];
52
+        Random random = new Random();
53
+        for (int y = 0; y < lines; ++y) {
54
+            for (int x = 0; x < columns; ++x) {
55
+                final String allowedCharacters = rq.getAllowedCharacters();
56
+                grid[y][x] = allowedCharacters.charAt(random.nextInt(allowedCharacters.length()));
57
+            }
58
+        }
59
+        return grid;
60
+    }
61
+}
0 62
new file mode 100644
... ...
@@ -0,0 +1,37 @@
0
+package im.bci.passgrid.data;
1
+
2
+import org.springframework.data.annotation.Id;
3
+
4
+public class Passgrid {
5
+
6
+    @Id
7
+    private String id;
8
+
9
+    private String name;
10
+
11
+    private Character[][] grid;
12
+
13
+    public String getId() {
14
+        return id;
15
+    }
16
+
17
+    public void setId(String id) {
18
+        this.id = id;
19
+    }
20
+
21
+    public String getName() {
22
+        return name;
23
+    }
24
+
25
+    public void setName(String name) {
26
+        this.name = name;
27
+    }
28
+
29
+    public void setGrid(Character[][] grid) {
30
+        this.grid = grid;
31
+    }
32
+
33
+    public Character[][] getGrid() {
34
+        return grid;
35
+    }
36
+}
0 37
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+package im.bci.passgrid.data;
1
+
2
+import java.util.List;
3
+
4
+public interface PassgridRepository {
5
+    
6
+    void save(Passgrid passgrid);
7
+
8
+    Passgrid findOne(String id);
9
+    
10
+    List<Passgrid> searchByName(String name);
11
+    
12
+}
0 13
new file mode 100644
... ...
@@ -0,0 +1,40 @@
0
+package im.bci.passgrid.data;
1
+
2
+import java.util.List;
3
+import org.apache.commons.lang3.StringUtils;
4
+import org.springframework.beans.factory.annotation.Autowired;
5
+import org.springframework.data.mongodb.core.MongoTemplate;
6
+import org.springframework.data.mongodb.core.query.Criteria;
7
+import org.springframework.data.mongodb.core.query.Query;
8
+import org.springframework.stereotype.Component;
9
+
10
+@Component
11
+public class PassgridRepositoryImpl implements PassgridRepository {
12
+
13
+    private static final String COLLECTION_NAME = "passgrid";
14
+
15
+    @Autowired
16
+    private MongoTemplate mongoTemplate;
17
+
18
+    @Override
19
+    public void save(Passgrid passgrid) {
20
+        mongoTemplate.save(passgrid, COLLECTION_NAME);
21
+    }
22
+
23
+    @Override
24
+    public Passgrid findOne(String id) {
25
+        Passgrid result = mongoTemplate.findById(id, Passgrid.class, COLLECTION_NAME);
26
+        return result;
27
+    }
28
+
29
+    @Override
30
+    public List<Passgrid> searchByName(String name) {
31
+        Query query = new Query();
32
+        if (StringUtils.isNotBlank(name)) {
33
+            query = query.addCriteria(Criteria.where("name").regex(name));
34
+        }
35
+        List<Passgrid> results = mongoTemplate.find(query, Passgrid.class, COLLECTION_NAME);
36
+        return results;
37
+    }
38
+
39
+}
0 40
new file mode 100644
... ...
@@ -0,0 +1,46 @@
0
+package im.bci.passgrid.frontend;
1
+
2
+/**
3
+ *
4
+ * @author devnewton
5
+ */
6
+public class CreatePassgridRQ {
7
+
8
+    private String name;
9
+    private int lines;
10
+    private int columns;
11
+    private String allowedCharacters;
12
+
13
+    public String getName() {
14
+        return name;
15
+    }
16
+
17
+    public void setName(String name) {
18
+        this.name = name;
19
+    }
20
+
21
+    public int getLines() {
22
+        return lines;
23
+    }
24
+
25
+    public void setLines(int lines) {
26
+        this.lines = lines;
27
+    }
28
+
29
+    public int getColumns() {
30
+        return columns;
31
+    }
32
+
33
+    public void setColumns(int columns) {
34
+        this.columns = columns;
35
+    }
36
+
37
+    public String getAllowedCharacters() {
38
+        return allowedCharacters;
39
+    }
40
+
41
+    public void setAllowedCharacters(String allowedCharacters) {
42
+        this.allowedCharacters = allowedCharacters;
43
+    }
44
+
45
+}
0 46
new file mode 100644
... ...
@@ -0,0 +1,68 @@
0
+package im.bci.passgrid.frontend;
1
+
2
+import java.util.Map;
3
+
4
+/**
5
+ *
6
+ * @author devnewton
7
+ */
8
+public class ErrorMV {
9
+
10
+    public Integer status;
11
+    public String error;
12
+    public String message;
13
+    public String timeStamp;
14
+    public String trace;
15
+
16
+    public ErrorMV(int status, Map<String, Object> errorAttributes) {
17
+        this.status = status;
18
+        this.error = (String) errorAttributes.get("error");
19
+        this.message = (String) errorAttributes.get("message");
20
+        this.timeStamp = errorAttributes.get("timestamp").toString();
21
+        this.trace = (String) errorAttributes.get("trace");
22
+    }
23
+
24
+    public ErrorMV() {
25
+    }
26
+
27
+    public Integer getStatus() {
28
+        return status;
29
+    }
30
+
31
+    public void setStatus(Integer status) {
32
+        this.status = status;
33
+    }
34
+
35
+    public String getError() {
36
+        return error;
37
+    }
38
+
39
+    public void setError(String error) {
40
+        this.error = error;
41
+    }
42
+
43
+    public String getMessage() {
44
+        return message;
45
+    }
46
+
47
+    public void setMessage(String message) {
48
+        this.message = message;
49
+    }
50
+
51
+    public String getTimeStamp() {
52
+        return timeStamp;
53
+    }
54
+
55
+    public void setTimeStamp(String timeStamp) {
56
+        this.timeStamp = timeStamp;
57
+    }
58
+
59
+    public String getTrace() {
60
+        return trace;
61
+    }
62
+
63
+    public void setTrace(String trace) {
64
+        this.trace = trace;
65
+    }
66
+
67
+}
0 68
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+package im.bci.passgrid.handlebars;
1
+
2
+import im.bci.passgrid.data.Passgrid;
3
+import pl.allegro.tech.boot.autoconfigure.handlebars.HandlebarsHelper;
4
+
5
+/**
6
+ *
7
+ * @author devnewton
8
+ */
9
+@HandlebarsHelper
10
+public class PassgridHelper {
11
+
12
+    public String passgridColspan(Passgrid context) {
13
+        Character[][] grid = context.getGrid();
14
+        if (null != grid && grid.length > 0) {
15
+            Character[] row = grid[0];
16
+            if (null != row && row.length > 0) {
17
+                return String.valueOf(row.length + 1);
18
+            }
19
+        }
20
+        return "";
21
+    }
22
+}
0 23
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+handlebars.suffix=.html
1
+handlebars.failOnMissingFile=true
2
+handlebars.prettyPrint=true
3
+handlebars.cache=false
4
+
5
+spring.data.mongodb.database=passgrid
6
+error.whitelabel.enabled=false
7
+
8
+security.user.name=admin
9
+security.user.password=admin
10
+security.enable-csrf=true
11
+
12
+logging.level.org.springframework.web: DEBUG
0 13
\ No newline at end of file
1 14
new file mode 100644
2 15
Binary files /dev/null and b/src/main/resources/static/favicon.png differ
3 16
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+.passgrid {
1
+    font-family: monospace;
2
+    width: 8cm;
3
+    height: 6cm;
4
+    border-collapse: collapse;
5
+    border: 1px solid black;
6
+    float: left;
7
+    page-break-inside : avoid;
8
+    margin: 2mm;
9
+}
10
+.passgrid th {
11
+    background-color: #CCCCFF;
12
+    font-size: 0.8em;
13
+}
14
+
15
+.passgrid td {
16
+    text-align: center;
17
+    vertical-align: middle;
18
+    border-bottom: dotted 1px black;
19
+}
20
+
21
+.passgrid tbody tr:nth-child(odd) td:nth-child(odd) {
22
+    background-color: #CCCCCC;
23
+}
24
+.passgrid tbody tr:nth-child(even) td:nth-child(even) {
25
+    background-color: #CCCCCC;
26
+}
0 27
\ No newline at end of file
1 28
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+<meta charset="UTF-8">
1
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
2
+<meta name="robots" content="noindex,nofollow">
3
+<link rel="stylesheet" type="text/css" href="/passgrid.css">
4
+<link rel="icon" type="image/png" href="/favicon.png">
0 5
\ No newline at end of file
1 6
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+<h2>Create grid</h2>
1
+<form action="create" method="POST">
2
+    <label for="create-passgrid-name" >Name: </label>
3
+    <input id="create-passgrid-name" type="text" name="name">
4
+    <label for="create-passgrid-allowed-characters" >Lines: </label>
5
+    <input id="create-passgrid-allowed-characters" type="text" name="allowedCharacters" value="0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz">
6
+    <label for="create-passgrid-lines" >Lines: </label>
7
+    <input id="create-passgrid-lines" type="number" name="lines" value="26">
8
+    <label for="create-passgrid-columns" >Columns: </label>
9
+    <input id="create-passgrid-columns" type="number" name="columns" value="10">
10
+    <input type="hidden" name="{{_csrf.parameterName}}" value="{{_csrf.token}}"/>
11
+    <input type="submit" >
12
+</form>
0 13
\ No newline at end of file
1 14
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+<!DOCTYPE html>
1
+<html>
2
+    <head>
3
+        <title>passgrid error</title>
4
+        {{> common-head}}
5
+    </head>
6
+    <body>
7
+        <table>
8
+            <tr>
9
+                <th>Status</th>
10
+                <td>{{error.status}}</td>
11
+            </tr>
12
+            <tr>
13
+                <th>Error</th>
14
+                <td>{{error.error}}</td>
15
+            </tr>
16
+            <tr>
17
+                <th>Message</th>
18
+                <td>{{error.message}}</td>
19
+            </tr>
20
+            <tr>
21
+                <th>Trace</th>
22
+                <td>{{error.trace}}</td>
23
+            </tr>
24
+        </table>
25
+    </body>
26
+</html>
0 27
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+<!DOCTYPE html>
1
+<html>
2
+    <head>
3
+        <title>passgrid</title>
4
+        {{> common-head}}
5
+    </head>
6
+    <body>
7
+        {{> search-form}}
8
+        {{> create-form}}
9
+    </body>
10
+</html>
0 11
\ No newline at end of file
1 12
new file mode 100644
... ...
@@ -0,0 +1,25 @@
0
+<table class="passgrid">
1
+    <thead>
2
+        <tr>
3
+            <td colspan="{{passgridColspan}}">{{name}}</td>
4
+        </tr>
5
+    </thead>
6
+    <tbody>
7
+        {{#each grid}}
8
+        {{#if @first}}
9
+        <tr>
10
+            <th></th>
11
+            {{#each this}}
12
+            <th>{{@index_1}}</th>
13
+            {{/each}}            
14
+        </tr>
15
+        {{/if}}
16
+        <tr>
17
+            <th>{{@index_1}}</th>
18
+            {{#each this}}
19
+            <td>{{this}}</td>
20
+            {{/each}}
21
+        </tr>
22
+        {{/each}}
23
+    </tbody>
24
+</table>
0 25
\ No newline at end of file
1 26
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+<form action="search">
1
+    <label for="search-passgrid-name" >Search grid: </label>
2
+    <input id="search-passgrid-name" type="text" name="name">
3
+    <input type="submit" >
4
+</form>
0 5
\ No newline at end of file
1 6
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+<!DOCTYPE html>
1
+<html>
2
+    <head>
3
+        <title>passgrid search</title>
4
+        {{> common-head}}
5
+    </head>
6
+    <body>
7
+        {{> search-form}}
8
+        {{#each passgrids}}
9
+        <div>
10
+            <a href="/view/{{this.id}}">{{this.id}}</a>
11
+        </div>
12
+        {{/each}}
13
+    </body>
14
+</html>
0 15
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+<!DOCTYPE html>
1
+<html>
2
+    <head>
3
+        <title>passgrid</title>
4
+        {{> common-head}}
5
+    </head>
6
+    <body>
7
+        {{> search-form}}
8
+        {{> passgrid passgrid}}
9
+        {{> create-form}}
10
+    </body>
11
+</html>
0 12
\ No newline at end of file