Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
641c340b15 | |||
af4181185c | |||
ced84a05a6 | |||
474d014f09 | |||
a5346f6970 | |||
85b831e05c | |||
911ea52b96 | |||
385fe25dab | |||
8ceadfa28a | |||
42f8978d06 | |||
2a2844afcc | |||
943171f35a | |||
0037f07559 | |||
40cc234b83 | |||
dce2e3ece3 | |||
24ae4f99ec | |||
852ecb4993 | |||
b5373fd286 | |||
9ebc9f6188 | |||
f7db51db63 | |||
b3e92aec86 | |||
7ab40e7fd3 | |||
561f9dc60f | |||
c0cfd538db | |||
d86cb08338 | |||
60cdc31b3f | |||
6a7f291e5c | |||
b9ad037f60 | |||
e443ea3fd4 | |||
3eb617f272 | |||
ecfcecb53c | |||
795ca1b682 | |||
9119fe57f3 | |||
e569420ca5 | |||
b6c4cb1575 | |||
85a8f41da0 | |||
05ba6fbacc | |||
77bff3924c | |||
4e40f0bdd8 | |||
b725458a8a |
3
.gitignore
vendored
3
.gitignore
vendored
@ -29,6 +29,7 @@ build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
.cache/
|
||||
|
||||
node_modules
|
||||
bundle.js
|
||||
@ -36,6 +37,8 @@ bundle.min.js
|
||||
adminBundle.js
|
||||
adminBundle.min.js
|
||||
worker.js
|
||||
chatWorker.js
|
||||
chatWorker.min.js
|
||||
node
|
||||
src/main/javascript/node/
|
||||
dist
|
||||
|
51
.prettierignore
Normal file
51
.prettierignore
Normal file
@ -0,0 +1,51 @@
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**
|
||||
!**/src/test/**
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
.cache/
|
||||
|
||||
node_modules
|
||||
bundle.js
|
||||
bundle.min.js
|
||||
adminBundle.js
|
||||
adminBundle.min.js
|
||||
worker.js
|
||||
chatWorker.js
|
||||
chatWorker.min.js
|
||||
node
|
||||
src/main/javascript/node/
|
||||
dist
|
||||
out
|
||||
yarn-error.log
|
||||
gen-password.txt
|
||||
|
||||
**/static/**
|
||||
|
||||
License.md
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"printWidth": 80
|
||||
}
|
209
Gruntfile.js
209
Gruntfile.js
@ -1,97 +1,140 @@
|
||||
module.exports = function (grunt) {
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
terser: {
|
||||
options: {
|
||||
// Task-specific options go here.
|
||||
},
|
||||
build: {
|
||||
// Target-specific file lists and/or options go here.
|
||||
src: 'src/main/resources/static/js/bundle.js',
|
||||
dest: 'src/main/resources/static/js/bundle.min.js'
|
||||
},
|
||||
chat_worker: {
|
||||
src: 'src/main/resources/static/js/worker.js',
|
||||
dest: 'src/main/resources/static/js/worker.js'
|
||||
},
|
||||
},
|
||||
banner: `
|
||||
const dedent = require("dedent-js");
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON("package.json"),
|
||||
terser: {
|
||||
options: {
|
||||
// Task-specific options go here.
|
||||
},
|
||||
build: {
|
||||
// Target-specific file lists and/or options go here.
|
||||
src: "src/main/resources/static/js/bundle.js",
|
||||
dest: "src/main/resources/static/js/bundle.min.js",
|
||||
},
|
||||
chat_worker: {
|
||||
src: "src/main/resources/static/js/chatWorker.js",
|
||||
dest: "src/main/resources/static/js/chatWorker.min.js",
|
||||
},
|
||||
admin_bundle: {
|
||||
src: "src/main/resources/static/js/adminBundle.js",
|
||||
dest: "src/main/resources/static/js/adminBundle.min.js",
|
||||
},
|
||||
},
|
||||
banner: dedent(`
|
||||
/*
|
||||
* -----------------------------------------
|
||||
* @date <%= grunt.template.today("yyyy-mm-dd") %>
|
||||
* @project Chatto
|
||||
* @author nova
|
||||
* @license GPL
|
||||
* -----------------------------------------
|
||||
`,
|
||||
usebanner: {
|
||||
dist: {
|
||||
options: {
|
||||
position: 'top',
|
||||
banner: '/*! <%= banner %> */ '
|
||||
},
|
||||
files: {
|
||||
src: ['src/main/resources/static/js/bundle.min.js', 'src/main/resources/static/js/worker.js']
|
||||
}
|
||||
}
|
||||
*/
|
||||
`),
|
||||
usebanner: {
|
||||
dist: {
|
||||
options: {
|
||||
position: "top",
|
||||
banner: "<%= banner %>",
|
||||
},
|
||||
browserify: {
|
||||
chat_worker_dev: {
|
||||
src: 'src/main/frontend/workers/encryption-worker/main.ts',
|
||||
dest: 'src/main/resources/static/js/worker.js',
|
||||
options: {
|
||||
browserifyOptions: {
|
||||
debug: true
|
||||
},
|
||||
}
|
||||
files: {
|
||||
src: [
|
||||
"src/main/resources/static/js/bundle.min.js",
|
||||
"src/main/resources/static/js/adminBundle.min.js",
|
||||
"src/main/resources/static/js/chatWorker.min.js",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
browserify: {
|
||||
chat_worker_dev: {
|
||||
src: "src/main/frontend/workers/encryption-worker/main.ts",
|
||||
dest: "src/main/resources/static/js/chatWorker.js",
|
||||
options: {
|
||||
browserifyOptions: {
|
||||
debug: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
dev: {
|
||||
src: "src/main/frontend/chat/main.ts",
|
||||
dest: "src/main/resources/static/js/bundle.js",
|
||||
options: {
|
||||
browserifyOptions: {
|
||||
debug: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
prod: {
|
||||
src: "src/main/frontend/chat/main.ts",
|
||||
dest: "src/main/resources/static/js/bundle.js",
|
||||
banner: '/*! Chat.js <%= grunt.template.today("yyyy-mm-dd") %> */ ',
|
||||
options: {
|
||||
browserifyOptions: {
|
||||
debug: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
chat_worker_prod: {
|
||||
src: "src/main/frontend/workers/encryption-worker/main.ts",
|
||||
dest: "src/main/resources/static/js/chatWorker.js",
|
||||
},
|
||||
admin_bundle_dev: {
|
||||
src: "src/main/frontend/admin/main.ts",
|
||||
dest: "src/main/resources/static/js/adminBundle.js",
|
||||
options: {
|
||||
browserifyOptions: {
|
||||
debug: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
admin_bundle_prod: {
|
||||
src: "src/main/frontend/admin/main.ts",
|
||||
dest: "src/main/resources/static/js/adminBundle.js",
|
||||
banner: '/*! Chat.js <%= grunt.template.today("yyyy-mm-dd") %> */ ',
|
||||
options: {
|
||||
browserifyOptions: {
|
||||
debug: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
options: {
|
||||
plugin: [
|
||||
[
|
||||
"tsify",
|
||||
{
|
||||
target: "ES6",
|
||||
noImplicitAny: true,
|
||||
esModuleInterop: true,
|
||||
allowSyntheticDefaultImports: true,
|
||||
},
|
||||
dev: {
|
||||
src: 'src/main/frontend/chat/main.ts',
|
||||
dest: 'src/main/resources/static/js/bundle.js',
|
||||
options: {
|
||||
browserifyOptions: {
|
||||
debug: true
|
||||
},
|
||||
}
|
||||
},
|
||||
prod: {
|
||||
src: 'src/main/frontend/chat/main.ts',
|
||||
dest: 'src/main/resources/static/js/bundle.js',
|
||||
banner: '/*! Chat.js <%= grunt.template.today("yyyy-mm-dd") %> */ ',
|
||||
options: {
|
||||
browserifyOptions: {
|
||||
debug: false
|
||||
},
|
||||
}
|
||||
},
|
||||
chat_worker_prod: {
|
||||
src: 'src/main/frontend/workers/encryption-worker/main.ts',
|
||||
dest: 'src/main/resources/static/js/worker.min.js',
|
||||
},
|
||||
options: {
|
||||
plugin: [
|
||||
['tsify', { target: 'ES6', noImplicitAny: true, esModuleInterop: true, allowSyntheticDefaultImports: true }], // register plugin by name
|
||||
],
|
||||
], // register plugin by name
|
||||
],
|
||||
|
||||
browserifyOptions: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
browserifyOptions: {},
|
||||
}
|
||||
}
|
||||
});
|
||||
// Load the plugin that provides the "uglify" task.
|
||||
grunt.loadNpmTasks("grunt-terser");
|
||||
|
||||
// Load the plugin that provides the "uglify" task.
|
||||
grunt.loadNpmTasks('grunt-terser');
|
||||
// // Default task(s).
|
||||
// grunt.registerTask('default', ['uglify']);
|
||||
|
||||
// // Default task(s).
|
||||
// grunt.registerTask('default', ['uglify']);
|
||||
grunt.loadNpmTasks("grunt-browserify");
|
||||
grunt.loadNpmTasks("grunt-banner");
|
||||
|
||||
grunt.loadNpmTasks('grunt-browserify')
|
||||
grunt.loadNpmTasks('grunt-banner');
|
||||
|
||||
|
||||
|
||||
grunt.registerTask('default', ['browserify:dev', 'browserify:chat_worker_dev'])
|
||||
grunt.registerTask('prod', ["browserify:prod", 'browserify:chat_worker_dev', "terser", 'usebanner'])
|
||||
|
||||
};
|
||||
grunt.registerTask("default", [
|
||||
"browserify:dev",
|
||||
"browserify:chat_worker_dev",
|
||||
"browserify:admin_bundle_dev",
|
||||
]);
|
||||
grunt.registerTask("prod", [
|
||||
"browserify:prod",
|
||||
"browserify:chat_worker_prod",
|
||||
"browserify:admin_bundle_prod",
|
||||
"terser",
|
||||
"usebanner",
|
||||
]);
|
||||
};
|
||||
|
10
Readme.md
10
Readme.md
@ -1,8 +1,16 @@
|
||||
# Chatto
|
||||
A self hosted chat application with end-to-end encrypted messaging.
|
||||
|
||||
![Chatto Preview](chatto-preview-main.png)
|
||||
|
||||
Documentation available [here](https://git.arcusiridis.com/nova/Chatto/wiki)
|
||||
|
||||
The backend is written in Java, and uses Spring Boot. The app itself provides a front end written with thymeleaf templates. The chat AJAX logic is written in TypeScript without any JS framework. The app exposes a JSON API that the inbuilt front end uses to provide chat functionality. However, this API can also be used by a separate client app to provide the same functions.
|
||||
|
||||
A WIP client made in ScalaFX is available [here](https://git.arcusiridis.com/nova/Chatto-Desktop-ScalaFX). It is a complete rewrite in Scala of the previous client, which was also written using Java and Spring Boot.
|
||||
A WIP client made in ScalaFX is available [here](https://git.arcusiridis.com/nova/Chatto-Desktop-ScalaFX). It is a complete rewrite in Scala of the previous client, which was also written using Java and Spring Boot.
|
||||
|
||||
## License
|
||||
|
||||
Chatto is [Free Software](https://www.gnu.org/philosophy/free-sw.en.html), licensed under AGPLv3.
|
||||
|
||||
![AGPLv3](agpl-v3-logo.png)
|
||||
|
BIN
agpl-v3-logo.png
Normal file
BIN
agpl-v3-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
BIN
chatto-preview-main.png
Normal file
BIN
chatto-preview-main.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 270 KiB |
@ -1,6 +1,9 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@types/bootbox": "^5.2.0",
|
||||
"@types/datatables.net": "^1.10.19",
|
||||
"@types/datatables.net-buttons": "^1.4.3",
|
||||
"@types/datatables.net-select": "^1.2.6",
|
||||
"@types/dompurify": "^2.0.0",
|
||||
"@types/jquery": "^3.3.31",
|
||||
"@types/markdown-it": "^0.0.9",
|
||||
@ -10,6 +13,7 @@
|
||||
"bootbox": "^5.4.0",
|
||||
"builder-pattern": "^1.2.3",
|
||||
"chart.js": "^2.9.3",
|
||||
"dedent-js": "^1.0.1",
|
||||
"dompurify": "^2.0.7",
|
||||
"fuse.js": "^3.4.6",
|
||||
"global": "^4.4.0",
|
||||
@ -30,6 +34,7 @@
|
||||
"grunt-banner": "^0.6.0",
|
||||
"grunt-browserify": "^5.3.0",
|
||||
"grunt-terser": "^1.0.0",
|
||||
"prettier": "2.0.5",
|
||||
"tsify": "^4.0.1",
|
||||
"typescript": "^3.7.4",
|
||||
"uglify-js": "^3.7.5"
|
||||
@ -56,6 +61,6 @@
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "watchify src/main/frontend/chat/main.ts -p [ tsify --target ES6 --noImplicitAny ] --debug -o src/main/resources/static/js/bundle.js",
|
||||
"watch-admin": "watchify src/main/frontend/admin/main.ts -p [ tsify --target ES6 --noImplicitAny ] --debug -o src/main/resources/static/js/adminBundle.js"
|
||||
"watch:admin": "watchify src/main/frontend/admin/main.ts -p [ tsify --target ES6 --noImplicitAny ] --debug -o src/main/resources/static/js/adminBundle.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
44
pom.xml
44
pom.xml
@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.8.RELEASE</version>
|
||||
<version>2.1.16.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
<groupId>org.ros</groupId>
|
||||
@ -103,6 +103,11 @@
|
||||
<artifactId>flyway-core</artifactId>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.spencerwi</groupId>
|
||||
<artifactId>Either.java</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
@ -192,6 +197,43 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>pl.project13.maven</groupId>
|
||||
<artifactId>git-commit-id-plugin</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>get-the-git-infos</id>
|
||||
<goals>
|
||||
<goal>revision</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>validate-the-git-infos</id>
|
||||
<goals>
|
||||
<goal>validateRevision</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<generateGitPropertiesFile>true</generateGitPropertiesFile>
|
||||
<excludeProperties>
|
||||
<excludeProperty>git.user.*</excludeProperty>
|
||||
</excludeProperties>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>net.revelc.code.formatter</groupId>
|
||||
<artifactId>formatter-maven-plugin</artifactId>
|
||||
<version>2.12.0</version>
|
||||
<configuration>
|
||||
<configFile>${project.basedir}/eclipse-formatter.xml</configFile>
|
||||
<compilerSource>11</compilerSource>
|
||||
<compilerCompliance>11</compilerCompliance>
|
||||
<compilerTargetPlatform>11</compilerTargetPlatform>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
@ -2,11 +2,26 @@ import { changePassphrase } from "./pages/user/ChangePassphrase";
|
||||
import { EncryptionServiceFactory } from "../common/service/EncryptionServiceFactory";
|
||||
import log from "loglevel";
|
||||
import { AlertifyNotificationService } from "../common/service/AlertifyNotificationService";
|
||||
import { stats } from "./pages/Home";
|
||||
import { Credentials } from "../common/global/Credentials";
|
||||
import { getOtherUsers } from "../common/ajax/Users";
|
||||
import { viewUsers } from "./pages/user/ViewUsers";
|
||||
|
||||
log.setLevel("TRACE");
|
||||
// log.setLevel("TRACE");
|
||||
const es = EncryptionServiceFactory.getEncryptionService();
|
||||
const ns = new AlertifyNotificationService();
|
||||
const authToken = Credentials.authToken;
|
||||
$("#changePassphraseForm").on("submit", (event) => {
|
||||
event.preventDefault();
|
||||
changePassphrase(es, ns);
|
||||
changePassphrase(es, ns, authToken);
|
||||
});
|
||||
|
||||
const pathMatcher = (path: string) => window.location.pathname == path;
|
||||
|
||||
if (pathMatcher("/admin")) {
|
||||
stats();
|
||||
}
|
||||
if (pathMatcher("/admin/users")) {
|
||||
// $("#usersTableBody").html("");
|
||||
viewUsers(authToken);
|
||||
}
|
||||
|
18
src/main/frontend/admin/pages/Home.ts
Normal file
18
src/main/frontend/admin/pages/Home.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { getStats } from "../../common/ajax/Messages";
|
||||
|
||||
import { Credentials } from "../../common/global/Credentials";
|
||||
import log from "loglevel";
|
||||
|
||||
export async function stats(): Promise<void> {
|
||||
const stats = await getStats(Credentials.authToken);
|
||||
setTimeout(() => {
|
||||
$(".admin-stats-spinner").hide();
|
||||
|
||||
$("#totalMessages").text(stats.totalMessages).removeAttr("hidden");
|
||||
$("#totalUsers").text(stats.totalUsers).removeAttr("hidden");
|
||||
$("#totalOnlineUsers").text(stats.totalOnlineUsers).removeAttr("hidden");
|
||||
$("#numMessagesToday").text(stats.numMessagesToday).removeAttr("hidden");
|
||||
}, 1000);
|
||||
|
||||
log.debug(stats);
|
||||
}
|
@ -7,15 +7,16 @@ import { Credentials } from "../../../common/global/Credentials";
|
||||
import { MessageCipher } from "../../../common/entity/MessageCipher";
|
||||
import log from "loglevel";
|
||||
import { NotificationService } from "../../../common/service/NotificationService";
|
||||
import { isPassphraseValid } from "../../../common/util/passphrase";
|
||||
import { isPassphraseValid } from "../../../common/util/Passphrase";
|
||||
import {
|
||||
getAllMessages,
|
||||
sendReencryptedMessages,
|
||||
} from "../../../common/ajax/messages";
|
||||
} from "../../../common/ajax/Messages";
|
||||
|
||||
export async function changePassphrase(
|
||||
es: EncryptionService,
|
||||
ns: NotificationService
|
||||
ns: NotificationService,
|
||||
authToken: string
|
||||
): Promise<void> {
|
||||
// $("#changePassphraseForm").val();
|
||||
|
||||
@ -26,7 +27,7 @@ export async function changePassphrase(
|
||||
const passphraseOld: string = $("#passphraseOld").val() as string;
|
||||
const passphraseNew: string = $("#passphraseNew").val() as string;
|
||||
|
||||
const valid = await isPassphraseValid(passphraseOld, user, es);
|
||||
const valid = await isPassphraseValid(passphraseOld, user, es, authToken);
|
||||
|
||||
if (!valid) {
|
||||
log.error("Please check your passphrase");
|
||||
|
75
src/main/frontend/admin/pages/user/ViewUsers.ts
Normal file
75
src/main/frontend/admin/pages/user/ViewUsers.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import moment from "moment";
|
||||
import { capitalize } from "../../../common/util/Util";
|
||||
import { AdminUserDTO } from "../../../common/dto/AdminUserDTO";
|
||||
import log from "loglevel";
|
||||
import { createApiHeaders } from "../../../common/ajax/util";
|
||||
|
||||
export async function viewUsers(authToken: string) {
|
||||
// const users = await getOtherUsers(authToken);
|
||||
|
||||
const usersTable = $("#usersTable").DataTable({
|
||||
ajax: {
|
||||
url: "/api/admin/get/users",
|
||||
headers: {
|
||||
"X-AUTH-TOKEN": authToken,
|
||||
},
|
||||
dataSrc: "",
|
||||
},
|
||||
columns: [
|
||||
{ data: "id" },
|
||||
{ data: "userName" },
|
||||
{
|
||||
data: "role",
|
||||
render: (data: string) => {
|
||||
return capitalize(data.replace("_", " ").toLowerCase());
|
||||
},
|
||||
},
|
||||
{
|
||||
data: "joinDate",
|
||||
render: (data: string, type) => {
|
||||
return type === "sort"
|
||||
? data
|
||||
: moment.utc(data).format("DD/MM/YY").toString();
|
||||
},
|
||||
},
|
||||
],
|
||||
buttons: [
|
||||
{
|
||||
extend: "selectedSingle",
|
||||
text: "User Profile",
|
||||
action: (e, dt, button, config) => {
|
||||
const username = (dt.row({ selected: true }).data() as AdminUserDTO)
|
||||
.userName;
|
||||
window.location.assign(`/admin/users/${username}`);
|
||||
},
|
||||
},
|
||||
{
|
||||
extend: "selectedSingle",
|
||||
text: "Delete User",
|
||||
action: (e, dt, button, config) => {
|
||||
const selectedRow = dt.row({ selected: true });
|
||||
const username = (selectedRow.data() as AdminUserDTO).userName;
|
||||
fetch(`/api/admin/delete/users/${username}`, {
|
||||
headers: createApiHeaders(authToken),
|
||||
method: "DELETE",
|
||||
}).then((resp) => {
|
||||
if (resp.status === 200) {
|
||||
selectedRow.remove();
|
||||
log.info("User deleted successfully");
|
||||
dt.draw();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "New User",
|
||||
action: () => {
|
||||
window.location.assign("/admin/users?mode=new");
|
||||
},
|
||||
},
|
||||
],
|
||||
dom: "Blfrtip",
|
||||
lengthMenu: [2, 10, 25, 50, 75, 100],
|
||||
select: true,
|
||||
});
|
||||
}
|
@ -4,27 +4,21 @@ import { ReencryptionDTO } from "../dto/ReencryptionDTO";
|
||||
import { ChatMessageDTO } from "../dto/ChatMessageDTO";
|
||||
import { JsonAPI } from "../../chat/singleton/JsonAPI";
|
||||
import * as log from "loglevel";
|
||||
import { StatsDTO } from "../dto/StatsDTO";
|
||||
import { createApiHeaders } from "./util";
|
||||
|
||||
export async function getAllMessages(user: string, authToken: string) {
|
||||
let headers = new Headers();
|
||||
// headers.append('Accept','application/json')
|
||||
// headers.append('Content-Type', 'application/json');
|
||||
headers.append("X-AUTH-TOKEN", authToken);
|
||||
let response = await fetch(`${Routes.Admin.getAllMessagesURL}${user}`, {
|
||||
let response = await fetch(`${Routes.Admin.getAllMessages}${user}`, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
headers: createApiHeaders(authToken),
|
||||
});
|
||||
return response.json() as Promise<ReencryptionDTO[]>;
|
||||
}
|
||||
|
||||
async function getAllRegularUsers(authToken: string) {
|
||||
let headers = new Headers();
|
||||
// headers.append('Accept','application/json')
|
||||
// headers.append('Content-Type', 'application/json');
|
||||
headers.append("X-AUTH-TOKEN", authToken);
|
||||
let response = await fetch(`${Routes.Admin.getAllRegularUsersURL}`, {
|
||||
let response = await fetch(`${Routes.Admin.getOtherUsers}`, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
headers: createApiHeaders(authToken),
|
||||
});
|
||||
let data = (await response.json()) as string[];
|
||||
return data;
|
||||
@ -40,7 +34,7 @@ export async function sendReencryptedMessages(
|
||||
// headers.append('Accept','application/json')
|
||||
headers.append("Content-Type", "application/json");
|
||||
headers.append("X-AUTH-TOKEN", authToken);
|
||||
fetch(Routes.Admin.reencryptURL, {
|
||||
fetch(Routes.Admin.reencrypt, {
|
||||
method: "POST",
|
||||
headers: headers,
|
||||
body: JSON.stringify(rencryptionDTOs),
|
||||
@ -49,19 +43,14 @@ export async function sendReencryptedMessages(
|
||||
|
||||
export async function getOneMessage(
|
||||
toUser: string,
|
||||
page: number
|
||||
page: number,
|
||||
authToken: string
|
||||
): Promise<ChatMessageDTO[]> {
|
||||
const headers = new Headers();
|
||||
if (JsonAPI.authToken == null) {
|
||||
log.error("authToken null");
|
||||
return [];
|
||||
}
|
||||
headers.append("X-AUTH-TOKEN", JsonAPI.authToken);
|
||||
const url = Sprintf(JsonAPI.CHAT_MESSAGE_PAGE_GET, toUser, page, 1);
|
||||
log.debug(url);
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: headers,
|
||||
headers: createApiHeaders(authToken),
|
||||
});
|
||||
log.debug(response.clone());
|
||||
// if (fetchErrorHandler(response.clone(), this._notificationService)) {
|
||||
@ -80,3 +69,11 @@ export async function getOneMessage(
|
||||
const data2 = func(data);
|
||||
return data2;
|
||||
}
|
||||
|
||||
export async function getStats(authToken: string) {
|
||||
const response = await fetch("/api/stats/", {
|
||||
headers: createApiHeaders(authToken),
|
||||
method: "GET",
|
||||
});
|
||||
return (await response.json()) as StatsDTO;
|
||||
}
|
15
src/main/frontend/common/ajax/Users.ts
Normal file
15
src/main/frontend/common/ajax/Users.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Routes } from "../routes/Routes";
|
||||
import { createApiHeaders } from "./util";
|
||||
import { AdminUserDTO } from "../dto/AdminUserDTO";
|
||||
|
||||
export async function getOtherUsers(
|
||||
authToken: string
|
||||
): Promise<AdminUserDTO[]> {
|
||||
const response = await fetch(Routes.Admin.getOtherUsers, {
|
||||
method: "GET",
|
||||
headers: createApiHeaders(authToken),
|
||||
});
|
||||
|
||||
let data = (await response.json()) as AdminUserDTO[];
|
||||
return data;
|
||||
}
|
9
src/main/frontend/common/ajax/util.ts
Normal file
9
src/main/frontend/common/ajax/util.ts
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
export function createApiHeaders(authToken: string): Headers {
|
||||
const headers = new Headers();
|
||||
// headers.append("Content-Type", "application/json");
|
||||
headers.append("X-AUTH-TOKEN", authToken);
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
6
src/main/frontend/common/dto/AdminUserDTO.ts
Normal file
6
src/main/frontend/common/dto/AdminUserDTO.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface AdminUserDTO {
|
||||
id: number;
|
||||
userName: string;
|
||||
role: string;
|
||||
joinDate: string;
|
||||
}
|
6
src/main/frontend/common/dto/StatsDTO.ts
Normal file
6
src/main/frontend/common/dto/StatsDTO.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface StatsDTO {
|
||||
totalMessages: number;
|
||||
totalOnlineUsers: number;
|
||||
totalUsers: number;
|
||||
numMessagesToday: number;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
export namespace Admin {
|
||||
export const getAllMessagesURL = `/api/admin/get/messages/`; //hostAddress set in thymeleaf backend
|
||||
export const reencryptURL = `/api/admin/post/re-encrypt`;
|
||||
export const getAllRegularUsersURL = `/api/admin/get/users`;
|
||||
export const getAllMessages = `/api/admin/get/messages/`; //hostAddress set in thymeleaf backend
|
||||
export const reencrypt = `/api/admin/post/re-encrypt`;
|
||||
export const getOtherUsers = `/api/admin/get/users`;
|
||||
}
|
||||
|
3
src/main/frontend/common/routes/Stats.ts
Normal file
3
src/main/frontend/common/routes/Stats.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export namespace Stats {
|
||||
export const rootStats = "/api/stats/"
|
||||
}
|
@ -4,9 +4,13 @@ import { SJCLEncryptionService } from "./SJCLEncryptionService";
|
||||
import PromiseWorker from "promise-worker";
|
||||
|
||||
export class EncryptionServiceFactory {
|
||||
private static readonly _worker = new Worker('/js/worker.js');
|
||||
private static readonly _promiseWorker = new PromiseWorker(EncryptionServiceFactory._worker);
|
||||
public static getEncryptionService(): EncryptionService {
|
||||
return new SJCLEncryptionService(this._promiseWorker)
|
||||
}
|
||||
}
|
||||
private static readonly _worker = new Worker(
|
||||
localStorage.getItem("CHAT_WORKER_BUNDLE") || "chat-worker-error.js"
|
||||
);
|
||||
private static readonly _promiseWorker = new PromiseWorker(
|
||||
EncryptionServiceFactory._worker
|
||||
);
|
||||
public static getEncryptionService(): EncryptionService {
|
||||
return new SJCLEncryptionService(this._promiseWorker);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { EncryptionService } from "../service/EncryptionService";
|
||||
import { ChatMessageDTO } from "../dto/ChatMessageDTO";
|
||||
import * as log from "loglevel";
|
||||
import { getOneMessage } from "../ajax/messages";
|
||||
import { getOneMessage } from "../ajax/Messages";
|
||||
|
||||
export async function isPassphraseValid(
|
||||
passphrase: string,
|
||||
userName: string,
|
||||
es: EncryptionService
|
||||
es: EncryptionService,
|
||||
authToken: string
|
||||
): Promise<boolean> {
|
||||
const messages: ChatMessageDTO[] = await getOneMessage(userName, 0);
|
||||
const messages: ChatMessageDTO[] = await getOneMessage(userName, 0, authToken);
|
||||
if (messages.length === 0) return true;
|
||||
try {
|
||||
es.decrypt(passphrase, messages[0].messageCipher);
|
16
src/main/frontend/common/util/Util.ts
Normal file
16
src/main/frontend/common/util/Util.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Capitalizes first letters of words in string.
|
||||
* @param {string} str String to be modified
|
||||
* @param {boolean=false} lower Whether all other letters should be lowercased
|
||||
* @return {string}
|
||||
* @usage
|
||||
* capitalize('fix this string'); // -> 'Fix This String'
|
||||
* capitalize('javaSCrIPT'); // -> 'JavaSCrIPT'
|
||||
* capitalize('javaSCrIPT', true); // -> 'Javascript'
|
||||
*/
|
||||
export function capitalize(str: string, lower = false): string {
|
||||
return (lower ? str.toLowerCase() : str).replace(
|
||||
/(?:^|\s|["'([{])+\S/g,
|
||||
(match) => match.toUpperCase()
|
||||
);
|
||||
}
|
@ -1,15 +1,34 @@
|
||||
{"properties": [{
|
||||
"name": "chatto.token.timeout-duration",
|
||||
"type": "java.lang.String",
|
||||
"description": "The duration for auth token validity. Token expires after this period of inactivity"
|
||||
},
|
||||
{
|
||||
"name": "chatto.frontend.log-level",
|
||||
"type": "java.lang.String",
|
||||
"description": "The log level for the frontend JS application"
|
||||
},
|
||||
{
|
||||
"name": "chatto.frontend.chat-page-size",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "The pagination size for the chat area"
|
||||
}]}
|
||||
"properties" : [
|
||||
{
|
||||
"name" : "chatto.token.timeout-duration",
|
||||
"type" : "java.lang.String",
|
||||
"description" : "The duration for auth token validity. Token expires after this period of inactivity"
|
||||
},
|
||||
{
|
||||
"name" : "chatto.frontend.log-level",
|
||||
"type" : "java.lang.String",
|
||||
"description" : "The log level for the frontend JS application"
|
||||
},
|
||||
{
|
||||
"name" : "chatto.frontend.chat-page-size",
|
||||
"type" : "java.lang.Integer",
|
||||
"description" : "The pagination size for the chat area"
|
||||
},
|
||||
{
|
||||
"name" : "chat-worker-bundle",
|
||||
"type" : "java.lang.String",
|
||||
"description" : "Name of the chat worker js bundle"
|
||||
},
|
||||
{
|
||||
"name" : "chat-bundle",
|
||||
"type" : "java.lang.String",
|
||||
"description" : "Name of the chatjs bundle"
|
||||
},
|
||||
{
|
||||
"name" : "admin-bundle",
|
||||
"type" : "java.lang.String",
|
||||
"description" : "Name of the admin js bundle"
|
||||
}
|
||||
]
|
||||
}
|
@ -2,6 +2,9 @@ package db.migration;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.security.SecureRandom;
|
||||
import java.sql.PreparedStatement;
|
||||
|
||||
@ -12,50 +15,75 @@ import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
public class V3__add_default_admin extends BaseJavaMigration {
|
||||
|
||||
private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
|
||||
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
private final SecureRandom random = new SecureRandom();
|
||||
|
||||
/** different dictionaries used */
|
||||
private final String ALPHA_CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
private final String ALPHA = "abcdefghijklmnopqrstuvwxyz";
|
||||
private final String NUMERIC = "0123456789";
|
||||
private final String SPECIAL_CHARS = "!@#$%^&*_=+-/";
|
||||
/** different dictionaries used */
|
||||
private final String ALPHA_CAPS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
private final String ALPHA = "abcdefghijklmnopqrstuvwxyz";
|
||||
private final String NUMERIC = "0123456789";
|
||||
private final String SPECIAL_CHARS = "!@#$%^&*_=+-/";
|
||||
|
||||
/**
|
||||
* Method will generate random string based on the parameters
|
||||
*
|
||||
* @param len the length of the random string
|
||||
* @param dic the dictionary used to generate the password
|
||||
* @return the random password
|
||||
*/
|
||||
public String generatePassword(int len, String dic) {
|
||||
String result = "";
|
||||
for (int i = 0; i < len; i++) {
|
||||
int index = random.nextInt(dic.length());
|
||||
result += dic.charAt(index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Method will generate random string based on the parameters
|
||||
*
|
||||
* @param len
|
||||
* the length of the random string
|
||||
* @param dic
|
||||
* the dictionary used to generate the password
|
||||
* @return the random password
|
||||
*/
|
||||
public String generatePassword(int len, String dic) {
|
||||
String result = "";
|
||||
for (int i = 0; i < len; i++) {
|
||||
int index = random.nextInt(dic.length());
|
||||
result += dic.charAt(index);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate(final Context context) throws Exception {
|
||||
try (final PreparedStatement ps = context.getConnection()
|
||||
.prepareStatement("insert into users (user_id, name, password) values (0,?,?)")) {
|
||||
final String generatedPassword = generatePassword(60, ALPHA_CAPS + ALPHA + SPECIAL_CHARS);
|
||||
final BufferedWriter bw = new BufferedWriter(new FileWriter("gen-password.txt"));
|
||||
bw.write(generatedPassword);
|
||||
bw.write("\nPlease delete this file");
|
||||
bw.close();
|
||||
ps.setString(1, "admin");
|
||||
ps.setString(2, passwordEncoder.encode(generatedPassword));
|
||||
ps.execute();
|
||||
}
|
||||
@Override
|
||||
public void migrate(final Context context) throws Exception {
|
||||
try (final PreparedStatement ps = context.getConnection()
|
||||
.prepareStatement(
|
||||
"insert into users (user_id, name, password) values (0,?,?)")) {
|
||||
final String generatedPassword = generatePassword(60,
|
||||
ALPHA_CAPS + ALPHA + SPECIAL_CHARS);
|
||||
final BufferedWriter bw = new BufferedWriter(
|
||||
new FileWriter("gen-password.txt"));
|
||||
|
||||
try (final PreparedStatement ps = context.getConnection()
|
||||
.prepareStatement("insert into users_roles (user_id, role_id) values (1,0)")) {
|
||||
ps.execute();
|
||||
}
|
||||
}
|
||||
bw.write(generatedPassword);
|
||||
bw.write("\nPlease delete this file");
|
||||
bw.close();
|
||||
|
||||
final var perms = Files
|
||||
.getPosixFilePermissions(Paths.get("gen-password.txt"));
|
||||
|
||||
// add owners permission
|
||||
perms.add(PosixFilePermission.OWNER_READ);
|
||||
perms.add(PosixFilePermission.OWNER_WRITE);
|
||||
perms.remove(PosixFilePermission.OWNER_EXECUTE);
|
||||
// add group permissions
|
||||
perms.remove(PosixFilePermission.GROUP_READ);
|
||||
perms.remove(PosixFilePermission.GROUP_WRITE);
|
||||
perms.remove(PosixFilePermission.GROUP_EXECUTE);
|
||||
// add others permissions
|
||||
perms.remove(PosixFilePermission.OTHERS_READ);
|
||||
perms.remove(PosixFilePermission.OTHERS_WRITE);
|
||||
perms.remove(PosixFilePermission.OTHERS_EXECUTE);
|
||||
Files.setPosixFilePermissions(Paths.get("gen-password.txt"), perms);
|
||||
|
||||
ps.setString(1, "admin");
|
||||
ps.setString(2, passwordEncoder.encode(generatedPassword));
|
||||
ps.execute();
|
||||
}
|
||||
|
||||
try (final PreparedStatement ps = context.getConnection()
|
||||
.prepareStatement(
|
||||
"insert into users_roles (user_id, role_id) values (1,0)")) {
|
||||
ps.execute();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -28,7 +28,8 @@ public class BeanConfigurations {
|
||||
@Bean
|
||||
public MessageSource messageSource() {
|
||||
final ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
||||
messageSource.setBasenames("classpath:/messages,file:./config/messages");
|
||||
messageSource
|
||||
.setBasenames("classpath:/messages,file:./config/messages");
|
||||
messageSource.setUseCodeAsDefaultMessage(true);
|
||||
messageSource.setDefaultEncoding("UTF-8");
|
||||
messageSource.setCacheSeconds(5);
|
||||
@ -45,10 +46,4 @@ public class BeanConfigurations {
|
||||
return keyBasedPersistenceTokenService;
|
||||
}
|
||||
|
||||
// @Bean
|
||||
// public Connection connection() throws SQLException
|
||||
// {
|
||||
// return DriverManager.getConnection(url, userName, password);
|
||||
// }
|
||||
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ public class ChattoApplication extends SpringBootServletInitializer {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Profile("development")
|
||||
@ComponentScan(lazyInit = true)
|
||||
static class LocalConfig {
|
||||
}
|
||||
@Profile("development")
|
||||
@ComponentScan(lazyInit = true)
|
||||
static class LocalConfig {
|
||||
}
|
||||
}
|
@ -12,19 +12,21 @@ import org.springframework.security.web.authentication.www.BasicAuthenticationEn
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public final class RESTAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
|
||||
public final class RESTAuthenticationEntryPoint
|
||||
extends BasicAuthenticationEntryPoint {
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
|
||||
throws IOException, ServletException {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.println("HTTP ApplicationStatus 401 - " + authEx.getMessage());
|
||||
}
|
||||
@Override
|
||||
public void commence(HttpServletRequest request,
|
||||
HttpServletResponse response, AuthenticationException authEx)
|
||||
throws IOException, ServletException {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.println("HTTP ApplicationStatus 401 - " + authEx.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
setRealmName("Chatto");
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
setRealmName("Chatto");
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,8 @@ import org.springframework.boot.web.servlet.support.SpringBootServletInitializer
|
||||
public class ServletInitializer extends SpringBootServletInitializer {
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
|
||||
protected SpringApplicationBuilder configure(
|
||||
SpringApplicationBuilder application) {
|
||||
return application.sources(ChattoApplication.class);
|
||||
}
|
||||
|
||||
|
@ -5,16 +5,14 @@ import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
//@EnableWebMvc
|
||||
// @EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOrigins("*")
|
||||
.allowedMethods("POST","GET","OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(false).maxAge(3600);
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/api/**").allowedOrigins("*")
|
||||
.allowedMethods("POST", "GET", "OPTIONS").allowedHeaders("*")
|
||||
.allowCredentials(false).maxAge(3600);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -20,14 +20,17 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Autowired
|
||||
private MyUserDetailsService myUserDetailsService;
|
||||
private final MyUserDetailsService myUserDetailsService;
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
@ -37,7 +40,7 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Bean
|
||||
public AuthenticationProvider authenticationProvider() {
|
||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||
final DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||
provider.setUserDetailsService(myUserDetailsService);
|
||||
provider.setPasswordEncoder(passwordEncoder);
|
||||
return provider;
|
||||
@ -50,80 +53,76 @@ public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Configuration
|
||||
@Order(1)
|
||||
@RequiredArgsConstructor
|
||||
public static class ApiWebSecurity extends WebSecurityConfigurerAdapter {
|
||||
@Autowired
|
||||
private RESTAuthenticationEntryPoint authenticationEntryPoint;
|
||||
|
||||
@Autowired
|
||||
private CustomBasicAuthenticationFilter customBasicAuthFilter;
|
||||
private final RESTAuthenticationEntryPoint authenticationEntryPoint;
|
||||
|
||||
@Autowired
|
||||
private TokenAuthenticationFilter tokenFilter;
|
||||
private final CustomBasicAuthenticationFilter customBasicAuthFilter;
|
||||
|
||||
@Autowired
|
||||
private final TokenAuthenticationFilter tokenFilter;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
protected void configure(final HttpSecurity http) throws Exception {
|
||||
http.csrf().disable().exceptionHandling()
|
||||
|
||||
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||
// .cors().and()
|
||||
|
||||
.and().sessionManagement()
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
// .cors().and()
|
||||
.antMatcher("/api/**").authorizeRequests()
|
||||
.antMatchers("/api/chat/**").hasAnyRole("USER", "ADMIN", "SUPER_USER")
|
||||
.antMatchers("/api/chat/**")
|
||||
.hasAnyRole("USER", "ADMIN", "SUPER_USER")
|
||||
.antMatchers("/api/admin/**")
|
||||
.hasAnyRole("ADMIN", "SUPER_USER")
|
||||
.antMatchers("/api/demo/**").hasRole("SUPER_USER")
|
||||
// .antMatchers("/perform-login").permitAll()
|
||||
|
||||
.anyRequest()
|
||||
// .hasAnyRole("USER", "ADMIN", "SUPER_USER")
|
||||
.authenticated()
|
||||
.and().httpBasic().authenticationEntryPoint(authenticationEntryPoint)
|
||||
;
|
||||
|
||||
http.addFilterBefore(tokenFilter, BasicAuthenticationFilter.class);
|
||||
|
||||
// Creating token when basic authentication is successful and the same token can
|
||||
// be used to authenticate for further requests
|
||||
// final CustomBasicAuthenticationFilter customBasicAuthFilter = new CustomBasicAuthenticationFilter(
|
||||
// authenticationManagerBean());
|
||||
http.addFilter(customBasicAuthFilter);
|
||||
|
||||
.anyRequest().authenticated().and().httpBasic()
|
||||
.authenticationEntryPoint(authenticationEntryPoint).and()
|
||||
.addFilterBefore(tokenFilter,
|
||||
BasicAuthenticationFilter.class)
|
||||
// Creating token when basic authentication is successful
|
||||
// and the
|
||||
// same token can
|
||||
// be used to authenticate for further requests
|
||||
.addFilter(customBasicAuthFilter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@Order(2)
|
||||
@RequiredArgsConstructor
|
||||
public static class FormWebSecurity extends WebSecurityConfigurerAdapter {
|
||||
@Autowired
|
||||
private UserSessionLoggingLoginSuccessHandler loginSuccessHandler;
|
||||
private final UserSessionLoggingLoginSuccessHandler loginSuccessHandler;
|
||||
|
||||
@Autowired
|
||||
private UserSessionLoggingLogoutSuccessHandler logoutSuccessHandler;
|
||||
private final UserSessionLoggingLogoutSuccessHandler logoutSuccessHandler;
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
||||
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS).and()
|
||||
protected void configure(final HttpSecurity httpSecurity)
|
||||
throws Exception {
|
||||
httpSecurity.sessionManagement()
|
||||
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS).and()
|
||||
|
||||
.authorizeRequests()
|
||||
// .antMatchers(HttpMethod.POST, "/api/**").permitAll()
|
||||
.antMatchers("/", "perform_login", "/logout**", "/favicon.ico", "/login*", "/registration",
|
||||
"/perform_registration", "/css/**", "/js/**", "/img/**")
|
||||
.permitAll()
|
||||
// .antMatchers("/","/api**","/api/**","/login*","/registration","/perform_registration","/css/**", "/js/**", "/images/**").permitAll()
|
||||
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN", "SUPER_USER").antMatchers("/admin/**")
|
||||
.hasAnyRole("ADMIN", "SUPER_USER")
|
||||
// .and()
|
||||
// .antMatcher("/api/**")
|
||||
// .authorizeRequests()
|
||||
.antMatchers("/", "perform_login", "/logout**",
|
||||
"/favicon.ico", "/login*", "/registration",
|
||||
"/perform_registration", "/css/**", "/js/**",
|
||||
"/img/**")
|
||||
.permitAll().antMatchers("/user/**")
|
||||
.hasAnyRole("USER", "ADMIN", "SUPER_USER")
|
||||
.antMatchers("/admin/**").hasAnyRole("ADMIN", "SUPER_USER")
|
||||
.anyRequest().authenticated()
|
||||
|
||||
.and()
|
||||
|
||||
.formLogin().loginPage("/login").permitAll().loginProcessingUrl("/perform_login")
|
||||
.formLogin().loginPage("/login").permitAll()
|
||||
.loginProcessingUrl("/perform_login")
|
||||
.successHandler(loginSuccessHandler).and().logout()
|
||||
.logoutSuccessHandler(logoutSuccessHandler)
|
||||
;
|
||||
.logoutSuccessHandler(logoutSuccessHandler);
|
||||
}
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,10 @@ import java.awt.image.BufferedImage;
|
||||
|
||||
interface CaptchaBehaviour {
|
||||
public BufferedImage generateCaptcha();
|
||||
|
||||
public BufferedImage generateCaptcha(String captchaText);
|
||||
|
||||
public String getRandomChars(int size);
|
||||
|
||||
public String getRandomChars();
|
||||
}
|
||||
|
@ -6,9 +6,10 @@ import lombok.Builder;
|
||||
|
||||
/*Class for providing your own captcha generator*/
|
||||
@Builder
|
||||
public class ManualCaptchaBehaviour implements CaptchaBehaviour{
|
||||
public class ManualCaptchaBehaviour implements CaptchaBehaviour {
|
||||
private final int length;
|
||||
private final String style;
|
||||
|
||||
@Override
|
||||
public BufferedImage generateCaptcha() {
|
||||
// TODO Auto-generated method stub
|
||||
@ -33,5 +34,4 @@ public class ManualCaptchaBehaviour implements CaptchaBehaviour{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -10,174 +10,183 @@ import java.io.IOException;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* This class represents a simple captcha consisting
|
||||
* of an image {@code png} and its text value.
|
||||
* Comic Neue Bold Font.
|
||||
* Capital english letters {@code ONLY}.
|
||||
* This class represents a simple captcha consisting of an image {@code png} and
|
||||
* its text value. Comic Neue Bold Font. Capital english letters {@code ONLY}.
|
||||
*
|
||||
* @since 1.3
|
||||
* @author Gennadiy Golovin
|
||||
*/
|
||||
public final class SimpleCaptcha {
|
||||
|
||||
private BufferedImage imagePng;
|
||||
private char[] text;
|
||||
private BufferedImage imagePng;
|
||||
private char[] text;
|
||||
|
||||
/**
|
||||
* Initializes a newly created default object
|
||||
* consisting of 8 capital english letters.
|
||||
*/
|
||||
public SimpleCaptcha() {
|
||||
this.text = getRandomChars();
|
||||
/**
|
||||
* Initializes a newly created default object consisting of 8 capital
|
||||
* english letters.
|
||||
*/
|
||||
public SimpleCaptcha() {
|
||||
this.text = getRandomChars();
|
||||
|
||||
try {
|
||||
generateCaptcha();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
try {
|
||||
generateCaptcha();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a newly created object, which length
|
||||
* depends on the passed {@code int} parameter,
|
||||
* which {@code MUST} be greater than 0.
|
||||
* If the condition is not met, initializes a newly
|
||||
* created default object consisting of 8 symbols.
|
||||
*
|
||||
* @param length the quantity of symbols, that the
|
||||
* captcha consists of, greater than 0.
|
||||
*/
|
||||
public SimpleCaptcha(int length) {
|
||||
if (length < 1) {
|
||||
this.text = getRandomChars();
|
||||
} else {
|
||||
this.text = getRandomChars(length);
|
||||
}
|
||||
/**
|
||||
* Initializes a newly created object, which length depends on the passed
|
||||
* {@code int} parameter, which {@code MUST} be greater than 0. If the
|
||||
* condition is not met, initializes a newly created default object
|
||||
* consisting of 8 symbols.
|
||||
*
|
||||
* @param length
|
||||
* the quantity of symbols, that the captcha consists of, greater
|
||||
* than 0.
|
||||
*/
|
||||
public SimpleCaptcha(int length) {
|
||||
if (length < 1) {
|
||||
this.text = getRandomChars();
|
||||
} else {
|
||||
this.text = getRandomChars(length);
|
||||
}
|
||||
|
||||
try {
|
||||
generateCaptcha();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
try {
|
||||
generateCaptcha();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a newly created object based on the passed
|
||||
* {@link String} parameter, consisting of capital english
|
||||
* letters. If the condition is not met, initializes a newly
|
||||
* created default object consisting of 8 capital english letters.
|
||||
*
|
||||
* @param text the text string with the value of the captcha,
|
||||
* length greater than 0.
|
||||
*/
|
||||
public SimpleCaptcha(String text) {
|
||||
if (text == null || text.equals("")) {
|
||||
this.text = getRandomChars();
|
||||
} else {
|
||||
this.text = text.toCharArray();
|
||||
}
|
||||
/**
|
||||
* Initializes a newly created object based on the passed {@link String}
|
||||
* parameter, consisting of capital english letters. If the condition is not
|
||||
* met, initializes a newly created default object consisting of 8 capital
|
||||
* english letters.
|
||||
*
|
||||
* @param text
|
||||
* the text string with the value of the captcha, length greater
|
||||
* than 0.
|
||||
*/
|
||||
public SimpleCaptcha(String text) {
|
||||
if (text == null || text.equals("")) {
|
||||
this.text = getRandomChars();
|
||||
} else {
|
||||
this.text = text.toCharArray();
|
||||
}
|
||||
|
||||
try {
|
||||
generateCaptcha();
|
||||
} catch (IOException e) {
|
||||
this.text = getRandomChars();
|
||||
try {
|
||||
generateCaptcha();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
generateCaptcha();
|
||||
} catch (IOException e) {
|
||||
this.text = getRandomChars();
|
||||
try {
|
||||
generateCaptcha();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the picture with captcha
|
||||
*
|
||||
* @return {@link BufferedImage}
|
||||
*/
|
||||
public BufferedImage getImagePng() {
|
||||
return imagePng;
|
||||
}
|
||||
/**
|
||||
* Returns the picture with captcha
|
||||
*
|
||||
* @return {@link BufferedImage}
|
||||
*/
|
||||
public BufferedImage getImagePng() {
|
||||
return imagePng;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text value of the captcha
|
||||
*
|
||||
* @return {@link String}
|
||||
*/
|
||||
public String getText() {
|
||||
return String.valueOf(text);
|
||||
}
|
||||
/**
|
||||
* Returns the text value of the captcha
|
||||
*
|
||||
* @return {@link String}
|
||||
*/
|
||||
public String getText() {
|
||||
return String.valueOf(text);
|
||||
}
|
||||
|
||||
//////// //////// //////// //////// //////// //////// //////// ////////
|
||||
//////// //////// //////// //////// //////// //////// //////// ////////
|
||||
|
||||
private char[] getRandomChars() {
|
||||
return getRandomChars(8);
|
||||
}
|
||||
private char[] getRandomChars() {
|
||||
return getRandomChars(8);
|
||||
}
|
||||
|
||||
private char[] getRandomChars(int quantity) {
|
||||
private char[] getRandomChars(int quantity) {
|
||||
|
||||
char[] randomString = new char[quantity];
|
||||
char[] randomString = new char[quantity];
|
||||
|
||||
Random random = new Random();
|
||||
Random random = new Random();
|
||||
|
||||
int capitalLetter;
|
||||
int capitalLetter;
|
||||
|
||||
for (int i = 0; i < quantity; i++) {
|
||||
capitalLetter = 65 + random.nextInt(26);
|
||||
randomString[i] = (char) capitalLetter;
|
||||
}
|
||||
for (int i = 0; i < quantity; i++) {
|
||||
capitalLetter = 65 + random.nextInt(26);
|
||||
randomString[i] = (char) capitalLetter;
|
||||
}
|
||||
|
||||
return randomString;
|
||||
}
|
||||
return randomString;
|
||||
}
|
||||
|
||||
private void generateCaptcha() throws IOException {
|
||||
int charsQuantity = this.text.length;
|
||||
BufferedImage[] images = new BufferedImage[charsQuantity];
|
||||
private void generateCaptcha() throws IOException {
|
||||
int charsQuantity = this.text.length;
|
||||
BufferedImage[] images = new BufferedImage[charsQuantity];
|
||||
|
||||
for (int i = 0; i < charsQuantity; i++) {
|
||||
images[i] = ImageIO.read(SimpleCaptcha.class.getResourceAsStream("/pictures/" + this.text[i] + ".png"));
|
||||
if (i % 2 == 0) {
|
||||
images[i] = rotateImage(images[i], 25);
|
||||
} else {
|
||||
images[i] = rotateImage(images[i], -20);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < charsQuantity; i++) {
|
||||
images[i] = ImageIO.read(SimpleCaptcha.class
|
||||
.getResourceAsStream("/pictures/" + this.text[i] + ".png"));
|
||||
if (i % 2 == 0) {
|
||||
images[i] = rotateImage(images[i], 25);
|
||||
} else {
|
||||
images[i] = rotateImage(images[i], -20);
|
||||
}
|
||||
}
|
||||
|
||||
int imageSize = 30;
|
||||
int rotatedImageSize = (int) Math.sqrt(imageSize * imageSize * 2);
|
||||
int imageSize = 30;
|
||||
int rotatedImageSize = (int) Math.sqrt(imageSize * imageSize * 2);
|
||||
|
||||
BufferedImage captchaImg = new BufferedImage(rotatedImageSize * (charsQuantity - 1) / 10 * 6 + rotatedImageSize, rotatedImageSize, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics2d = captchaImg.createGraphics();
|
||||
graphics2d.setBackground(Color.WHITE);
|
||||
graphics2d.clearRect(0, 0, captchaImg.getWidth(), captchaImg.getHeight());
|
||||
for (int i = 0; i < charsQuantity; i++) {
|
||||
captchaImg.getGraphics().drawImage(images[i], rotatedImageSize * i / 10 * 6, 0, null);
|
||||
}
|
||||
graphics2d.dispose();
|
||||
this.imagePng = captchaImg;
|
||||
}
|
||||
BufferedImage captchaImg = new BufferedImage(
|
||||
rotatedImageSize * (charsQuantity - 1) / 10 * 6
|
||||
+ rotatedImageSize,
|
||||
rotatedImageSize, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D graphics2d = captchaImg.createGraphics();
|
||||
graphics2d.setBackground(Color.WHITE);
|
||||
graphics2d.clearRect(0, 0, captchaImg.getWidth(),
|
||||
captchaImg.getHeight());
|
||||
for (int i = 0; i < charsQuantity; i++) {
|
||||
captchaImg.getGraphics().drawImage(images[i],
|
||||
rotatedImageSize * i / 10 * 6, 0, null);
|
||||
}
|
||||
graphics2d.dispose();
|
||||
this.imagePng = captchaImg;
|
||||
}
|
||||
|
||||
private BufferedImage rotateImage(BufferedImage buffImage, double angle) {
|
||||
private BufferedImage rotateImage(BufferedImage buffImage, double angle) {
|
||||
|
||||
double radian = Math.toRadians(angle);
|
||||
double sin = Math.abs(Math.sin(radian));
|
||||
double cos = Math.abs(Math.cos(radian));
|
||||
double radian = Math.toRadians(angle);
|
||||
double sin = Math.abs(Math.sin(radian));
|
||||
double cos = Math.abs(Math.cos(radian));
|
||||
|
||||
int width = buffImage.getWidth();
|
||||
int height = buffImage.getHeight();
|
||||
int width = buffImage.getWidth();
|
||||
int height = buffImage.getHeight();
|
||||
|
||||
int nWidth = (int) Math.floor((double) width * cos + (double) height * sin);
|
||||
int nHeight = (int) Math.floor((double) height * cos + (double) width * sin);
|
||||
int nWidth = (int) Math
|
||||
.floor((double) width * cos + (double) height * sin);
|
||||
int nHeight = (int) Math
|
||||
.floor((double) height * cos + (double) width * sin);
|
||||
|
||||
BufferedImage rotatedImage = new BufferedImage(nWidth, nHeight, BufferedImage.TYPE_INT_ARGB);
|
||||
BufferedImage rotatedImage = new BufferedImage(nWidth, nHeight,
|
||||
BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
Graphics2D graphics = rotatedImage.createGraphics();
|
||||
Graphics2D graphics = rotatedImage.createGraphics();
|
||||
|
||||
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
graphics.translate((nWidth - width) / 2, (nHeight - height) / 2);
|
||||
graphics.rotate(radian, (double) (width / 2), (double) (height / 2));
|
||||
graphics.drawImage(buffImage, 0, 0,null);
|
||||
graphics.dispose();
|
||||
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
graphics.translate((nWidth - width) / 2, (nHeight - height) / 2);
|
||||
graphics.rotate(radian, (double) (width / 2), (double) (height / 2));
|
||||
graphics.drawImage(buffImage, 0, 0, null);
|
||||
graphics.dispose();
|
||||
|
||||
return rotatedImage;
|
||||
}
|
||||
return rotatedImage;
|
||||
}
|
||||
}
|
@ -9,30 +9,30 @@ public class SimpleCaptchaBehavior implements CaptchaBehaviour {
|
||||
SimpleCaptcha simpleCaptcha = new SimpleCaptcha();
|
||||
return simpleCaptcha.getImagePng();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage generateCaptcha(String captchaText) {
|
||||
SimpleCaptcha simpleCaptcha = new SimpleCaptcha(captchaText);
|
||||
return simpleCaptcha.getImagePng();
|
||||
}
|
||||
|
||||
|
||||
public String getRandomChars() {
|
||||
return getRandomChars(8);
|
||||
}
|
||||
|
||||
public String getRandomChars(int quantity)
|
||||
{
|
||||
return getRandomChars(8);
|
||||
}
|
||||
|
||||
public String getRandomChars(int quantity) {
|
||||
char[] randomString = new char[quantity];
|
||||
|
||||
Random random = new Random();
|
||||
Random random = new Random();
|
||||
|
||||
int capitalLetter;
|
||||
int capitalLetter;
|
||||
|
||||
for (int i = 0; i < quantity; i++) {
|
||||
capitalLetter = 65 + random.nextInt(26);
|
||||
randomString[i] = (char) capitalLetter;
|
||||
}
|
||||
for (int i = 0; i < quantity; i++) {
|
||||
capitalLetter = 65 + random.nextInt(26);
|
||||
randomString[i] = (char) capitalLetter;
|
||||
}
|
||||
|
||||
return new String(randomString);
|
||||
return new String(randomString);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,34 +7,35 @@ import lombok.Builder;
|
||||
@Builder
|
||||
public class WebCaptcha {
|
||||
private final CaptchaBehaviour captchaBehaviour;
|
||||
|
||||
|
||||
public BufferedImage generateCaptcha() {
|
||||
return captchaBehaviour.generateCaptcha();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public BufferedImage generateCaptcha(String captchaText) {
|
||||
return captchaBehaviour.generateCaptcha(captchaText);
|
||||
}
|
||||
|
||||
public String getRandomChars() {
|
||||
return captchaBehaviour.getRandomChars();
|
||||
}
|
||||
}
|
||||
|
||||
public String getRandomChars(int quantity) {
|
||||
return captchaBehaviour.getRandomChars(quantity);
|
||||
}
|
||||
public String getRandomChars() {
|
||||
return captchaBehaviour.getRandomChars();
|
||||
}
|
||||
|
||||
public String getRandomChars(int quantity) {
|
||||
return captchaBehaviour.getRandomChars(quantity);
|
||||
}
|
||||
}
|
||||
|
||||
// WebCaptcha webCaptcha = WebCaptcha.builder().captchaBehaviour(new SimpleCaptchaBehavior()).build();
|
||||
// webCaptcha.generateCaptcha();
|
||||
// WebCaptcha webCaptcha = WebCaptcha.builder().captchaBehaviour(new
|
||||
// SimpleCaptchaBehavior()).build();
|
||||
// webCaptcha.generateCaptcha();
|
||||
//
|
||||
// // @formatter:off
|
||||
// webCaptcha = WebCaptcha.builder()
|
||||
// .captchaBehaviour(
|
||||
// ManualCaptchaBehaviour.builder()
|
||||
// .length(8)
|
||||
// .style("black")
|
||||
// .build()
|
||||
// ).build();
|
||||
//
|
||||
// // @formatter:on
|
||||
// // @formatter:off
|
||||
// webCaptcha = WebCaptcha.builder()
|
||||
// .captchaBehaviour(
|
||||
// ManualCaptchaBehaviour.builder()
|
||||
// .length(8)
|
||||
// .style("black")
|
||||
// .build()
|
||||
// ).build();
|
||||
//
|
||||
// // @formatter:on
|
||||
|
22
src/main/java/org/ros/chatto/config/BuildInfo.java
Normal file
22
src/main/java/org/ros/chatto/config/BuildInfo.java
Normal file
@ -0,0 +1,22 @@
|
||||
package org.ros.chatto.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
@Component
|
||||
@PropertySource(value = "classpath:git.properties")
|
||||
@Getter
|
||||
public class BuildInfo {
|
||||
private final String buildVersion;
|
||||
private final String branchName;
|
||||
|
||||
public BuildInfo(@Value("${git.build.version") String buildVersion,
|
||||
@Value("${git.branch") String branchName) {
|
||||
this.buildVersion = buildVersion;
|
||||
this.branchName = branchName;
|
||||
}
|
||||
|
||||
}
|
@ -6,11 +6,14 @@ import org.ehcache.event.CacheEventListener;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class CustomCacheEventLogger implements CacheEventListener<Object, Object> {
|
||||
public class CustomCacheEventLogger
|
||||
implements CacheEventListener<Object, Object> {
|
||||
|
||||
@Override
|
||||
public void onEvent(CacheEvent<? extends Object, ? extends Object> cacheEvent) {
|
||||
log.debug("custom Caching event {} key = {} old {} new {} ", cacheEvent.getType(), cacheEvent.getKey(),
|
||||
public void onEvent(
|
||||
CacheEvent<? extends Object, ? extends Object> cacheEvent) {
|
||||
log.debug("custom Caching event {} key = {} old {} new {} ",
|
||||
cacheEvent.getType(), cacheEvent.getKey(),
|
||||
cacheEvent.getOldValue(), cacheEvent.getNewValue());
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.ros.chatto.config;
|
||||
|
||||
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
|
@ -1,16 +1,25 @@
|
||||
package org.ros.chatto.controller;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.ros.chatto.model.ChatUser;
|
||||
import org.ros.chatto.service.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/admin")
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class AdminController {
|
||||
|
||||
@ -27,4 +36,25 @@ public class AdminController {
|
||||
model.addAttribute("userNames", userService.getAllRegularUsers());
|
||||
return "admin/change-passphrase";
|
||||
}
|
||||
|
||||
@GetMapping("/users")
|
||||
public String usersPage(@RequestParam Optional<String> mode) {
|
||||
if (mode.isPresent()) {
|
||||
switch (mode.get()) {
|
||||
case "new":
|
||||
return "admin/new-user";
|
||||
}
|
||||
}
|
||||
return "admin/users";
|
||||
}
|
||||
|
||||
@GetMapping("/users/{userName}")
|
||||
public String userProfile(@PathVariable String userName) {
|
||||
Optional<ChatUser> maybeUser = userService.getUser(userName);
|
||||
if (!maybeUser.isPresent()) {
|
||||
throw new ResponseStatusException(HttpStatus.NOT_FOUND,
|
||||
"Requested user does not exist");
|
||||
}
|
||||
return "admin/user-profile";
|
||||
}
|
||||
}
|
||||
|
@ -2,33 +2,42 @@ package org.ros.chatto.controller;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
import com.spencerwi.either.Result;
|
||||
|
||||
import org.ros.chatto.dto.AdminUserDTO;
|
||||
import org.ros.chatto.dto.ChatMessageDTO;
|
||||
import org.ros.chatto.dto.ReencryptionDTO;
|
||||
import org.ros.chatto.service.AdminService;
|
||||
import org.ros.chatto.service.ChatService;
|
||||
import org.ros.chatto.service.UserDTOSpec;
|
||||
import org.ros.chatto.service.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/admin")
|
||||
@RequiredArgsConstructor
|
||||
public class AdminRESTController {
|
||||
@Autowired
|
||||
private ChatService chatService;
|
||||
private final ChatService chatService;
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
private final UserService userService;
|
||||
|
||||
private final AdminService adminService;
|
||||
|
||||
@PostMapping(value = "/post/re-encrypt", consumes = { "application/json" })
|
||||
public ResponseEntity<ReencryptionDTO> reencryptMessages(
|
||||
@ -56,13 +65,33 @@ public class AdminRESTController {
|
||||
return chatMessageDTOs;
|
||||
}
|
||||
|
||||
@GetMapping("/regular-users")
|
||||
public List<String> getAllRegularUsers() {
|
||||
return userService.getAllRegularUsers();
|
||||
@GetMapping("/get/users")
|
||||
public List<AdminUserDTO> getUsers(Principal principal,
|
||||
@RequestParam Optional<String> select) {
|
||||
if (select.isPresent()) {
|
||||
switch (select.get()) {
|
||||
case "all":
|
||||
return adminService.getUsers("", UserDTOSpec.ALL_USERS);
|
||||
|
||||
case "regular":
|
||||
return adminService.getUsers("", UserDTOSpec.REGULAR_USERS);
|
||||
|
||||
}
|
||||
}
|
||||
return adminService.getUsers(principal.getName(),
|
||||
UserDTOSpec.OTHER_USERS);
|
||||
}
|
||||
|
||||
@GetMapping("/get/users")
|
||||
public List<String> getAllOtherUsers(Principal principal) {
|
||||
return userService.findAllOtherUsers(principal.getName());
|
||||
@DeleteMapping("/delete/users/{userName}")
|
||||
public ResponseEntity<?> deleteUser(@PathVariable String userName) {
|
||||
Result<Void> res = adminService.deleteUser(userName);
|
||||
if (res.isOk()) {
|
||||
return ResponseEntity.ok()
|
||||
.body("Deleted User with name - " + userName);
|
||||
} else {
|
||||
return ResponseEntity.badRequest().body(
|
||||
"An error occured while trying to delete user with name - "
|
||||
+ userName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,8 +89,8 @@ public class ChatMessageController {
|
||||
@PathVariable final String userName,
|
||||
@PathVariable final Instant lastMessageTime,
|
||||
final Principal principal) {
|
||||
final List<ChatMessageDTO> chatMessageDTOs = chatService.getNewMessages(
|
||||
principal.getName(), userName, lastMessageTime);
|
||||
final List<ChatMessageDTO> chatMessageDTOs = chatService
|
||||
.getNewMessages(principal.getName(), userName, lastMessageTime);
|
||||
return chatMessageDTOs;
|
||||
}
|
||||
|
||||
|
14
src/main/java/org/ros/chatto/controller/CsrfController.java
Normal file
14
src/main/java/org/ros/chatto/controller/CsrfController.java
Normal file
@ -0,0 +1,14 @@
|
||||
package org.ros.chatto.controller;
|
||||
|
||||
import org.springframework.security.web.csrf.CsrfToken;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class CsrfController {
|
||||
|
||||
@RequestMapping("/csrf")
|
||||
public CsrfToken csrf(CsrfToken token) {
|
||||
return token;
|
||||
}
|
||||
}
|
@ -45,7 +45,7 @@ public class Home {
|
||||
|| r.getAuthority().equals("ROLE_SUPER_USER"));
|
||||
log.trace("Is admin? " + isAdmin);
|
||||
// model.addAttribute("activeUsers",
|
||||
// userService.getOtherActiveUsers(principal.getName()));
|
||||
// userService.getOtherActiveUsers(principal.getName()));
|
||||
return "chat";
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
package org.ros.chatto.controller;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.ros.chatto.dto.StatsDTO;
|
||||
import org.ros.chatto.service.StatisticsService;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/stats")
|
||||
@RequiredArgsConstructor
|
||||
public class StatisticsController {
|
||||
private final StatisticsService statisticsService;
|
||||
|
||||
@GetMapping
|
||||
public StatsDTO rootStats() {
|
||||
return StatsDTO.builder()
|
||||
.totalMessages(statisticsService.totalMessage())
|
||||
.totalOnlineUsers(statisticsService.totalUsersOnline())
|
||||
.numMessagesToday(
|
||||
statisticsService.messagesOnDay(Instant.now()))
|
||||
.totalUsers(statisticsService.totalUsers()).build();
|
||||
}
|
||||
|
||||
}
|
@ -6,9 +6,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@Controller
|
||||
@RequestMapping("/user")
|
||||
public class UserController {
|
||||
|
||||
@RequestMapping
|
||||
public String viewUserProfile() {
|
||||
return "user/home";
|
||||
}
|
||||
|
||||
@RequestMapping
|
||||
public String viewUserProfile() {
|
||||
return "user/home";
|
||||
}
|
||||
}
|
17
src/main/java/org/ros/chatto/dto/AdminUserDTO.java
Normal file
17
src/main/java/org/ros/chatto/dto/AdminUserDTO.java
Normal file
@ -0,0 +1,17 @@
|
||||
package org.ros.chatto.dto;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
public class AdminUserDTO {
|
||||
private int id;
|
||||
private String userName;
|
||||
private String role;
|
||||
private Instant joinDate;
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package org.ros.chatto.dto;
|
||||
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Size;
|
||||
@ -12,10 +11,10 @@ import lombok.Data;
|
||||
public class ChatMessageDTO {
|
||||
@NotBlank(message = "Username should not be blank")
|
||||
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "Username must be alphanumeric")
|
||||
@Size(max=15)
|
||||
@Size(max = 15)
|
||||
private String toUser;
|
||||
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "Username must be alphanumeric")
|
||||
@Size(max=15)
|
||||
@Size(max = 15)
|
||||
private String fromUser;
|
||||
private MessageCipherDTO messageCipher;
|
||||
private Instant messageTime;
|
||||
|
@ -12,7 +12,9 @@ import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class MessageCipherDTO {
|
||||
@Pattern(regexp = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$") // regex for base64
|
||||
@Pattern(regexp = "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$") // regex
|
||||
// for
|
||||
// base64
|
||||
@NotBlank
|
||||
private String iv;
|
||||
@Max(1)
|
||||
|
21
src/main/java/org/ros/chatto/dto/StatsDTO.java
Normal file
21
src/main/java/org/ros/chatto/dto/StatsDTO.java
Normal file
@ -0,0 +1,21 @@
|
||||
package org.ros.chatto.dto;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@Builder
|
||||
@Getter
|
||||
public class StatsDTO {
|
||||
@Builder.Default
|
||||
private long totalUsers = 0;
|
||||
|
||||
@Builder.Default
|
||||
private long totalOnlineUsers = 0;
|
||||
|
||||
@Builder.Default
|
||||
private long totalMessages = 0;
|
||||
|
||||
@Builder.Default
|
||||
private long numMessagesToday = 0;
|
||||
|
||||
}
|
@ -16,11 +16,13 @@ public class UserRegistrationDTO {
|
||||
@Transient
|
||||
@Size(min = 4, max = 75, message = "Password must be between 4 and 75 characters")
|
||||
@NotBlank(message = "Password should not be blank")
|
||||
// @Pattern(regexp = "^.*(?=.{6,})(?=.*d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$", message = "Invalid password format")
|
||||
// @Pattern(regexp =
|
||||
// "^.*(?=.{6,})(?=.*d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$", message
|
||||
// = "Invalid password format")
|
||||
private String password;
|
||||
|
||||
|
||||
private Long captchaID;
|
||||
private String captchaText;
|
||||
|
||||
|
||||
private String captchaInput;
|
||||
}
|
||||
|
@ -7,15 +7,14 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ErrorModel{
|
||||
public class ErrorModel {
|
||||
@JsonProperty("field_name")
|
||||
private String fieldName;
|
||||
private String fieldName;
|
||||
@JsonProperty("rejected_value")
|
||||
private Object rejectedValue;
|
||||
private Object rejectedValue;
|
||||
@JsonProperty("error_message")
|
||||
private String messageError;
|
||||
|
||||
|
||||
private String messageError;
|
||||
|
||||
}
|
@ -15,6 +15,6 @@ import lombok.NoArgsConstructor;
|
||||
@AllArgsConstructor
|
||||
public class ErrorResponse {
|
||||
@JsonProperty("errors")
|
||||
private List<ErrorModel> errorMessage;
|
||||
|
||||
private List<ErrorModel> errorMessage;
|
||||
|
||||
}
|
@ -4,5 +4,6 @@ import org.springframework.cache.annotation.CacheEvict;
|
||||
|
||||
public class TokenCacheUtil {
|
||||
@CacheEvict(value = "userTokenCache", key = "#cacheKey")
|
||||
public static void evictSingleTokenValue(String cacheKey) {}
|
||||
public static void evictSingleTokenValue(String cacheKey) {
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,8 @@ public class UserLoggingSessionListener implements HttpSessionBindingListener {
|
||||
@Override
|
||||
public void valueBound(HttpSessionBindingEvent event) {
|
||||
|
||||
UserLoggingSessionListener user = (UserLoggingSessionListener) event.getValue();
|
||||
UserLoggingSessionListener user = (UserLoggingSessionListener) event
|
||||
.getValue();
|
||||
|
||||
log.debug("Incrementing session count for user {}", user.getUsername());
|
||||
|
||||
@ -38,7 +39,8 @@ public class UserLoggingSessionListener implements HttpSessionBindingListener {
|
||||
|
||||
@Override
|
||||
public void valueUnbound(HttpSessionBindingEvent event) {
|
||||
UserLoggingSessionListener user = (UserLoggingSessionListener) event.getValue();
|
||||
UserLoggingSessionListener user = (UserLoggingSessionListener) event
|
||||
.getValue();
|
||||
|
||||
log.debug("Decrementing session count for user {}", user.getUsername());
|
||||
|
||||
|
@ -11,17 +11,19 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component("myAuthenticationSuccessHandler")
|
||||
public class UserSessionLoggingLoginSuccessHandler implements AuthenticationSuccessHandler {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request,
|
||||
HttpServletResponse response, Authentication authentication)
|
||||
throws IOException {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
UserLoggingSessionListener user = new UserLoggingSessionListener(authentication.getName());
|
||||
session.setAttribute("user", user);
|
||||
}
|
||||
response.sendRedirect("/chat");
|
||||
}
|
||||
public class UserSessionLoggingLoginSuccessHandler
|
||||
implements AuthenticationSuccessHandler {
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request,
|
||||
HttpServletResponse response, Authentication authentication)
|
||||
throws IOException {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
UserLoggingSessionListener user = new UserLoggingSessionListener(
|
||||
authentication.getName());
|
||||
session.setAttribute("user", user);
|
||||
}
|
||||
response.sendRedirect("/chat");
|
||||
}
|
||||
}
|
@ -12,15 +12,16 @@ import org.springframework.security.web.authentication.logout.LogoutSuccessHandl
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component("myLogoutSuccessHandler")
|
||||
public class UserSessionLoggingLogoutSuccessHandler implements LogoutSuccessHandler{
|
||||
@Override
|
||||
public void onLogoutSuccess(HttpServletRequest request,
|
||||
HttpServletResponse response, Authentication authentication)
|
||||
throws IOException, ServletException {
|
||||
HttpSession session = request.getSession();
|
||||
if (session != null){
|
||||
session.removeAttribute("user");
|
||||
}
|
||||
response.sendRedirect("/login?logout");
|
||||
}
|
||||
public class UserSessionLoggingLogoutSuccessHandler
|
||||
implements LogoutSuccessHandler {
|
||||
@Override
|
||||
public void onLogoutSuccess(HttpServletRequest request,
|
||||
HttpServletResponse response, Authentication authentication)
|
||||
throws IOException, ServletException {
|
||||
HttpSession session = request.getSession();
|
||||
if (session != null) {
|
||||
session.removeAttribute("user");
|
||||
}
|
||||
response.sendRedirect("/login?logout");
|
||||
}
|
||||
}
|
@ -24,18 +24,18 @@ public class ChatMessage {
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "m_id")
|
||||
private Long messageID;
|
||||
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "from_user")
|
||||
private ChatUser fromUser;
|
||||
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "to_user")
|
||||
private ChatUser toUser;
|
||||
|
||||
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "message")
|
||||
private MessageCipher messageCipher;
|
||||
|
||||
private Instant messageTime;
|
||||
|
||||
private Instant messageTime;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package org.ros.chatto.model;
|
||||
|
||||
import java.util.Date;
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
@ -12,8 +12,6 @@ import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonBackReference;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
@ -43,9 +41,9 @@ public class ChatUser {
|
||||
@Column(name = "name")
|
||||
private String userName;
|
||||
|
||||
String password;
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date joinDate;
|
||||
private String password;
|
||||
|
||||
private Instant joinDate;
|
||||
|
||||
@ToString.Exclude
|
||||
@EqualsAndHashCode.Exclude
|
||||
@ -53,11 +51,4 @@ public class ChatUser {
|
||||
CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH })
|
||||
@JsonBackReference
|
||||
private Set<UserRole> userRoles;
|
||||
|
||||
// @ToString.Exclude
|
||||
// @EqualsAndHashCode.Exclude
|
||||
// @OneToMany(mappedBy = "fromUser", cascade = { CascadeType.PERSIST,
|
||||
// CascadeType.MERGE, CascadeType.DETACH,
|
||||
// CascadeType.REFRESH })
|
||||
// private List<ChatMessage> chatMessages;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ this is what the json will look like*/
|
||||
@Entity
|
||||
@Table(name = "message_ciphers")
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
//@JsonIgnoreProperties(value = { "id"}, allowGetters = false)
|
||||
// @JsonIgnoreProperties(value = { "id"}, allowGetters = false)
|
||||
public class MessageCipher {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
|
@ -16,7 +16,6 @@ import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(name = "roles")
|
||||
@Data
|
||||
@ -24,14 +23,14 @@ public class Role {
|
||||
@Id
|
||||
@Column(name = "role_id")
|
||||
private int roleID;
|
||||
|
||||
|
||||
@Column(name = "role_name")
|
||||
private String name;
|
||||
|
||||
private String name;
|
||||
|
||||
private String description;
|
||||
|
||||
@OneToMany(mappedBy = "role", cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH,
|
||||
CascadeType.REFRESH })
|
||||
|
||||
@OneToMany(mappedBy = "role", cascade = { CascadeType.PERSIST,
|
||||
CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH })
|
||||
@JsonBackReference
|
||||
@ToString.Exclude
|
||||
@EqualsAndHashCode.Exclude
|
||||
|
@ -20,13 +20,13 @@ public class UserSession {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private int id;
|
||||
|
||||
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "user_id")
|
||||
private ChatUser user;
|
||||
|
||||
|
||||
private boolean online;
|
||||
|
||||
|
||||
private int numSessions;
|
||||
private Instant timeStamp;
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package org.ros.chatto.repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.ros.chatto.dto.AdminUserDTO;
|
||||
import org.ros.chatto.model.ChatUser;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface AdminUserRepository extends JpaRepository<ChatUser, Long> {
|
||||
|
||||
@Query("select new org.ros.chatto.dto.AdminUserDTO(u.userID, u.userName, ur.role.name, u.joinDate )"
|
||||
+ " from ChatUser u join u.userRoles ur where u.userName != ?1")
|
||||
public List<AdminUserDTO> getOtherUsers(String principal);
|
||||
|
||||
@Query("select new org.ros.chatto.dto.AdminUserDTO(u.userID, u.userName, ur.role.name, u.joinDate )"
|
||||
+ " from ChatUser u join u.userRoles ur ")
|
||||
public List<AdminUserDTO> getAllUsers();
|
||||
|
||||
@Query("select new org.ros.chatto.dto.AdminUserDTO(u.userID, u.userName, ur.role.name, u.joinDate )"
|
||||
+ " from ChatUser u join u.userRoles ur where ur.role.roleID = 2")
|
||||
public List<AdminUserDTO> getRegularUsers();
|
||||
}
|
@ -10,7 +10,8 @@ import org.springframework.stereotype.Repository;
|
||||
import java.time.Instant;
|
||||
|
||||
@Repository
|
||||
public interface ChatMessageRepository extends JpaRepository<ChatMessage, Long> {
|
||||
public interface ChatMessageRepository
|
||||
extends JpaRepository<ChatMessage, Long> {
|
||||
@Query("select m from ChatMessage m join fetch m.messageCipher mc join fetch m.toUser tu join fetch m.fromUser fu "
|
||||
+ "where (tu.userName = ?1 or tu.userName = ?2) and "
|
||||
+ "(fu.userName = ?1 or fu.userName = ?2) order by m.messageTime asc")
|
||||
@ -19,10 +20,18 @@ public interface ChatMessageRepository extends JpaRepository<ChatMessage, Long>
|
||||
@Query("select m from ChatMessage m join fetch m.messageCipher mc join fetch m.toUser tu join fetch m.fromUser fu "
|
||||
+ "where (tu.userName = ?1 or tu.userName = ?2) and "
|
||||
+ "(fu.userName = ?1 or fu.userName = ?2) and (m.messageTime > ?3) order by m.messageTime asc")
|
||||
public List<ChatMessage> getNewMessages(String fromUser, String toUser, Instant lastMessageTime);
|
||||
public List<ChatMessage> getNewMessages(String fromUser, String toUser,
|
||||
Instant lastMessageTime);
|
||||
|
||||
@Query("select m from ChatMessage m join fetch m.messageCipher mc join fetch m.toUser tu join fetch m.fromUser fu "
|
||||
+ "where (tu.userName = ?1 or tu.userName = ?2) and "
|
||||
+ "(fu.userName = ?1 or fu.userName = ?2) order by m.messageTime desc")
|
||||
public List<ChatMessage> getAllMessages(String fromUser, String toUser, PageRequest pageRequest);
|
||||
public List<ChatMessage> getAllMessages(String fromUser, String toUser,
|
||||
PageRequest pageRequest);
|
||||
|
||||
@Query("select count(c) from ChatMessage c")
|
||||
public Long totalMessages();
|
||||
|
||||
@Query("select count(c) from ChatMessage c where c.messageTime=?1")
|
||||
public Long messagesOnDate(Instant date);
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
//package org.ros.chatto.repository;
|
||||
// package org.ros.chatto.repository;
|
||||
//
|
||||
//import org.springframework.data.jpa.repository.JpaRepository;
|
||||
//import org.springframework.stereotype.Repository;
|
||||
// import org.springframework.data.jpa.repository.JpaRepository;
|
||||
// import org.springframework.stereotype.Repository;
|
||||
//
|
||||
//@Repository
|
||||
//public interface DBInitializerRepostory extends JpaRepository<Integer, Integer>{
|
||||
//
|
||||
//}
|
||||
// @Repository
|
||||
// public interface DBInitializerRepostory extends JpaRepository<Integer,
|
||||
// Integer>{
|
||||
//
|
||||
// }
|
||||
|
@ -5,6 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface MessageCipherRepository extends JpaRepository<MessageCipher, Long>{
|
||||
public interface MessageCipherRepository
|
||||
extends JpaRepository<MessageCipher, Long> {
|
||||
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface RoleRepository extends JpaRepository<Role, Long>{
|
||||
public interface RoleRepository extends JpaRepository<Role, Long> {
|
||||
@Query("select r from Role r where r.name = ?1")
|
||||
public Role findByName(String roleName);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package org.ros.chatto.repository;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.ros.chatto.dto.AdminUserDTO;
|
||||
import org.ros.chatto.model.ChatUser;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
@ -10,12 +11,12 @@ import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<ChatUser, Long> {
|
||||
@Query("select cu from ChatUser cu where cu.userName = ?1")
|
||||
public Optional<ChatUser> findByUserName(String userName);
|
||||
|
||||
@Query("select cu from ChatUser cu join fetch cu.userRoles where cu.userName = ?1")
|
||||
public ChatUser findByUserNameWithRole(String userName);
|
||||
public Optional<ChatUser> findByUserName(String userName);
|
||||
|
||||
@Query("select cu.userName from ChatUser cu where cu.userName != ?1")
|
||||
public List<String> findAllOtherUserNames(String userName);
|
||||
|
||||
@Query("select count(u) from ChatUser u")
|
||||
public Long totalUsers();
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import java.util.List;
|
||||
|
||||
public interface UserRepositoryCustom {
|
||||
|
||||
// @Query("select s from Article s where s.author like ?1 and s.title = ?2")
|
||||
// List<Article> findByAuthorAndTitle(String author, String title);
|
||||
// @Query("select u from ChatUser u")
|
||||
// @Query("select s from Article s where s.author like ?1 and s.title = ?2")
|
||||
// List<Article> findByAuthorAndTitle(String author, String title);
|
||||
// @Query("select u from ChatUser u")
|
||||
public List<String> getAllUserNames(String s);
|
||||
}
|
||||
|
@ -14,30 +14,34 @@ import org.ros.chatto.model.ChatUser;
|
||||
import org.ros.chatto.repository.UserRepositoryCustom;
|
||||
|
||||
@Service
|
||||
class UserRepositoryCustomImpl implements UserRepositoryCustom{
|
||||
class UserRepositoryCustomImpl implements UserRepositoryCustom {
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
|
||||
@Override
|
||||
public List<String> getAllUserNames(String userName) {
|
||||
List<String> userNamesList = null;
|
||||
// Session session = null;
|
||||
// Session session = null;
|
||||
try {
|
||||
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
|
||||
CriteriaQuery<String> criteriaQuery = criteriaBuilder.createQuery(String.class);
|
||||
CriteriaBuilder criteriaBuilder = entityManager
|
||||
.getCriteriaBuilder();
|
||||
CriteriaQuery<String> criteriaQuery = criteriaBuilder
|
||||
.createQuery(String.class);
|
||||
Root<ChatUser> root = criteriaQuery.from(ChatUser.class);
|
||||
criteriaQuery.select(root.get("userName"));
|
||||
criteriaQuery.where(criteriaBuilder.notEqual(root.get("userName"), userName));
|
||||
|
||||
userNamesList = entityManager.createQuery(criteriaQuery).getResultList();
|
||||
// for(String un: userNamesList)
|
||||
// {
|
||||
// System.out.println(un);
|
||||
// }
|
||||
criteriaQuery.where(
|
||||
criteriaBuilder.notEqual(root.get("userName"), userName));
|
||||
|
||||
userNamesList = entityManager.createQuery(criteriaQuery)
|
||||
.getResultList();
|
||||
// for(String un: userNamesList)
|
||||
// {
|
||||
// System.out.println(un);
|
||||
// }
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return userNamesList;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -8,10 +8,14 @@ import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface UserSessionRepository extends JpaRepository<UserSession, Long> {
|
||||
public interface UserSessionRepository
|
||||
extends JpaRepository<UserSession, Long> {
|
||||
@Query("select us from UserSession us join fetch us.user where us.user.userName = ?1 ")
|
||||
public UserSession findByUserName(String userName);
|
||||
|
||||
|
||||
@Query("select us from UserSession us join fetch us.user")
|
||||
public List<UserSession> findAllUserSessions();
|
||||
|
||||
@Query("select count(us) from UserSession us where us.online=true")
|
||||
public Long totalOnlineUsers();
|
||||
}
|
||||
|
@ -16,20 +16,29 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
|
||||
|
||||
private static final SimpleGrantedAuthority SUPER_USER_AUTHORITY = new SimpleGrantedAuthority("ROLE_SUPER_USER");
|
||||
private static final SimpleGrantedAuthority ADMIN_AUTHORITY = new SimpleGrantedAuthority("ROLE_ADMIN");
|
||||
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
|
||||
public class AuthenticationSuccessHandlerImpl
|
||||
implements AuthenticationSuccessHandler {
|
||||
|
||||
private static final SimpleGrantedAuthority SUPER_USER_AUTHORITY = new SimpleGrantedAuthority(
|
||||
"ROLE_SUPER_USER");
|
||||
private static final SimpleGrantedAuthority ADMIN_AUTHORITY = new SimpleGrantedAuthority(
|
||||
"ROLE_ADMIN");
|
||||
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
|
||||
Authentication authentication) throws IOException, ServletException {
|
||||
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
if (authorities.contains(ADMIN_AUTHORITY) || authorities.contains(SUPER_USER_AUTHORITY)) {
|
||||
redirectStrategy.sendRedirect(httpServletRequest, httpServletResponse, "/admin");
|
||||
} else {
|
||||
redirectStrategy.sendRedirect(httpServletRequest, httpServletResponse, "/user");
|
||||
}
|
||||
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
|
||||
HttpServletResponse httpServletResponse,
|
||||
Authentication authentication)
|
||||
throws IOException, ServletException {
|
||||
Collection<? extends GrantedAuthority> authorities = authentication
|
||||
.getAuthorities();
|
||||
if (authorities.contains(ADMIN_AUTHORITY)
|
||||
|| authorities.contains(SUPER_USER_AUTHORITY)) {
|
||||
redirectStrategy.sendRedirect(httpServletRequest,
|
||||
httpServletResponse, "/admin");
|
||||
} else {
|
||||
redirectStrategy.sendRedirect(httpServletRequest,
|
||||
httpServletResponse, "/user");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.ros.chatto.security;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.ros.chatto.model.ChatUser;
|
||||
@ -26,17 +27,19 @@ public class MyUserDetailsService implements UserDetailsService {
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(final String username) {
|
||||
log.trace("User Details - loading with username: {}", username);
|
||||
ChatUser user = userService.getUserWithRole(username);
|
||||
Optional<ChatUser> user = userService.getUser(username);
|
||||
|
||||
if (user == null) {
|
||||
if (!user.isPresent()) {
|
||||
log.warn("Request for unknown user {}", username);
|
||||
throw new UsernameNotFoundException(username);
|
||||
}
|
||||
|
||||
Set<UserRole> userRoles = user.getUserRoles();
|
||||
ChatUser user2 = user.get();
|
||||
|
||||
return User.withUsername(user.getUserName())
|
||||
.password(user.getPassword())
|
||||
Set<UserRole> userRoles = user2.getUserRoles();
|
||||
|
||||
return User.withUsername(user2.getUserName())
|
||||
.password(user2.getPassword())
|
||||
.roles(userRoles.stream().map(ur -> ur.getRole().getName())
|
||||
.toArray(size -> new String[size]))
|
||||
.build();
|
||||
|
46
src/main/java/org/ros/chatto/service/AdminService.java
Normal file
46
src/main/java/org/ros/chatto/service/AdminService.java
Normal file
@ -0,0 +1,46 @@
|
||||
package org.ros.chatto.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.spencerwi.either.Result;
|
||||
|
||||
import org.ros.chatto.dto.AdminUserDTO;
|
||||
import org.ros.chatto.model.ChatUser;
|
||||
import org.ros.chatto.repository.AdminUserRepository;
|
||||
import org.ros.chatto.repository.UserRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Transactional
|
||||
public class AdminService {
|
||||
private final UserRepository userRepository;
|
||||
private final AdminUserRepository adminUserRepository;
|
||||
|
||||
public List<AdminUserDTO> getUsers(String principal, UserDTOSpec spec) {
|
||||
switch (spec) {
|
||||
case OTHER_USERS:
|
||||
return adminUserRepository.getOtherUsers(principal);
|
||||
case ALL_USERS:
|
||||
return adminUserRepository.getAllUsers();
|
||||
case REGULAR_USERS:
|
||||
return adminUserRepository.getRegularUsers();
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public Result<Void> deleteUser(String userName) {
|
||||
Optional<ChatUser> user = userRepository.findByUserName(userName);
|
||||
return Result.attempt(() -> user.get()).map((u) -> {
|
||||
userRepository.delete(u);
|
||||
return (Void) null;
|
||||
});
|
||||
}
|
||||
}
|
@ -9,18 +9,17 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class CaptchaService {
|
||||
private final WebCaptcha webCaptcha;
|
||||
|
||||
|
||||
public CaptchaService() {
|
||||
webCaptcha = WebCaptcha.builder().captchaBehaviour(new SimpleCaptchaBehavior()).build();
|
||||
webCaptcha = WebCaptcha.builder()
|
||||
.captchaBehaviour(new SimpleCaptchaBehavior()).build();
|
||||
}
|
||||
|
||||
public BufferedImage createCaptchaImage(final String captchaText)
|
||||
{
|
||||
|
||||
public BufferedImage createCaptchaImage(final String captchaText) {
|
||||
return webCaptcha.generateCaptcha(captchaText);
|
||||
}
|
||||
|
||||
public String getRandomText()
|
||||
{
|
||||
|
||||
public String getRandomText() {
|
||||
return webCaptcha.getRandomChars();
|
||||
}
|
||||
}
|
||||
|
@ -46,13 +46,15 @@ public class DBInitializerService {
|
||||
});
|
||||
}
|
||||
|
||||
private void resetAllUserSessions(final Connection connection) throws SQLException {
|
||||
private void resetAllUserSessions(final Connection connection)
|
||||
throws SQLException {
|
||||
final PreparedStatement preparedStatement = connection
|
||||
.prepareStatement(dbInitializerConfig.getResetSessionsQuery());
|
||||
preparedStatement.executeUpdate();
|
||||
}
|
||||
|
||||
private void clearAllTokens(final Connection connection) throws SQLException {
|
||||
private void clearAllTokens(final Connection connection)
|
||||
throws SQLException {
|
||||
final PreparedStatement preparedStatement = connection
|
||||
.prepareStatement(dbInitializerConfig.getClearTokensQuery());
|
||||
preparedStatement.executeUpdate();
|
||||
|
36
src/main/java/org/ros/chatto/service/StatisticsService.java
Normal file
36
src/main/java/org/ros/chatto/service/StatisticsService.java
Normal file
@ -0,0 +1,36 @@
|
||||
package org.ros.chatto.service;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import org.ros.chatto.repository.ChatMessageRepository;
|
||||
import org.ros.chatto.repository.UserRepository;
|
||||
import org.ros.chatto.repository.UserSessionRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
@Transactional(readOnly = true)
|
||||
public class StatisticsService {
|
||||
private final ChatMessageRepository chatMessageRepository;
|
||||
private final UserRepository userRepo;
|
||||
private final UserSessionRepository userSessionRepo;
|
||||
|
||||
public Long totalMessage() {
|
||||
return chatMessageRepository.totalMessages();
|
||||
}
|
||||
|
||||
public Long totalUsers() {
|
||||
return userRepo.totalUsers();
|
||||
}
|
||||
|
||||
public Long totalUsersOnline() {
|
||||
return userSessionRepo.totalOnlineUsers();
|
||||
}
|
||||
|
||||
public Long messagesOnDay(Instant date) {
|
||||
return chatMessageRepository.messagesOnDate(date);
|
||||
}
|
||||
}
|
5
src/main/java/org/ros/chatto/service/UserDTOSpec.java
Normal file
5
src/main/java/org/ros/chatto/service/UserDTOSpec.java
Normal file
@ -0,0 +1,5 @@
|
||||
package org.ros.chatto.service;
|
||||
|
||||
public enum UserDTOSpec {
|
||||
ALL_USERS, OTHER_USERS, REGULAR_USERS
|
||||
}
|
@ -25,9 +25,8 @@ public interface UserService {
|
||||
|
||||
public List<ActiveUserDTO> getOtherActiveUsers(String userName);
|
||||
|
||||
public ChatUser getUserWithRole(String userName);
|
||||
|
||||
public void incrementUserSession(String userName);
|
||||
|
||||
public UserSession decrementUserSession(String userName);
|
||||
|
||||
}
|
||||
|
@ -139,12 +139,6 @@ public class UserServiceImpl implements UserService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ChatUser getUserWithRole(final String userName) {
|
||||
return userRepository.findByUserNameWithRole(userName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementUserSession(final String userName) {
|
||||
final Optional<ChatUser> chatUser = getUser(userName);
|
||||
@ -200,4 +194,14 @@ public class UserServiceImpl implements UserService {
|
||||
return user.getUserRoles().stream().map(ur -> ur.getRole())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public void deleteUser(String userName) {
|
||||
//
|
||||
Optional<ChatUser> user = userRepository.findByUserName(userName);
|
||||
user.ifPresentOrElse(u -> {
|
||||
userRepository.delete(u);
|
||||
}, () -> {
|
||||
log.error("User with name {} not found for deletion", userName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -6,4 +6,6 @@ logging.level.org.hibernate.type.descriptor.sql.BasicBinder=INFO
|
||||
spring.http.log-request-details=false
|
||||
logging.level.org.springframework.cache = INFO
|
||||
chat-bundle=bundle.min.js
|
||||
admin-bundle=adminBundle.min.js
|
||||
chat-worker-bundle=chatWorker.min.js
|
||||
chatto.frontend.log-level=INFO
|
@ -3,7 +3,6 @@
|
||||
#spring.datasource.url = jdbc:mysql://localhost:3306/chatto_db?useSSL=false
|
||||
#spring.datasource.username = chatto_user
|
||||
#spring.datasource.password = password
|
||||
|
||||
chatto.datasource.username=chatto_user
|
||||
chatto.datasource.password=password
|
||||
chatto.datasource.database-name=chatto_db2
|
||||
@ -29,6 +28,8 @@ spring.cache.jcache.config=classpath:ehcache.xml
|
||||
logging.level.org.springframework.cache=DEBUG
|
||||
chatto.token.timeout-duration=30
|
||||
chat-bundle=bundle.js
|
||||
admin-bundle=adminBundle.js
|
||||
chat-worker-bundle=chatWorker.js
|
||||
# spring.devtools.add-properties=false
|
||||
chatto.frontend.log-level=TRACE
|
||||
chatto.frontend.chat-page-size=9
|
@ -1,33 +1,33 @@
|
||||
.text-primary-dark {
|
||||
/* color: #67b2fd; */
|
||||
color: #7accff;
|
||||
/* color: #67b2fd; */
|
||||
color: #7accff;
|
||||
}
|
||||
|
||||
.border-left-primary-dark {
|
||||
border-left: 0.25rem solid #7accff !important;
|
||||
border-left: 0.25rem solid #7accff !important;
|
||||
}
|
||||
|
||||
.sidebar .nav-item .collapse .collapse-inner .collapse-item,
|
||||
.sidebar .nav-item .collapsing .collapse-inner .collapse-item {
|
||||
padding: 0.5rem 1rem;
|
||||
margin: 0 0.5rem;
|
||||
display: block;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border-radius: 0.35rem;
|
||||
white-space: nowrap;
|
||||
padding: 0.5rem 1rem;
|
||||
margin: 0 0.5rem;
|
||||
display: block;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
border-radius: 0.35rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar-dark .nav-item .nav-link i {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.sidebar-dark .nav-item .nav-link {
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
.sidebar-dark .nav-item .nav-link[data-toggle="collapse"]::after {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.sidebar .nav-item .collapse .collapse-inner .collapse-item:hover,
|
||||
@ -40,7 +40,7 @@
|
||||
} */
|
||||
|
||||
.text-info {
|
||||
color: #5bd6e8 !important;
|
||||
color: #5bd6e8 !important;
|
||||
}
|
||||
|
||||
/* :root {
|
||||
@ -48,14 +48,18 @@
|
||||
} */
|
||||
|
||||
.sidebar-dark hr.sidebar-divider {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.5);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
body {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
background: #333;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#wrapper #content-wrapper {
|
||||
background-color: #333;
|
||||
}
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
table.table-bordered.dataTable {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
|
8
src/main/resources/static/css/admin-table.css
Normal file
8
src/main/resources/static/css/admin-table.css
Normal file
@ -0,0 +1,8 @@
|
||||
label,
|
||||
#usersTable_info,
|
||||
.dataTables_paginate,
|
||||
.paginate_button,
|
||||
#usersTable_next,
|
||||
#usersTable_previous {
|
||||
color: white !important;
|
||||
}
|
@ -25,8 +25,9 @@ input[type="radio"]:focus + label {
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
min-height: 900px;
|
||||
height: auto;
|
||||
min-height: 600px;
|
||||
height: 95vh;
|
||||
/* height: auto; */
|
||||
}
|
||||
|
||||
#home-section .home-inner {
|
||||
@ -44,7 +45,8 @@ input[type="radio"]:focus + label {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
min-height: 900px;
|
||||
min-height: 600px;
|
||||
height: 95vh;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
@ -99,3 +101,24 @@ textarea {
|
||||
width: 90%;
|
||||
} */
|
||||
}
|
||||
|
||||
html {
|
||||
--scrollbarBG: hsla(0, 0%, 100%, 0.1);
|
||||
--thumbBG: #90a4ae;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
width: 11px;
|
||||
}
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--thumbBG) var(--scrollbarBG);
|
||||
}
|
||||
*::-webkit-scrollbar-track {
|
||||
background: var(--scrollbarBG);
|
||||
}
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background-color: var(--thumbBG);
|
||||
border-radius: 6px;
|
||||
border: 3px solid var(--scrollbarBG);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,9 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Webpack App</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="/bundle.js"></script></body>
|
||||
</html>
|
@ -8,14 +8,9 @@
|
||||
<script src="https://code.jquery.com/jquery-2.1.4.min.js" th:if="false"></script>
|
||||
<script src="http://blackpeppersoftware.github.io/thymeleaf-fragment.js/thymeleaf-fragment.js"
|
||||
data-template-prefix="../" defer="defer" th:if="false"></script>
|
||||
<!-- <script th:src="@{/js/admin.js}" src="../../static/js/admin.js" defer="defer"></script> -->
|
||||
<script th:src="@{/js/adminBundle.js}" src="../../static/js/adminBundle.js" defer="defer"></script>
|
||||
<script th:src="@{/js/sb-admin-2.js}" src="../../static/js/sb-admin-2.js" defer="defer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.1/jquery.easing.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.js"></script>
|
||||
|
||||
<link th:href="@{/css/sb-admin-2.css}" href="../../static/css/sb-admin-2.css" rel="stylesheet">
|
||||
<link th:href="@{/css/admin-custom.css}" href="../../static/css/admin-custom.css" rel="stylesheet">
|
||||
<th:block th:include="fragments/admin :: headFragment"></th:block>
|
||||
|
||||
</head>
|
||||
<!-- TODO
|
||||
Make user admin / remove user from admin
|
||||
|
@ -6,14 +6,10 @@
|
||||
<title id="pageTitle">Admin Home</title>
|
||||
</div>
|
||||
<script src="https://code.jquery.com/jquery-2.1.4.min.js" th:if="false"></script>
|
||||
<script src="http://blackpeppersoftware.github.io/thymeleaf-fragment.js/thymeleaf-fragment.js" data-template-prefix="../" defer="defer" th:if="false"></script>
|
||||
<script th:src="@{js/admin.js}" src="../../static/js/admin.js" defer="defer"></script>
|
||||
<script th:src="@{js/sb-admin-2.js}" src="../../static/js/sb-admin-2.js" defer="defer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.1/jquery.easing.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.js"></script>
|
||||
<script src="http://blackpeppersoftware.github.io/thymeleaf-fragment.js/thymeleaf-fragment.js"
|
||||
data-template-prefix="../" defer="defer" th:if="false"></script>
|
||||
|
||||
<link th:href="@{/css/sb-admin-2.css}" href="../../static/css/sb-admin-2.css" rel="stylesheet">
|
||||
<link th:href="@{/css/admin-custom.css}" href="../../static/css/admin-custom.css" rel="stylesheet">
|
||||
<th:block th:include="fragments/admin :: headFragment"></th:block>
|
||||
</head>
|
||||
<!-- TODO
|
||||
Make user admin / remove user from admin
|
||||
@ -21,13 +17,13 @@
|
||||
Delete Messages
|
||||
-->
|
||||
|
||||
<!-- <div th:include="fragments/admin :: admin-sidebar"></div> -->
|
||||
<!-- <div th:include="fragments/admin :: admin-sideb ar"></div> -->
|
||||
|
||||
<body id="page-top">
|
||||
|
||||
<!-- Page Wrapper -->
|
||||
<div id="wrapper">
|
||||
|
||||
|
||||
|
||||
<th:block th:include="fragments/admin :: sidebar-fragment"></th:block>
|
||||
|
||||
@ -36,7 +32,7 @@
|
||||
|
||||
<!-- Main Content -->
|
||||
<div id="content">
|
||||
|
||||
|
||||
<div th:include="fragments/admin :: topbar-fragment"></div>
|
||||
|
||||
<!-- Begin Page Content -->
|
||||
@ -45,7 +41,8 @@
|
||||
<!-- Page Heading -->
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-light">Dashboard</h1>
|
||||
<a href="#" class="d-none d-sm-inline-block btn btn-sm btn-primary shadow-sm"><i class="fas fa-download fa-sm text-white-50"></i> Generate Report</a>
|
||||
<a href="#" class="d-none d-sm-inline-block btn btn-sm btn-primary shadow-sm"><i
|
||||
class="fas fa-download fa-sm text-white-50"></i> Generate Report</a>
|
||||
</div>
|
||||
|
||||
<!-- Content Row -->
|
||||
@ -53,15 +50,21 @@
|
||||
|
||||
<!-- Earnings (Monthly) Card Example -->
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card text-light border border-dark bg-dark border-left-primary-dark shadow h-100 py-2">
|
||||
<div
|
||||
class="card text-light border border-dark bg-dark border-left-primary-dark shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-primary-dark text-uppercase mb-1">Earnings (Monthly)</div>
|
||||
<div class="h5 mb-0 font-weight-bold ">$40,000</div>
|
||||
<div
|
||||
class="text-xs font-weight-bold text-primary-dark text-uppercase mb-1">
|
||||
Total Users</div>
|
||||
<div class="h5 mb-0 font-weight-bold " id="totalUsers" hidden>0</div>
|
||||
<div
|
||||
class="spinner-grow spinner-grow-lg text-info admin-stats-spinner admin-spinner">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-calendar fa-2x text-gray-300"></i>
|
||||
<i class="fas fa-users fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -70,15 +73,21 @@
|
||||
|
||||
<!-- Earnings (Monthly) Card Example -->
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card text-light border border-dark bg-dark border-left-success shadow h-100 py-2">
|
||||
<div
|
||||
class="card text-light border border-dark bg-dark border-left-success shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">Earnings (Annual)</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-light">$215,000</div>
|
||||
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">
|
||||
Total Messages</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-light" id="totalMessages" hidden>0
|
||||
</div>
|
||||
<div
|
||||
class="spinner-grow spinner-grow-lg text-info admin-stats-spinner admin-spinner">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-dollar-sign fa-2x text-gray-300"></i>
|
||||
<i class="fas fa-comments fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -91,20 +100,28 @@
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">Tasks</div>
|
||||
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">Users
|
||||
Online
|
||||
</div>
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col-auto">
|
||||
<div class="h5 mb-0 mr-3 font-weight-bold text-white">50%</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="progress progress-sm mr-2">
|
||||
<div class="progress-bar bg-info" role="progressbar" style="width: 50%" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="h5 mb-0 mr-3 font-weight-bold text-white"
|
||||
id="totalOnlineUsers" hidden>0</div>
|
||||
<div
|
||||
class="spinner-grow spinner-grow-lg text-info admin-stats-spinner admin-spinner">
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="col">
|
||||
<div class="progress progress-sm mr-2">
|
||||
<div class="progress-bar bg-info" role="progressbar"
|
||||
style="width: 50%" aria-valuenow="50" aria-valuemin="0"
|
||||
aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-clipboard-list fa-2x text-gray-300"></i>
|
||||
<i class="fas fa-user fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -113,15 +130,21 @@
|
||||
|
||||
<!-- Pending Requests Card Example -->
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card text-light border border-dark bg-dark border-left-warning shadow h-100 py-2">
|
||||
<div
|
||||
class="card text-light border border-dark bg-dark border-left-warning shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">Pending Requests</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-white">18</div>
|
||||
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
|
||||
Messages Today</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-white" id="numMessagesToday"
|
||||
hidden>0</div>
|
||||
<div
|
||||
class="spinner-grow spinner-grow-lg text-info admin-stats-spinner admin-spinner">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<i class="fas fa-comments fa-2x text-gray-300"></i>
|
||||
<i class="fas fa-comment fa-2x text-gray-300"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -137,13 +160,16 @@
|
||||
<div class="col-xl-8 col-lg-7">
|
||||
<div class="card bg-dark border border-dark text-white shadow mb-4">
|
||||
<!-- Card Header - Dropdown -->
|
||||
<div class="card-header bg-secondary border border-secondary py-3 d-flex flex-row align-items-center justify-content-between">
|
||||
<div
|
||||
class="card-header bg-secondary border border-secondary py-3 d-flex flex-row align-items-center justify-content-between">
|
||||
<h6 class="m-0 font-weight-bold text-white">Earnings Overview</h6>
|
||||
<div class="dropdown no-arrow">
|
||||
<a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-ellipsis-v fa-sm fa-fw text-white"></i>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right shadow animated--fade-in" aria-labelledby="dropdownMenuLink">
|
||||
<div class="dropdown-menu dropdown-menu-right shadow animated--fade-in"
|
||||
aria-labelledby="dropdownMenuLink">
|
||||
<div class="dropdown-header">Dropdown Header:</div>
|
||||
<a class="dropdown-item" href="#">Action</a>
|
||||
<a class="dropdown-item" href="#">Another action</a>
|
||||
@ -165,13 +191,16 @@
|
||||
<div class="col-xl-4 col-lg-5">
|
||||
<div class="card bg-dark border border-dark text-white shadow mb-4">
|
||||
<!-- Card Header - Dropdown -->
|
||||
<div class="card-header bg-secondary border border-secondary py-3 d-flex flex-row align-items-center justify-content-between">
|
||||
<div
|
||||
class="card-header bg-secondary border border-secondary py-3 d-flex flex-row align-items-center justify-content-between">
|
||||
<h6 class="m-0 font-weight-bold text-white">Revenue Sources</h6>
|
||||
<div class="dropdown no-arrow">
|
||||
<a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<a class="dropdown-toggle" href="#" role="button" id="dropdownMenuLink"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-ellipsis-v fa-sm fa-fw text-white"></i>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right shadow animated--fade-in" aria-labelledby="dropdownMenuLink">
|
||||
<div class="dropdown-menu dropdown-menu-right shadow animated--fade-in"
|
||||
aria-labelledby="dropdownMenuLink">
|
||||
<div class="dropdown-header">Dropdown Header:</div>
|
||||
<a class="dropdown-item" href="#">Action</a>
|
||||
<a class="dropdown-item" href="#">Another action</a>
|
||||
@ -187,14 +216,14 @@
|
||||
</div>
|
||||
<div class="mt-4 text-center small">
|
||||
<span class="mr-2">
|
||||
<i class="fas fa-circle text-primary"></i> Direct
|
||||
</span>
|
||||
<i class="fas fa-circle text-primary"></i> Direct
|
||||
</span>
|
||||
<span class="mr-2">
|
||||
<i class="fas fa-circle text-success"></i> Social
|
||||
</span>
|
||||
<i class="fas fa-circle text-success"></i> Social
|
||||
</span>
|
||||
<span class="mr-2">
|
||||
<i class="fas fa-circle text-info"></i> Referral
|
||||
</span>
|
||||
<i class="fas fa-circle text-info"></i> Referral
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -213,25 +242,35 @@
|
||||
<h6 class="m-0 font-weight-bold text-white">Projects</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h4 class="small font-weight-bold">Server Migration <span class="float-right">20%</span></h4>
|
||||
<h4 class="small font-weight-bold">Server Migration <span
|
||||
class="float-right">20%</span></h4>
|
||||
<div class="progress mb-4">
|
||||
<div class="progress-bar bg-danger" role="progressbar" style="width: 20%" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="progress-bar bg-danger" role="progressbar" style="width: 20%"
|
||||
aria-valuenow="20" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<h4 class="small font-weight-bold">Sales Tracking <span class="float-right">40%</span></h4>
|
||||
<h4 class="small font-weight-bold">Sales Tracking <span
|
||||
class="float-right">40%</span></h4>
|
||||
<div class="progress mb-4">
|
||||
<div class="progress-bar bg-warning" role="progressbar" style="width: 40%" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="progress-bar bg-warning" role="progressbar" style="width: 40%"
|
||||
aria-valuenow="40" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<h4 class="small font-weight-bold">Customer Database <span class="float-right">60%</span></h4>
|
||||
<h4 class="small font-weight-bold">Customer Database <span
|
||||
class="float-right">60%</span></h4>
|
||||
<div class="progress mb-4">
|
||||
<div class="progress-bar" role="progressbar" style="width: 60%" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="progress-bar" role="progressbar" style="width: 60%"
|
||||
aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<h4 class="small font-weight-bold">Payout Details <span class="float-right">80%</span></h4>
|
||||
<h4 class="small font-weight-bold">Payout Details <span
|
||||
class="float-right">80%</span></h4>
|
||||
<div class="progress mb-4">
|
||||
<div class="progress-bar bg-info" role="progressbar" style="width: 80%" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="progress-bar bg-info" role="progressbar" style="width: 80%"
|
||||
aria-valuenow="80" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<h4 class="small font-weight-bold">Account Setup <span class="float-right">Complete!</span></h4>
|
||||
<h4 class="small font-weight-bold">Account Setup <span
|
||||
class="float-right">Complete!</span></h4>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-success" role="progressbar" style="width: 100%" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<div class="progress-bar bg-success" role="progressbar" style="width: 100%"
|
||||
aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -299,11 +338,17 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="text-center">
|
||||
<img class="img-fluid px-3 px-sm-4 mt-3 mb-4" style="width: 25rem;" th:src="@{/img/undraw_posting_photo.svg}" src="../../static/img/undraw_posting_photo.svg" alt="">
|
||||
<img class="img-fluid px-3 px-sm-4 mt-3 mb-4" style="width: 25rem;"
|
||||
th:src="@{/img/undraw_posting_photo.svg}"
|
||||
src="../../static/img/undraw_posting_photo.svg" alt="">
|
||||
</div>
|
||||
<p>Add some quality, svg illustrations to your project courtesy of <a target="_blank" rel="nofollow" href="https://undraw.co/">unDraw</a>, a constantly updated collection of beautiful svg images that you can use completely
|
||||
<p>Add some quality, svg illustrations to your project courtesy of <a
|
||||
target="_blank" rel="nofollow" href="https://undraw.co/">unDraw</a>, a
|
||||
constantly updated collection of beautiful svg images that you can use
|
||||
completely
|
||||
free and without attribution!</p>
|
||||
<a target="_blank" rel="nofollow" href="https://undraw.co/">Browse Illustrations on unDraw →</a>
|
||||
<a target="_blank" rel="nofollow" href="https://undraw.co/">Browse Illustrations on
|
||||
unDraw →</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -313,8 +358,11 @@
|
||||
<h6 class="m-0 font-weight-bold text-white">Development Approach</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>SB Admin 2 makes extensive use of Bootstrap 4 utility classes in order to reduce CSS bloat and poor page performance. Custom CSS classes are used to create custom components and custom utility classes.</p>
|
||||
<p class="mb-0">Before working with this theme, you should become familiar with the Bootstrap framework, especially the utility classes.</p>
|
||||
<p>SB Admin 2 makes extensive use of Bootstrap 4 utility classes in order to reduce
|
||||
CSS bloat and poor page performance. Custom CSS classes are used to create
|
||||
custom components and custom utility classes.</p>
|
||||
<p class="mb-0">Before working with this theme, you should become familiar with the
|
||||
Bootstrap framework, especially the utility classes.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -336,7 +384,9 @@
|
||||
<!-- End of Page Wrapper -->
|
||||
|
||||
<th:block th:include="fragments/admin :: modal"></th:block>
|
||||
<div th:if="false"><th:block th:include="admin :: modal"></th:block></div>
|
||||
<div th:if="false">
|
||||
<th:block th:include="admin :: modal"></th:block>
|
||||
</div>
|
||||
|
||||
<!-- Page level custom scripts -->
|
||||
<script th:src="@{/js/demo/chart-area-demo.js}" src="../../static/js/demo/chart-area-demo.js"></script>
|
||||
|
@ -1,110 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<div th:replace="fragments/head :: headFragment">
|
||||
<title id="pageTitle">Admin Home</title>
|
||||
</div>
|
||||
<script src="https://code.jquery.com/jquery-2.1.4.min.js" th:if="false"></script>
|
||||
<script src="http://blackpeppersoftware.github.io/thymeleaf-fragment.js/thymeleaf-fragment.js" data-template-prefix="../" defer="defer" th:if="false"></script>
|
||||
<script th:src="@{js/admin.js}" src="../../static/js/admin.js" defer="defer"></script>
|
||||
<link th:href="@{/css/master.css}" href="../../static/css/master.css" rel="stylesheet" th:if="false">
|
||||
<link th:href="@{/css/colors.css}" href="../../static/css/colors.css" rel="stylesheet" th:if="false">
|
||||
</head>
|
||||
<!-- TODO
|
||||
Make user admin / remove user from admin
|
||||
Change E2E passphrase
|
||||
Delete Messages
|
||||
-->
|
||||
|
||||
<body>
|
||||
<div th:include="fragments/navbar :: navbarFragment"></div>
|
||||
|
||||
<header>
|
||||
<div class="container bg-primary">
|
||||
<div class="row">
|
||||
<div class="col-sm py-5">
|
||||
<h1 class="display-4 text-center">Admin Page</h1>
|
||||
<p class="alert-danger px-2">Warning: these settings can be dangerous..</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm col-md-4">
|
||||
<h4>Make User an Admin</h4>
|
||||
<form id="addUserToAdminForm">
|
||||
<div class="form-group">
|
||||
<label for="addUserToDropDown">Select User:</label>
|
||||
<select class="form-control custom-select" size="4" id="addUserToAdminDropDown">
|
||||
|
||||
<option th:each="userName : ${userNames}"
|
||||
th:value="${userName}"
|
||||
th:text="#{${userName}}">
|
||||
Wireframe
|
||||
</option>
|
||||
|
||||
</select>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-danger form-control">Make admin</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-sm col-md-4">
|
||||
<h4>Change passphrases</h4>
|
||||
<form id="changePassphraseForm">
|
||||
<div class="form-group">
|
||||
<label for="changePassphraseDropDown">Select User:</label>
|
||||
<select class="form-control" id="changePassphraseDropDown">
|
||||
|
||||
<option th:each="userName : ${userNames}"
|
||||
th:value="${userName}"
|
||||
th:text="#{${userName}}">
|
||||
Wireframe
|
||||
</option>
|
||||
|
||||
</select>
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="passphraseOld">Passphrase Old</label>
|
||||
<input type="password" id="passphraseOld" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="passphraseNew">Passphrase New</label>
|
||||
<input type="password" id="passphraseNew" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-danger form-control">Change Passphrase</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<!-- <div class="col-sm"></div> -->
|
||||
|
||||
<div class="col-sm d-lg-block">
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="py-5">
|
||||
<h4 class="p-2 text-center">Logout</h4>
|
||||
<form action="#" th:action="@{/logout}" method="POST">
|
||||
<!-- <input type="submit" value="logout"> -->
|
||||
<!-- <input type="hidden" th:name="${_csrf.parameterName}"
|
||||
th:value="${_csrf.token}" /> -->
|
||||
<div class="form-group">
|
||||
<button class="btn btn-secondary form-control">Logout</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
86
src/main/resources/templates/admin/new-user.html
Normal file
86
src/main/resources/templates/admin/new-user.html
Normal file
@ -0,0 +1,86 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<div th:replace="fragments/head :: headFragment">
|
||||
<title id="pageTitle" th:text="${userName}">New User</title>
|
||||
</div>
|
||||
<script src="https://code.jquery.com/jquery-2.1.4.min.js" th:if="false"></script>
|
||||
<script src="http://blackpeppersoftware.github.io/thymeleaf-fragment.js/thymeleaf-fragment.js"
|
||||
data-template-prefix="../" defer="defer" th:if="false"></script>
|
||||
|
||||
<th:block th:include="fragments/admin :: headFragment"></th:block>
|
||||
|
||||
</head>
|
||||
<!-- TODO
|
||||
Make user admin / remove user from admin
|
||||
Change E2E passphrase
|
||||
Delete Messages
|
||||
-->
|
||||
|
||||
<!-- <div th:include="fragments/admin :: admin-sidebar"></div> -->
|
||||
|
||||
<body id="page-top">
|
||||
|
||||
<!-- Page Wrapper -->
|
||||
<div id="wrapper">
|
||||
|
||||
|
||||
<div th:include="fragments/admin :: sidebar-fragment"></div>
|
||||
|
||||
<!-- Content Wrapper -->
|
||||
<div id="content-wrapper" class="d-flex flex-column" style="background-color: #333;">
|
||||
|
||||
<!-- Main Content -->
|
||||
<div id="content">
|
||||
|
||||
<div th:include="fragments/admin :: topbar-fragment"></div>
|
||||
|
||||
<!-- Begin Page Content -->
|
||||
<div class="container-fluid">
|
||||
|
||||
<!-- Page Heading -->
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-light">Dashboard</h1>
|
||||
</div>
|
||||
|
||||
<!-- Content Row -->
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-4 mb-4 offset-lg-4">
|
||||
|
||||
<!-- Approach -->
|
||||
<div class="card bg-dark border border-dark text-white shadow mb-4">
|
||||
<div class="card-header bg-secondary border border-secondary py-3">
|
||||
<h6 class="m-0 font-weight-bold text-white">New User</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
page under construction
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- /.container-fluid -->
|
||||
|
||||
</div>
|
||||
<!-- End of Main Content -->
|
||||
|
||||
<div th:include="fragments/admin :: footer"></div>
|
||||
|
||||
</div>
|
||||
<!-- End of Content Wrapper -->
|
||||
|
||||
</div>
|
||||
<!-- End of Page Wrapper -->
|
||||
|
||||
<th:block th:include="fragments/admin :: modal"></th:block>
|
||||
<div th:if="false">
|
||||
<th:block th:include="admin :: modal"></th:block>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
88
src/main/resources/templates/admin/user-profile.html
Normal file
88
src/main/resources/templates/admin/user-profile.html
Normal file
@ -0,0 +1,88 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<div th:replace="fragments/head :: headFragment">
|
||||
<title id="pageTitle" th:text="${userName}">User Profile</title>
|
||||
</div>
|
||||
<script src="https://code.jquery.com/jquery-2.1.4.min.js" th:if="false"></script>
|
||||
<script src="http://blackpeppersoftware.github.io/thymeleaf-fragment.js/thymeleaf-fragment.js"
|
||||
data-template-prefix="../" defer="defer" th:if="false"></script>
|
||||
|
||||
<th:block th:include="fragments/admin :: headFragment"></th:block>
|
||||
|
||||
</head>
|
||||
<!-- TODO
|
||||
Make user admin / remove user from admin
|
||||
Change E2E passphrase
|
||||
Delete Messages
|
||||
-->
|
||||
|
||||
<!-- <div th:include="fragments/admin :: admin-sidebar"></div> -->
|
||||
|
||||
<body id="page-top">
|
||||
|
||||
<!-- Page Wrapper -->
|
||||
<div id="wrapper">
|
||||
|
||||
|
||||
<div th:include="fragments/admin :: sidebar-fragment"></div>
|
||||
|
||||
<!-- Content Wrapper -->
|
||||
<div id="content-wrapper" class="d-flex flex-column" style="background-color: #333;">
|
||||
|
||||
<!-- Main Content -->
|
||||
<div id="content">
|
||||
|
||||
<div th:include="fragments/admin :: topbar-fragment"></div>
|
||||
|
||||
<!-- Begin Page Content -->
|
||||
<div class="container-fluid">
|
||||
|
||||
<!-- Page Heading -->
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-light">Dashboard</h1>
|
||||
<a href="#" class="d-none d-sm-inline-block btn btn-sm btn-primary shadow-sm"><i
|
||||
class="fas fa-download fa-sm text-white-50"></i> Generate Report</a>
|
||||
</div>
|
||||
|
||||
<!-- Content Row -->
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-4 mb-4 offset-lg-4">
|
||||
|
||||
<!-- Approach -->
|
||||
<div class="card bg-dark border border-dark text-white shadow mb-4">
|
||||
<div class="card-header bg-secondary border border-secondary py-3">
|
||||
<h6 class="m-0 font-weight-bold text-white">Development Approach</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div th:text="${userName}">John</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- /.container-fluid -->
|
||||
|
||||
</div>
|
||||
<!-- End of Main Content -->
|
||||
|
||||
<div th:include="fragments/admin :: footer"></div>
|
||||
|
||||
</div>
|
||||
<!-- End of Content Wrapper -->
|
||||
|
||||
</div>
|
||||
<!-- End of Page Wrapper -->
|
||||
|
||||
<th:block th:include="fragments/admin :: modal"></th:block>
|
||||
<div th:if="false">
|
||||
<th:block th:include="admin :: modal"></th:block>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
113
src/main/resources/templates/admin/users.html
Normal file
113
src/main/resources/templates/admin/users.html
Normal file
@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
|
||||
<head>
|
||||
<div th:replace="fragments/head :: headFragment">
|
||||
<title id="pageTitle">Users</title>
|
||||
</div>
|
||||
<script src="https://code.jquery.com/jquery-2.1.4.min.js" th:if="false"></script>
|
||||
<script src="http://blackpeppersoftware.github.io/thymeleaf-fragment.js/thymeleaf-fragment.js"
|
||||
data-template-prefix="../" defer="defer" th:if="false"></script>
|
||||
|
||||
<th:block th:include="fragments/admin :: headFragment"></th:block>
|
||||
|
||||
<style>
|
||||
/* .sidebar {
|
||||
height: 100%;
|
||||
} */
|
||||
</style>
|
||||
</head>
|
||||
<!-- TODO
|
||||
Make user admin / remove user from admin
|
||||
Change E2E passphrase
|
||||
Delete Messages
|
||||
-->
|
||||
|
||||
<!-- <div th:include="fragments/admin :: admin-sidebar"></div> -->
|
||||
|
||||
<body id="page-top">
|
||||
|
||||
<!-- Page Wrapper -->
|
||||
<div id="wrapper">
|
||||
|
||||
|
||||
<div th:include="fragments/admin :: sidebar-fragment"></div>
|
||||
|
||||
<!-- Content Wrapper -->
|
||||
<div id="content-wrapper" class="d-flex flex-column" style="background-color: #333;">
|
||||
|
||||
<!-- Main Content -->
|
||||
<div id="content">
|
||||
|
||||
<div th:include="fragments/admin :: topbar-fragment"></div>
|
||||
|
||||
<!-- Begin Page Content -->
|
||||
<div class="container-fluid">
|
||||
|
||||
<!-- Page Heading -->
|
||||
|
||||
<!-- Content Row -->
|
||||
<div class="row">
|
||||
|
||||
<div class="col">
|
||||
|
||||
<!-- Approach -->
|
||||
<div class="card bg-dark border border-dark text-white shadow mb-4">
|
||||
<div class="card-header bg-secondary border border-secondary py-3">
|
||||
<h6 class="m-0 font-weight-bold text-white">Users</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table data-toggle="table" class="table table-bordered text-white" width="100%"
|
||||
cellspacing="0" id="usersTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User ID</th>
|
||||
<th>User Name</th>
|
||||
<th>Role</th>
|
||||
<th>Registration Date</th>
|
||||
<!-- <th>Action</th> -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="usersTableBody">
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<!-- /.container-fluid -->
|
||||
|
||||
</div>
|
||||
<!-- End of Main Content -->
|
||||
|
||||
<div th:include="fragments/admin :: footer"></div>
|
||||
|
||||
</div>
|
||||
<!-- End of Content Wrapper -->
|
||||
|
||||
</div>
|
||||
<!-- End of Page Wrapper -->
|
||||
|
||||
<th:block th:include="fragments/admin :: modal"></th:block>
|
||||
<div th:if="false">
|
||||
<th:block th:include="admin :: modal"></th:block>
|
||||
</div>
|
||||
|
||||
<template id="users-table-template">
|
||||
<tr>
|
||||
<td>{{id}}</td>
|
||||
<td>{{userName}}</td>
|
||||
<td>{{role}}</td>
|
||||
<td>{{joinDate}}</td>
|
||||
<td><button class="btn btn-info"><i class="fas fa-ellipsis-h"></i></button></td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
@ -13,6 +13,8 @@
|
||||
</div>
|
||||
<script src="./../../javascript/bundle.js" th:src="@{'/js/' + ${@environment.getProperty('chat-bundle')}}"
|
||||
defer></script>
|
||||
<script src="./../../javascript/bundle.js" th:src="@{'/js/' + ${@environment.getProperty('chat-worker-bundle')}}"
|
||||
defer></script>
|
||||
<link rel="stylesheet" th:href="@{/css/chat.css}" href="../../resources/static/css/chat.css">
|
||||
|
||||
</head>
|
||||
@ -128,6 +130,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<th:block th:include="fragments/head :: footerFragment"></th:block>
|
||||
|
||||
<template id="msg_container_template">
|
||||
<div class="d-flex justify-content-start mb-4">
|
||||
<div class="img_cont_msg">
|
||||
|
@ -1,18 +0,0 @@
|
||||
<html>
|
||||
|
||||
<head xmlns:th="http://www.thymeleaf.org">
|
||||
<title></title>
|
||||
<!--
|
||||
<link th:href="@{/css/things.css}" rel="stylesheet"></link>
|
||||
JS
|
||||
<script th:src="@{/js/things.js}" type="text/javascript"></script> -->
|
||||
<script th:src="@{js/scljs.js}" type="text/javascript"></script>
|
||||
<script th:src="@{js/my_Crypto.js}" type="text/javascript"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="crypt"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -7,11 +7,84 @@
|
||||
<title id="pageTitle">Navbar Fragment</title>
|
||||
</div> -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js" th:if="false"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.bundle.js" th:if="false"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.css" th:if="false" rel="stylesheet" type="text/css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.bundle.js"
|
||||
th:if="false"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.css" th:if="false"
|
||||
rel="stylesheet" type="text/css">
|
||||
<script src="https://code.jquery.com/jquery-2.1.4.min.js" th:if="false"></script>
|
||||
<script src="http://blackpeppersoftware.github.io/thymeleaf-fragment.js/thymeleaf-fragment.js" defer="defer" th:if="false"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" th:if="false">
|
||||
<script src="http://blackpeppersoftware.github.io/thymeleaf-fragment.js/thymeleaf-fragment.js" defer="defer"
|
||||
th:if="false"></script>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css"
|
||||
th:if="false">
|
||||
|
||||
|
||||
|
||||
<th:block th:fragment="headFragment">
|
||||
<!-- <script src="https://code.jquery.com/jquery-3.3.1.min.js"
|
||||
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous" defer></script> -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
|
||||
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"
|
||||
defer></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
|
||||
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"
|
||||
defer></script>
|
||||
<!-- <script src="https://unpkg.com/bootstrap-table@1.17.1/dist/bootstrap-table.min.js" defer></script> -->
|
||||
|
||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.21/js/jquery.dataTables.min.js"
|
||||
integrity="sha512-BkpSL20WETFylMrcirBahHfSnY++H2O1W+UnEEO4yNIl+jI2+zowyoGJpbtk6bx97fBXf++WJHSSK2MV4ghPcg=="
|
||||
crossorigin="anonymous"></script> -->
|
||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.21/js/dataTables.bootstrap.min.js"
|
||||
integrity="sha512-F0E+jKGaUC90odiinxkfeS3zm9uUT1/lpusNtgXboaMdA3QFMUez0pBmAeXGXtGxoGZg3bLmrkSkbK1quua4/Q=="
|
||||
crossorigin="anonymous"></script> -->
|
||||
<!-- <link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.21/css/dataTables.bootstrap4.min.css"
|
||||
integrity="sha512-PT0RvABaDhDQugEbpNMwgYBCnGCiTZMh9yOzUsJHDgl/dMhD9yjHAwoumnUk3JydV3QTcIkNDuN40CJxik5+WQ=="
|
||||
crossorigin="anonymous" /> -->
|
||||
|
||||
|
||||
|
||||
<!-- <script th:src="@{/js/admin.js}" src="../../static/js/admin.js" defer="defer"></script> -->
|
||||
<script th:src="@{/js/sb-admin-2.js}" src="../../static/js/sb-admin-2.js" defer="defer"></script>
|
||||
|
||||
|
||||
|
||||
|
||||
<link href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" rel="stylesheet">
|
||||
<link href="http://fonts.googleapis.com/css?family=Roboto:400,700,300" rel="stylesheet" type="text/css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/startbootstrap-sb-admin-2/4.0.7/css/sb-admin-2.min.css" integrity="sha512-FXgL8f6gtCYx8PjODtilf5GCHlgTDdIVZKRcUT/smwfum7hr4M1ytewqTtNd9LK4/CzbW4czU6Tr3f3Xey6lRg==" crossorigin="anonymous" />
|
||||
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
|
||||
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
|
||||
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
|
||||
<!-- <link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.17.1/dist/bootstrap-table.min.css"> -->
|
||||
|
||||
<!-- <link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.21/css/jquery.dataTables.min.css"
|
||||
integrity="sha512-1k7mWiTNoyx2XtmI96o+hdjP8nn0f3Z2N4oF/9ZZRgijyV4omsKOXEnqL1gKQNPy2MTSP9rIEWGcH/CInulptA=="
|
||||
crossorigin="anonymous" /> -->
|
||||
<!-- <link rel="stylesheet" th:href="@{/css/admin-table.css}" href="../../../resources/static/css/admin-table.css"> -->
|
||||
|
||||
<link rel="stylesheet" type="text/css"
|
||||
href="https://cdn.datatables.net/v/bs4/jszip-2.5.0/dt-1.10.21/b-1.6.2/b-html5-1.6.2/datatables.min.css" />
|
||||
|
||||
<script type="text/javascript"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/pdfmake.min.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/vfs_fonts.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="https://cdn.datatables.net/v/bs4/jszip-2.5.0/dt-1.10.21/b-1.6.2/b-html5-1.6.2/datatables.min.js"></script>
|
||||
<script src="https://cdn.datatables.net/select/1.3.1/js/dataTables.select.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.datatables.net/select/1.3.1/css/select.dataTables.min.css">
|
||||
|
||||
<link th:href="@{/css/admin-custom.css}" href="../../static/css/admin-custom.css" rel="stylesheet">
|
||||
|
||||
<script th:src="@{'/js/' + ${@environment.getProperty('admin-bundle')}}" src="../../static/js/adminBundle.js"
|
||||
defer="defer"></script>
|
||||
|
||||
</th:block>
|
||||
|
||||
|
||||
|
||||
<link href="../../static/css/sb-admin-2.css" rel="stylesheet" th:if="false">
|
||||
<link href="../../static/css/admin-custom.css" rel="stylesheet" th:if="false">
|
||||
@ -27,7 +100,8 @@
|
||||
<ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordionSidebar">
|
||||
|
||||
<!-- Sidebar - Brand -->
|
||||
<a class="sidebar-brand d-flex align-items-center justify-content-center" th:href="@{/admin}" href="../admin/home.html">
|
||||
<a class="sidebar-brand d-flex align-items-center justify-content-center" th:href="@{/admin}"
|
||||
href="../admin/home.html">
|
||||
<div class="sidebar-brand-icon rotate-n-15">
|
||||
<i class="fas fa-laugh-wink"></i>
|
||||
</div>
|
||||
@ -54,15 +128,17 @@
|
||||
|
||||
<!-- Nav Item - Pages Collapse Menu -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseTwo">
|
||||
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseOne"
|
||||
aria-expanded="true" aria-controls="collapseTwo">
|
||||
<i class="fas fa-fw fa-cog"></i>
|
||||
<span>Users</span>
|
||||
</a>
|
||||
<div id="collapseOne" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionSidebar">
|
||||
<div class="bg-dark py-2 collapse-inner rounded">
|
||||
<h6 class="collapse-header">Settings:</h6>
|
||||
<a class="collapse-item" th:href="@{/admin/change-passphrase}" href="../admin/change-passphrase.html">Change Passphrase</a>
|
||||
<a class="collapse-item" href="cards.html">Cards</a>
|
||||
<a class="collapse-item" th:href="@{/admin/change-passphrase}"
|
||||
href="../admin/change-passphrase.html">Change Passphrase</a>
|
||||
<a class="collapse-item" th:href="@{/admin/users}">View users</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@ -78,7 +154,8 @@
|
||||
|
||||
<!-- Nav Item - Pages Collapse Menu -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="true" aria-controls="collapseTwo">
|
||||
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseTwo"
|
||||
aria-expanded="true" aria-controls="collapseTwo">
|
||||
<i class="fas fa-fw fa-cog"></i>
|
||||
<span>Components</span>
|
||||
</a>
|
||||
@ -93,11 +170,13 @@
|
||||
|
||||
<!-- Nav Item - Utilities Collapse Menu -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseUtilities" aria-expanded="true" aria-controls="collapseUtilities">
|
||||
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseUtilities"
|
||||
aria-expanded="true" aria-controls="collapseUtilities">
|
||||
<i class="fas fa-fw fa-wrench"></i>
|
||||
<span>Utilities</span>
|
||||
</a>
|
||||
<div id="collapseUtilities" class="collapse" aria-labelledby="headingUtilities" data-parent="#accordionSidebar">
|
||||
<div id="collapseUtilities" class="collapse" aria-labelledby="headingUtilities"
|
||||
data-parent="#accordionSidebar">
|
||||
<div class="bg-dark py-2 collapse-inner rounded">
|
||||
<h6 class="collapse-header">Custom Utilities:</h6>
|
||||
<a class="collapse-item" href="utilities-color.html">Colors</a>
|
||||
@ -118,11 +197,13 @@
|
||||
|
||||
<!-- Nav Item - Pages Collapse Menu -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapsePages" aria-expanded="true" aria-controls="collapsePages">
|
||||
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapsePages"
|
||||
aria-expanded="true" aria-controls="collapsePages">
|
||||
<i class="fas fa-fw fa-folder"></i>
|
||||
<span>Pages</span>
|
||||
</a>
|
||||
<div id="collapsePages" class="collapse" aria-labelledby="headingPages" data-parent="#accordionSidebar">
|
||||
<div id="collapsePages" class="collapse" aria-labelledby="headingPages"
|
||||
data-parent="#accordionSidebar">
|
||||
<div class="bg-dark py-2 collapse-inner rounded">
|
||||
<h6 class="collapse-header">Login Screens:</h6>
|
||||
<a class="collapse-item" href="login.html">Login</a>
|
||||
@ -174,17 +255,19 @@
|
||||
|
||||
<!-- Sidebar Toggle (Topbar) -->
|
||||
<button id="sidebarToggleTop" class="btn btn-link d-md-none rounded-circle mr-3">
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
|
||||
<!-- Topbar Search -->
|
||||
<form class="d-none d-sm-inline-block form-inline mr-auto ml-md-3 my-2 my-md-0 mw-100 navbar-search">
|
||||
<form
|
||||
class="d-none d-sm-inline-block form-inline mr-auto ml-md-3 my-2 my-md-0 mw-100 navbar-search">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control bg-light border-0 small" placeholder="Search for..." aria-label="Search" aria-describedby="basic-addon2">
|
||||
<input type="text" class="form-control bg-light border-0 small"
|
||||
placeholder="Search for..." aria-label="Search" aria-describedby="basic-addon2">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" type="button">
|
||||
<i class="fas fa-search fa-sm"></i>
|
||||
</button>
|
||||
<i class="fas fa-search fa-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@ -195,18 +278,22 @@
|
||||
|
||||
<!-- Nav Item - Search Dropdown (Visible Only XS) -->
|
||||
<li class="nav-item dropdown no-arrow d-sm-none">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="searchDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="searchDropdown" role="button"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-search fa-fw"></i>
|
||||
</a>
|
||||
<!-- Dropdown - Messages -->
|
||||
<div class="dropdown-menu dropdown-menu-right p-3 shadow animated--grow-in" aria-labelledby="searchDropdown">
|
||||
<div class="dropdown-menu dropdown-menu-right p-3 shadow animated--grow-in"
|
||||
aria-labelledby="searchDropdown">
|
||||
<form class="form-inline mr-auto w-100 navbar-search">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control bg-light border-0 small" placeholder="Search for..." aria-label="Search" aria-describedby="basic-addon2">
|
||||
<input type="text" class="form-control bg-light border-0 small"
|
||||
placeholder="Search for..." aria-label="Search"
|
||||
aria-describedby="basic-addon2">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-primary" type="button">
|
||||
<i class="fas fa-search fa-sm"></i>
|
||||
</button>
|
||||
<i class="fas fa-search fa-sm"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@ -221,13 +308,15 @@
|
||||
|
||||
<!-- Nav Item - Alerts -->
|
||||
<li class="nav-item dropdown no-arrow mx-1">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="alertsDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="alertsDropdown" role="button"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-bell fa-fw"></i>
|
||||
<!-- Counter - Alerts -->
|
||||
<span class="badge badge-danger badge-counter">3+</span>
|
||||
</a>
|
||||
<!-- Dropdown - Alerts -->
|
||||
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="alertsDropdown">
|
||||
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in"
|
||||
aria-labelledby="alertsDropdown">
|
||||
<h6 class="dropdown-header">
|
||||
Alerts Center
|
||||
</h6>
|
||||
@ -239,7 +328,8 @@
|
||||
</div>
|
||||
<div>
|
||||
<div class="small text-gray-500">December 12, 2019</div>
|
||||
<span class="font-weight-bold">A new monthly report is ready to download!</span>
|
||||
<span class="font-weight-bold">A new monthly report is ready to
|
||||
download!</span>
|
||||
</div>
|
||||
</a>
|
||||
<a class="dropdown-item d-flex align-items-center" href="#">
|
||||
@ -264,63 +354,76 @@
|
||||
Spending Alert: We've noticed unusually high spending for your account.
|
||||
</div>
|
||||
</a>
|
||||
<a class="dropdown-item text-center small text-gray-500" href="#">Show All Alerts</a>
|
||||
<a class="dropdown-item text-center small text-gray-500" href="#">Show All
|
||||
Alerts</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- Nav Item - Messages -->
|
||||
<li class="nav-item dropdown no-arrow mx-1">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="messagesDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="messagesDropdown" role="button"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-envelope fa-fw"></i>
|
||||
<!-- Counter - Messages -->
|
||||
<span class="badge badge-danger badge-counter">7</span>
|
||||
</a>
|
||||
<!-- Dropdown - Messages -->
|
||||
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="messagesDropdown">
|
||||
<div class="dropdown-list dropdown-menu dropdown-menu-right shadow animated--grow-in"
|
||||
aria-labelledby="messagesDropdown">
|
||||
<h6 class="dropdown-header">
|
||||
Message Center
|
||||
</h6>
|
||||
<a class="dropdown-item d-flex align-items-center" href="#">
|
||||
<div class="dropdown-list-image mr-3">
|
||||
<img class="rounded-circle" src="https://source.unsplash.com/fn_BT9fwg_E/60x60" alt="">
|
||||
<img class="rounded-circle"
|
||||
src="https://source.unsplash.com/fn_BT9fwg_E/60x60" alt="">
|
||||
<div class="status-indicator bg-success"></div>
|
||||
</div>
|
||||
<div class="font-weight-bold">
|
||||
<div class="text-truncate">Hi there! I am wondering if you can help me with a problem I've been having.</div>
|
||||
<div class="text-truncate">Hi there! I am wondering if you can help me with
|
||||
a problem I've been having.</div>
|
||||
<div class="small text-gray-500">Emily Fowler · 58m</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="dropdown-item d-flex align-items-center" href="#">
|
||||
<div class="dropdown-list-image mr-3">
|
||||
<img class="rounded-circle" src="https://source.unsplash.com/AU4VPcFN4LE/60x60" alt="">
|
||||
<img class="rounded-circle"
|
||||
src="https://source.unsplash.com/AU4VPcFN4LE/60x60" alt="">
|
||||
<div class="status-indicator"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-truncate">I have the photos that you ordered last month, how would you like them sent to you?</div>
|
||||
<div class="text-truncate">I have the photos that you ordered last month,
|
||||
how would you like them sent to you?</div>
|
||||
<div class="small text-gray-500">Jae Chun · 1d</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="dropdown-item d-flex align-items-center" href="#">
|
||||
<div class="dropdown-list-image mr-3">
|
||||
<img class="rounded-circle" src="https://source.unsplash.com/CS2uCrpNzJY/60x60" alt="">
|
||||
<img class="rounded-circle"
|
||||
src="https://source.unsplash.com/CS2uCrpNzJY/60x60" alt="">
|
||||
<div class="status-indicator bg-warning"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-truncate">Last month's report looks great, I am very happy with the progress so far, keep up the good work!</div>
|
||||
<div class="text-truncate">Last month's report looks great, I am very happy
|
||||
with the progress so far, keep up the good work!</div>
|
||||
<div class="small text-gray-500">Morgan Alvarez · 2d</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="dropdown-item d-flex align-items-center" href="#">
|
||||
<div class="dropdown-list-image mr-3">
|
||||
<img class="rounded-circle" src="https://source.unsplash.com/Mv9hjnEUHR4/60x60" alt="">
|
||||
<img class="rounded-circle"
|
||||
src="https://source.unsplash.com/Mv9hjnEUHR4/60x60" alt="">
|
||||
<div class="status-indicator bg-success"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-truncate">Am I a good boy? The reason I ask is because someone told me that people say this to all dogs, even if they aren't good...</div>
|
||||
<div class="text-truncate">Am I a good boy? The reason I ask is because
|
||||
someone told me that people say this to all dogs, even if they aren't
|
||||
good...</div>
|
||||
<div class="small text-gray-500">Chicken the Dog · 2w</div>
|
||||
</div>
|
||||
</a>
|
||||
<a class="dropdown-item text-center small text-gray-500" href="#">Read More Messages</a>
|
||||
<a class="dropdown-item text-center small text-gray-500" href="#">Read More
|
||||
Messages</a>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@ -328,7 +431,8 @@
|
||||
|
||||
<!-- Nav Item - User Information -->
|
||||
<li class="nav-item dropdown no-arrow">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="mr-2 d-none d-lg-inline text-white small" th:text="${#authentication.name}">Valerie Luna</span>
|
||||
<img class="img-profile rounded-circle" src="https://source.unsplash.com/QAB-WJcbgJk/60x60">
|
||||
</a>
|
||||
|
@ -64,6 +64,9 @@
|
||||
let loglevel = /*[[${@environment.getProperty('chatto.frontend.log-level')}]]*/ "DEBUG";
|
||||
window.log.setLevel(loglevel)
|
||||
|
||||
let chatWorkerBundle = /*[[${@environment.getProperty('chat-worker-bundle')}]]*/ "chatWorker.js";
|
||||
localStorage.setItem('CHAT_WORKER_BUNDLE', "/js/" + chatWorkerBundle)
|
||||
|
||||
let chatPageSize = /*[[${@environment.getProperty('chatto.frontend.chat-page-size')}]]*/ 9;
|
||||
localStorage.setItem("CHAT_PAGE_SIZE", chatPageSize);
|
||||
</script>
|
||||
@ -75,6 +78,27 @@
|
||||
|
||||
<body>
|
||||
|
||||
<th:block th:fragment="footerFragment">
|
||||
<!-- Footer -->
|
||||
<footer class="footer text-small py-2 bg-dark">
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row justify-content-between">
|
||||
|
||||
<div class="col-md-4 text-center text-md-left ">Chatto Version:
|
||||
<strong th:text="${@environment.getProperty('git.build.version')}">None
|
||||
</strong>
|
||||
</div>
|
||||
<div class="col-md-4 text-center text-md-right">Branch <strong
|
||||
th:text="${@environment.getProperty('git.branch')}">
|
||||
None</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- Footer -->
|
||||
</th:block>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
@ -6,10 +6,13 @@
|
||||
<meta charset="UTF-8">
|
||||
<title id="pageTitle">Navbar Fragment</title>
|
||||
</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.15.0/esm/popper.js" type="module" th:if="false"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.css" th:if="false" rel="stylesheet" type="text/css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.15.0/esm/popper.js" type="module"
|
||||
th:if="false"></script>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.css" th:if="false"
|
||||
rel="stylesheet" type="text/css">
|
||||
<script src="https://code.jquery.com/jquery-2.1.4.min.js" th:if="false"></script>
|
||||
<script src="http://blackpeppersoftware.github.io/thymeleaf-fragment.js/thymeleaf-fragment.js" defer="defer" th:if="false"></script>
|
||||
<script src="http://blackpeppersoftware.github.io/thymeleaf-fragment.js/thymeleaf-fragment.js" defer="defer"
|
||||
th:if="false"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -18,47 +21,6 @@
|
||||
<nav class="navbar navbar-expand-sm bg-dark navbar-dark fixed-top">
|
||||
<div class="container">
|
||||
<a href="home.html" th:href="@{/}" class="navbar-brand">Chatto</a>
|
||||
<!-- <div class="navbar-header">
|
||||
<button class="navbar-toggler" data-toggle="collapse" data-target="#navbarCollapse">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a href="home.html" th:href="@{/}" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="user/home.html" sec:authorize="isFullyAuthenticated()" th:href="@{/user}" class="nav-link">User Area</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a th:href="chat" href="chat.html" class="nav-link">Chat</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a th:href="login" sec:authorize="!isFullyAuthenticated()" href="login.html" class="nav-link">Login</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a th:href="registration" sec:authorize="!isFullyAuthenticated()" href="registration.html" class="nav-link">Register</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="nav-link">Contact</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" sec:authorize="isFullyAuthenticated()" th:href="@{/admin}" class="nav-link">
|
||||
Admin Area
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" sec:authorize="isFullyAuthenticated()" th:text="${#authentication.name}" class="nav-link text-white font-weight-bold">
|
||||
nova
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div> -->
|
||||
<th:block th:include="fragments/navbar :: navbarContent"></th:block>
|
||||
</div>
|
||||
</nav>
|
||||
@ -66,42 +28,46 @@
|
||||
</th:block>
|
||||
|
||||
<th:block th:fragment="navbarContent">
|
||||
<div class="navbar-header">
|
||||
<button class="navbar-toggler" data-toggle="collapse" data-target="#navbarCollapse">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a href="home.html" th:href="@{/}" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="user/home.html" sec:authorize="isFullyAuthenticated()" th:href="@{/user}" class="nav-link">User Area</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a th:href="chat" href="chat.html" class="nav-link">Chat</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a th:href="login" sec:authorize="!isFullyAuthenticated()" href="login.html" class="nav-link">Login</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a th:href="registration" sec:authorize="!isFullyAuthenticated()" href="registration.html" class="nav-link">Register</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="nav-link">Contact</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" sec:authorize="hasRole('ROLE_ADMIN') || hasRole('ROLE_SUPER_USER')" th:href="@{/admin}" class="nav-link">
|
||||
Admin Area
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" sec:authorize="isFullyAuthenticated()" th:text="${#authentication.name}" class="nav-link text-white font-weight-bold">
|
||||
<div class="navbar-header">
|
||||
<button class="navbar-toggler" data-toggle="collapse" data-target="#navbarCollapse">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a href="home.html" th:href="@{/}" class="nav-link">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="user/home.html" sec:authorize="isFullyAuthenticated()" th:href="@{/user}"
|
||||
class="nav-link">User Area</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a th:href="@{/chat}" href="chat.html" class="nav-link">Chat</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a th:href="login" sec:authorize="!isFullyAuthenticated()" href="login.html"
|
||||
class="nav-link">Login</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a th:href="registration" sec:authorize="!isFullyAuthenticated()" href="registration.html"
|
||||
class="nav-link">Register</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="nav-link">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="nav-link">Contact</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" sec:authorize="hasRole('ROLE_ADMIN') || hasRole('ROLE_SUPER_USER')" th:href="@{/admin}"
|
||||
class="nav-link">
|
||||
Admin Area
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" sec:authorize="isFullyAuthenticated()" th:text="${#authentication.name}" class="nav-link text-white font-weight-bold">
|
||||
nova
|
||||
</a>
|
||||
</li>
|
||||
|
@ -12,7 +12,8 @@
|
||||
|
||||
|
||||
<script src="https://code.jquery.com/jquery-2.1.4.min.js" th:if="false"></script>
|
||||
<script src="http://blackpeppersoftware.github.io/thymeleaf-fragment.js/thymeleaf-fragment.js" defer="defer" th:if="false"></script>
|
||||
<script src="http://blackpeppersoftware.github.io/thymeleaf-fragment.js/thymeleaf-fragment.js" defer="defer"
|
||||
th:if="false"></script>
|
||||
|
||||
|
||||
</link>
|
||||
@ -93,19 +94,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<section id="my-section">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col text-center py-5">
|
||||
<h1 class="display-4">
|
||||
<p class="lead">Lorem ipsum dolor sit, amet consectetur adipisicing elit. Aliquid illum ea accusamus animi voluptate. Quam temporibus aperiam, similique in labore sint quasi harum. Praesentium enim iste dicta quaerat perspiciatis eos.</p>
|
||||
</h1>
|
||||
<a href="#" class="btn btn-secondary">Find out more</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<th:block th:include="fragments/head :: footerFragment"></th:block>
|
||||
</body>
|
||||
|
||||
</html>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user