Merge pull request 'Initial' (#1) from master into main

Reviewed-on: #1
This commit is contained in:
2023-08-19 19:08:07 +00:00
23 changed files with 1688 additions and 0 deletions

35
.gitignore vendored Normal file
View File

@@ -0,0 +1,35 @@
HELP.md
target/
testfiles/
jte-classes/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### 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/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

BIN
.mvn/wrapper/maven-wrapper.jar vendored Normal file

Binary file not shown.

2
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar

316
mvnw vendored Executable file
View File

@@ -0,0 +1,316 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`\\unset -f command; \\command -v java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

188
mvnw.cmd vendored Normal file
View File

@@ -0,0 +1,188 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%

218
pom.xml Normal file
View File

@@ -0,0 +1,218 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.github.russp</groupId>
<artifactId>bpe</artifactId>
<version>0.0.2-SNAPSHOT</version>
<name>bpe</name>
<description>Bad-practice editor</description>
<properties>
<java.version>17</java.version>
<jte.version>3.0.3</jte.version>
<repackage.classifier/>
<spring-native.version>0.12.1</spring-native.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>${spring-native.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte</artifactId>
<version>${jte.version}</version>
</dependency>
<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte-spring-boot-starter-2</artifactId>
<version>${jte.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
<classifier>${repackage.classifier}</classifier>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>${spring-native.version}</version>
<executions>
<execution>
<id>test-generate</id>
<goals>
<goal>test-generate</goal>
</goals>
</execution>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>gg.jte</groupId>
<artifactId>jte-maven-plugin</artifactId>
<version>${jte.version}</version>
<configuration>
<sourceDirectory>${basedir}/src/main/jte</sourceDirectory>
<contentType>Html</contentType>
<targetResourceDirectory>${project.build.directory}/classes</targetResourceDirectory>
<extensions>
<extension>
<className>gg.jte.nativeimage.NativeResourcesExtension</className>
</extension>
</extensions>
</configuration>
<executions>
<execution>
<id>jte-gen</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte-native-resources</artifactId>
<version>${jte.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<profiles>
<profile>
<id>native</id>
<properties>
<repackage.classifier>exec</repackage.classifier>
<native-buildtools.version>0.9.13</native-buildtools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native-buildtools.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<id>test-native</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
<execution>
<id>build-native</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,13 @@
package com.github.russp.bpe;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BpeApplication {
public static void main(String[] args) {
SpringApplication.run(BpeApplication.class, args);
}
}

View File

@@ -0,0 +1,29 @@
package com.github.russp.bpe.config;
import gg.jte.springframework.boot.autoconfigure.ReactiveJteAutoConfiguration;
import gg.jte.springframework.boot.autoconfigure.ReactiveJteViewResolver;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.Locale;
@Slf4j
@Configuration
@ImportAutoConfiguration(ReactiveJteAutoConfiguration.class)
@RequiredArgsConstructor
public class JteConfiguration {
private final ReactiveJteViewResolver jteViewResolver;
@PostConstruct
public void vr() {
log.info("ReactiveJteViewResolver created: {}", jteViewResolver);
jteViewResolver.resolveViewName("login", Locale.ENGLISH)
.log()
.block();
}
}

View File

@@ -0,0 +1,52 @@
package com.github.russp.bpe.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
@EnableWebFluxSecurity
public class SecurityConfig {
@Value("${app.username}")
private String username;
@Value("${app.password}")
private String password;
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.authorizeExchange()
.pathMatchers("/login", "/*.css", "/*.js").permitAll()
.anyExchange().authenticated()
.and().formLogin().loginPage("/login")
.and().logout()
.requiresLogout(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/logout"))
.and()
.build();
}
@Bean
public MapReactiveUserDetailsService userDetailsService() {
UserDetails user = User
.withUsername(username)
.password(passwordEncoder().encode(password))
.roles("USER")
.build();
password = "";
return new MapReactiveUserDetailsService(user);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@@ -0,0 +1,92 @@
package com.github.russp.bpe.http;
import com.github.russp.bpe.service.FilesService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.MediaType;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ResponseBody;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.file.Paths;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
@Slf4j
@Controller
@RequiredArgsConstructor
public class BrowserController {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
private final FilesService filesService;
@GetMapping("/")
public String list(@RequestParam(defaultValue = "/") String path, Model model) {
log.info("Path: {}", path);
var files = filesService.ls(path)
.map(file -> FileDto.builder()
.dir(file.isDir())
.name(file.getName())
.fullName(file.getFullName())
.size(String.valueOf(file.getSize()))
.modifiedAt(file.getModifiedAt().format(DATE_TIME_FORMATTER))
.build())
.collectList();
var breadcrumbs = filesService.pwd(path).map(file -> FileDto.builder()
.name(file.getName())
.fullName(file.getFullName())
.build())
.collect(
ArrayList::new,
(list, value) -> list.add(0, value)
);
model.addAttribute("files", files);
model.addAttribute("breadcrumbs", breadcrumbs);
return "browse";
}
@GetMapping("edit")
public String edit(@RequestParam String path, Model model) {
var breadcrumbs = filesService.parent(path).map(file -> FileDto.builder()
.name(file.getName())
.fullName(file.getFullName())
.build())
.collect(
ArrayList::new,
(list, value) -> list.add(0, value)
);
model.addAttribute("breadcrumbs", breadcrumbs);
model.addAttribute("selectedFile", path);
model.addAttribute("selectedFileName", Paths.get(path).getFileName().toString());
return "edit";
}
@GetMapping(value = "/content",
produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public @ResponseBody Flux<DataBuffer> content(String path) {
return filesService.content(path);
}
@PostMapping(value = "/content")
public @ResponseBody Mono<Void> save(@RequestParam String path,
@RequestPart("file") Mono<FilePart> filePartMono) {
return filePartMono
.flatMap(fp -> filesService.save(path, fp::transferTo))
.then(Mono.empty());
}
}

View File

@@ -0,0 +1,21 @@
package com.github.russp.bpe.http;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FileDto {
private String name;
private String size;
private String modifiedAt;
private boolean dir;
private String fullName;
}

View File

@@ -0,0 +1,16 @@
package com.github.russp.bpe.http;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/login")
public class LoginController {
@GetMapping
public String login() {
return "login";
}
}

View File

@@ -0,0 +1,19 @@
package com.github.russp.bpe.http;
import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor;
import org.springframework.security.web.server.csrf.CsrfToken;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@ControllerAdvice
public class SecurityControllerAdvice {
@ModelAttribute(CsrfRequestDataValueProcessor.DEFAULT_CSRF_ATTR_NAME)
Mono<CsrfToken> csrfToken(ServerWebExchange exchange) {
return exchange.getAttribute(CsrfToken.class.getName());
}
}

View File

@@ -0,0 +1,27 @@
package com.github.russp.bpe.model;
import lombok.Builder;
import lombok.Getter;
import java.time.LocalDateTime;
@Getter
@Builder
public class FileEntry implements Comparable<FileEntry> {
private final boolean dir;
private final String name;
private final String fullName;
private final long size;
private final LocalDateTime modifiedAt;
@Override
public int compareTo(FileEntry o) {
if (dir && !o.isDir()) {
return -1;
}
if (!dir && o.isDir()) {
return 1;
}
return name.compareTo(o.getName());
}
}

View File

@@ -0,0 +1,126 @@
package com.github.russp.bpe.service;
import com.github.russp.bpe.model.FileEntry;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.function.Function;
@Slf4j
@Service
@RequiredArgsConstructor
public class FilesService {
@Value("${app.fs.root}")
private String root;
public Flux<FileEntry> ls(String path) {
try {
var rootPath = Paths.get(root);
return Flux.fromStream(Files.list(Path.of(root, path))
.map(p -> FileEntry.builder()
.name(p.getFileName().toString())
.fullName(p.toString().replace(root, ""))
.dir(Files.isDirectory(p))
.modifiedAt(getLastModifiedTime(p))
.size(size(p))
.build()
)
.sorted()
);
} catch (IOException e) {
log.debug("Listing error for path {}: {}", path, e.getMessage());
return Flux.error(e);
}
}
public Flux<FileEntry> parent(String path) {
var parent = Path.of(path).getParent();
if (parent == null) {
return Flux.empty();
}
return pwd(Path.of(path).getParent().toString());
}
public Flux<FileEntry> pwd(String path) {
if (!StringUtils.hasText(path)) {
return Flux.empty();
}
var rootPath = Path.of(root);
var end = Path.of(root, path);
return Flux.<Path, Path>generate(() -> end, (state, sink) -> {
if (rootPath.equals(state)) {
sink.complete();
}
sink.next(state);
return state.getParent();
})
.map(p -> FileEntry.builder()
.name(p.getFileName().toString())
.fullName(p.toString().replace(root, ""))
.dir(Files.isDirectory(p))
.build()
);
}
public Flux<DataBuffer> content(String path) {
var file = Path.of(root, path);
return DataBufferUtils.read(file, DefaultDataBufferFactory.sharedInstance, 1024, StandardOpenOption.READ);
}
public Mono<Void> save(String path, Function<Path, Mono<Void>> transferTo) {
return Mono.just(Path.of(root, path))
.doOnNext(fn -> {
var file = fn.toFile();
try {
FileUtils.moveFile(file, new File(fn.toString() + ".bak"), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
log.error("Can not create backup file for {}", path, e);
// throw new RuntimeException(e);
}
})
.flatMap(transferTo);
}
private static LocalDateTime getLastModifiedTime(Path path) {
try {
return LocalDateTime.ofInstant(
Files.getLastModifiedTime(path).toInstant(),
ZoneId.systemDefault());
} catch (IOException e) {
return LocalDateTime.MIN;
}
}
private static long size(Path path) {
try {
if (Files.isRegularFile(path)) {
return Files.size(path);
}
} catch (IOException e) {
log.debug("Size error for {}: {}", path, e.getMessage());
}
return 0;
}
}

0
src/main/jte/.jteroot Normal file
View File

104
src/main/jte/browse.jte Normal file
View File

@@ -0,0 +1,104 @@
@import java.util.List
@import com.github.russp.bpe.http.FileDto
@param List<FileDto> files
@param List<FileDto> breadcrumbs
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Browser">
<title>Index of </title>
<link rel="shortcut icon" href="https://picocss.com/favicon.ico">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.4/css/all.css"
integrity="sha384-DyZ88mC6Up2uqS4h/KRgHuoeGwBcD4Ng9SiP4dIRy0EXTlnuz47vAwmeGwVChigm" crossorigin="anonymous"/>
<!-- Pico.css -->
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
<!-- Custom template-->
<link rel="stylesheet" href="/stylesheet.css">
<style></style>
</head>
<body>
<header class="container">
<h5>Browse</h5>
<nav>
<ul class="breadcrumbs">
<li><a href="/"><i class="fa fa-home"></i></a></li>
@for(FileDto breadcrumb : breadcrumbs)
<li><a href="/?path=${breadcrumb.getFullName()}">${breadcrumb.getName()}</a></li>
@endfor
</ul>
</nav>
</header>
<!-- Main -->
<main class="container">
<!-- Tables -->
<section id="tables">
<figure>
<table role="grid">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Date</th>
<th scope="col">Size</th>
</tr>
</thead>
<tbody>
@for(FileDto file : files)
<tr>
<th scope="row">
@if(file.isDir())
<a href="/?path=${file.getFullName()}">
<i class="fa fa-folder"></i>
${file.getName()}
</a>
@else
<a href="/edit?path=${file.getFullName()}">
<i class="fa fa-file"></i>
${file.getName()}
</a>
@endif
</th>
<td>${file.getModifiedAt()}</td>
<td>${file.getSize()}</td>
</tr>
@endfor
</tbody>
</table>
</figure>
</section><!-- ./ Tables -->
</main><!-- ./ Main -->
<!-- Footer -->
<footer class="container">
<details role="list">
<summary aria-haspopup="listbox" role="button" class="secondary">Theme</summary>
<ul role="listbox">
<li><a href="#" data-theme-switcher="auto">Auto</a></li>
<li><a href="#" data-theme-switcher="light">Light</a></li>
<li><a href="#" data-theme-switcher="dark">Dark</a></li>
</ul>
</details>
<small>Built with <a href="https://picocss.com">Pico</a> • <a
href="https://github.com/picocss/examples/blob/master/basic-template/">Source code</a></small>
</footer><!-- ./ Footer -->
<!-- Minimal theme switcher -->
<script src="../js/minimal-theme-switcher.js"></script>
</body>
</html>

147
src/main/jte/edit.jte Normal file
View File

@@ -0,0 +1,147 @@
@import java.util.List
@import org.springframework.security.web.server.csrf.CsrfToken
@import com.github.russp.bpe.http.FileDto
@param String selectedFile
@param String selectedFileName
@param List<FileDto> breadcrumbs
@param CsrfToken _csrf
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Browser">
<meta name="_csrf" content="${_csrf.getToken()}"/>
<meta name="_csrf_header" content="${_csrf.getHeaderName()}"/>
<title>Edit ${selectedFileName}</title>
<link rel="shortcut icon" href="https://picocss.com/favicon.ico">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.4/css/all.css"
integrity="sha384-DyZ88mC6Up2uqS4h/KRgHuoeGwBcD4Ng9SiP4dIRy0EXTlnuz47vAwmeGwVChigm" crossorigin="anonymous"/>
<!-- Pico.css -->
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
<!-- Custom template-->
<link rel="stylesheet" href="/stylesheet.css">
</head>
<body>
<header class="container">
<h5>Edit ${selectedFileName}</h5>
<nav>
<ul class="breadcrumbs">
<li><a href="/"><i class="fa fa-home"></i></a></li>
@for(FileDto breadcrumb : breadcrumbs)
<li><a href="/?path=${breadcrumb.getFullName()}">${breadcrumb.getName()}</a></li>
@endfor
<li>${selectedFileName}</li>
</ul>
<ul>
<li><a href="javascript:save()" role="button">Save</a></li>
</ul>
</nav>
</header>
<!-- Main -->
<main class="container">
<section id="editor">
<figure>
<div id="editor"></div>
</figure>
</section>
</main><!-- ./ Main -->
<!-- Footer -->
<footer class="container">
<small>Built with <a href="https://picocss.com">Pico</a> • <a
href="https://github.com/picocss/examples/blob/master/basic-template/">Source code</a></small>
</footer><!-- ./ Footer -->
<!-- Minimal theme switcher -->
<script src="../js/minimal-theme-switcher.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.12.5/ace.js"
integrity="sha512-gLQA+KKlMRzGRNhdvGX+3F5UHojWkIIKvG2lNQk0ZM5QUbdG17/hDobp06zXMthrJrd4U1+boOEACntTGlPjJQ=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.12.5/ext-modelist.min.js"
integrity="sha512-gluOZJrbb4P8hFk1M2HREivZlXwZd0Uf1i5LLt6NRHjjnu7+4eE/yaYH/m82kMTU557+Q2xJhMmqtzMBBh8o/g=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/3.6.2/fetch.min.js"
integrity="sha512-1Gn7//DzfuF67BGkg97Oc6jPN6hqxuZXnaTpC9P5uw8C6W4yUNj5hoS/APga4g1nO2X6USBb/rXtGzADdaVDeA=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css">
<script>
var modelist = require("ace/ext/modelist");
var mode = modelist.getModeForPath("${selectedFileName}");
var editor = ace.edit("editor",
{
mode: mode.mode,
selectionStyle: "text",
theme: "ace/theme/dracula"
});
document.getElementById('editor').style.fontSize = '1.2em';
fetch('/content?path=${selectedFile}')
.then(function (response) {
return response.text()
})
.then(function (body) {
editor.setValue(body);
editor.clearSelection();
editor.gotoLine(1);
});
function save() {
var blob = new Blob([editor.getValue()]);
var formData = new FormData();
formData.set("file", blob);
fetch('/content?path=${selectedFile}', {
method: 'POST',
credentials: 'include',
headers: {
"${_csrf.getHeaderName()}": "${_csrf.getToken()}"
},
body: formData,
})
.then(function(ok) {
Toastify({
text: "Saved",
duration: 3000,
close: true,
gravity: "top",
position: "right",
stopOnFocus: true,
style: {
background: "green",
},
onClick: function(){} // Callback after click
}).showToast();
}).catch(function(ex) {
Toastify({
text: "Error",
duration: 3000,
close: true,
gravity: "top",
position: "right",
stopOnFocus: true,
style: {
background: "red",
},
onClick: function(){} // Callback after click
}).showToast();
})
}
</script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/toastify-js"></script>
</body>
</html>

130
src/main/jte/login.jte Normal file
View File

@@ -0,0 +1,130 @@
@import org.springframework.security.web.server.csrf.CsrfToken
@param CsrfToken _csrf
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Browser">
<title>Login</title>
<link rel="shortcut icon" href="https://picocss.com/favicon.ico">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.4/css/all.css"
integrity="sha384-DyZ88mC6Up2uqS4h/KRgHuoeGwBcD4Ng9SiP4dIRy0EXTlnuz47vAwmeGwVChigm" crossorigin="anonymous"/>
<!-- Pico.css -->
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
<!-- Custom template-->
<!-- <link rel="stylesheet" href="/stylesheet.css">-->
<style>
/* Grid */
body > main {
display: flex;
flex-direction: column;
justify-content: center;
min-height: calc(100vh - 7rem);
padding: 1rem 0;
}
article {
padding: 0;
overflow: hidden;
}
article div {
padding: 1rem;
}
@media (min-width: 576px) {
body > main {
padding: 1.25rem 0;
}
article div {
padding: 1.25rem;
}
}
@media (min-width: 768px) {
body > main {
padding: 1.5rem 0;
}
article div {
padding: 1.5rem;
}
}
@media (min-width: 992px) {
body > main {
padding: 1.75rem 0;
}
article div {
padding: 1.75rem;
}
}
@media (min-width: 1200px) {
body > main {
padding: 2rem 0;
}
article div {
padding: 2rem;
}
}
/* Nav */
summary[role="link"].secondary:is([aria-current],:hover,:active,:focus) {
background-color: transparent;
color: var(--secondary-hover);
}
@media (min-width: 992px) {
.grid > div:nth-of-type(2) {
display: block;
}
}
/* Footer */
body > footer {
padding: 1rem 0;
}
</style>
</head>
<body>
<!-- Main -->
<main class="container">
<article class="grid">
<div>
<hgroup>
<h1>Sign in</h1>
</hgroup>
<form method="post">
<input type="text" name="username" placeholder="Username" aria-label="Login" autocomplete="nickname" required>
<input type="password" name="password" placeholder="Password" aria-label="Password" autocomplete="current-password" required>
<input type="hidden" name="${_csrf.getParameterName()}" value="${_csrf.getToken()}"/>
<button type="submit" class="contrast">Login</button>
</form>
</div>
<div></div>
</article>
</main><!-- ./ Main -->
<!-- Footer -->
<footer class="container">
<small>Built with <a href="https://picocss.com">Pico</a> • <a
href="https://github.com/picocss/examples/blob/master/basic-template/">Source code</a></small>
</footer><!-- ./ Footer -->
</body>
</html>

View File

@@ -0,0 +1,13 @@
app:
fs:
root: ${APP_FS_ROOT:/app/fs}
bak:
count: 5
username: ${APP_USERNAME:admin}
password: ${APP_PASSWORD:admin}
server:
port: 8080
logging:
level:
org.springframework.web: DEBUG

View File

@@ -0,0 +1,89 @@
/*!
* Minimal theme switcher
*
* Pico.css - https://picocss.com
* Copyright 2019-2022 - Licensed under MIT
*/
const themeSwitcher = {
// Config
_scheme: "auto",
menuTarget: "details[role='list']",
buttonsTarget: "a[data-theme-switcher]",
buttonAttribute: "data-theme-switcher",
rootAttribute: "data-theme",
localStorageKey: "picoPreferedColorScheme",
// Init
init() {
this.scheme = this.schemeFromLocalStorage;
this.initSwitchers();
},
// Get color scheme from local storage
get schemeFromLocalStorage() {
if (typeof window.localStorage !== "undefined") {
if (window.localStorage.getItem(this.localStorageKey) !== null) {
return window.localStorage.getItem(this.localStorageKey);
}
}
return this._scheme;
},
// Prefered color scheme
get preferedColorScheme() {
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
},
// Init switchers
initSwitchers() {
const buttons = document.querySelectorAll(this.buttonsTarget);
buttons.forEach((button) => {
button.addEventListener("click", event => {
event.preventDefault();
// Set scheme
this.scheme = button.getAttribute(this.buttonAttribute);
// Close dropdown
document.querySelector(this.menuTarget).removeAttribute("open");
}, false);
});
},
// Set scheme
set scheme(scheme) {
if (scheme == "auto") {
this.preferedColorScheme == "dark"
? (this._scheme = "dark")
: (this._scheme = "light");
} else if (scheme == "dark" || scheme == "light") {
this._scheme = scheme;
}
this.applyScheme();
this.schemeToLocalStorage();
},
// Get scheme
get scheme() {
return this._scheme;
},
// Apply scheme
applyScheme() {
document
.querySelector("html")
.setAttribute(this.rootAttribute, this.scheme);
},
// Store scheme to local storage
schemeToLocalStorage() {
if (typeof window.localStorage !== "undefined") {
window.localStorage.setItem(this.localStorageKey, this.scheme);
}
},
};
// Init
themeSwitcher.init();

View File

@@ -0,0 +1,38 @@
/* Global CSS variables */
:root {
--spacing: 0.1em;
--t-ypography-spacing-vertical: 0px;
}
#editor {
font-size: 1.2em;
position: relative;
width: 100%;
height: 75vh;
}
ul.breadcrumbs > li {
padding-left: 2px;
padding-right: 2px;
}
ul.breadcrumbs > li:after {
content: "/";
text-indent: 4px;
color: var(--primary);
}
ul.breadcrumbs > li:last-child:after {
content: "";
}
/*body > header {*/
/* padding-top: 0px;*/
/* padding-bottom: 0px;*/
/*}*/
/*body > main {*/
/* padding-top: 16px;*/
/* padding-bottom: 16px;*/
/*}*/

View File

@@ -0,0 +1,13 @@
package com.github.russp.bpe;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class BpeApplicationTests {
@Test
void contextLoads() {
}
}