Merge pull request #63 from liosha84/55-store-uploads-folder-in-docker-volume-for-production
55 store uploads folder in docker volume for production
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
name: Docker Compose Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Install docker-compose (if needed)
|
||||
run: sudo apt-get install docker-compose
|
||||
|
||||
- name: Build and push Docker images
|
||||
run: |
|
||||
cd src/Docker
|
||||
docker-compose build
|
||||
@@ -0,0 +1,67 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle
|
||||
|
||||
name: Java CI with Gradle
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 24
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '24'
|
||||
distribution: 'temurin'
|
||||
|
||||
# Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies.
|
||||
# See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
|
||||
|
||||
- name: Build with Gradle Wrapper
|
||||
run: ./gradlew build
|
||||
|
||||
# NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html).
|
||||
# If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version.
|
||||
#
|
||||
# - name: Setup Gradle
|
||||
# uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
|
||||
# with:
|
||||
# gradle-version: '8.9'
|
||||
#
|
||||
# - name: Build with Gradle 8.9
|
||||
# run: gradle build
|
||||
|
||||
dependency-submission:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up JDK 24
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '24'
|
||||
distribution: 'temurin'
|
||||
|
||||
# Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies.
|
||||
# See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md
|
||||
- name: Generate and submit dependency graph
|
||||
uses: gradle/actions/dependency-submission@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
|
||||
@@ -48,3 +48,6 @@ out/
|
||||
|
||||
/logs/
|
||||
|
||||
/.idea/
|
||||
|
||||
/uploads/
|
||||
|
||||
Generated
+5
@@ -16,5 +16,10 @@
|
||||
<option name="name" value="MavenRepo" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven" />
|
||||
<option name="name" value="maven" />
|
||||
<option name="url" value="https://jitpack.io" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
FROM openjdk:24
|
||||
WORKDIR /jambotron/
|
||||
COPY './build/libs/jambotron-0.0.1-SNAPSHOT.jar' '/app/jambotron.jar'
|
||||
COPY '/build/libs/jambotron-0.0.1-SNAPSHOT.jar' '/app/jambotron.jar'
|
||||
#COPY --from=BUILD_IMAGE '/jambotron/build/libs/jambotron-0.0.1-SNAPSHOT.jar' '/app/jambotron.jar'
|
||||
EXPOSE 8080
|
||||
CMD ["java","-jar","/app/jambotron.jar"]
|
||||
CMD ["java","-jar","/app/jambotron.jar"]
|
||||
@@ -108,6 +108,42 @@ docker-compose -f src/Docker/docker-compose.yml up -d
|
||||
```
|
||||
- For connection from pgadmin use
|
||||
hostename: **host.docker.internal**
|
||||
|
||||
# How to run the application in Docker with HTTPS support
|
||||
|
||||
To run the application in Docker with HTTPS support, follow these steps:
|
||||
|
||||
1. **Cerbot**
|
||||
|
||||
- Run this command below to get certificate and store it to volume certs.
|
||||
|
||||
- Expanded(readable) command example:
|
||||
|
||||
docker run -d \
|
||||
--name certbot \
|
||||
-v certs:/etc/letsencrypt \
|
||||
-v certs-data:/var/lib/letsencrypt \
|
||||
-p 80:80 \
|
||||
-p 443:443 \
|
||||
certbot/certbot \
|
||||
certonly --standalone --preferred-challenges http --email youremail@gmail.com -d yourdomain.com --agree-tos
|
||||
|
||||
- Command to run certbot with specific domain and email:
|
||||
```bash
|
||||
docker run -d --name certbot -v certs:/etc/letsencrypt -v certs-data:/var/lib/letsencrypt -p 8081:80 -p 8443:443 certbot/certbot certonly --standalone --preferred-challenges http --email liosha84@gmail.com -d jambotron.run.place --agree-tos
|
||||
```
|
||||
- Tip: You can generate certificate to local machine used next command:
|
||||
```bash
|
||||
docker run -it --rm -p 8081:80 --name certbot -v "C:\certbot\etc\letsencrypt:/etc/letsencrypt" -v "C:\certbot\var\lib\letsencrypt:/var/lib/letsencrypt" certbot/certbot certonly --standalone -d jambotron.run.place
|
||||
```
|
||||
certificate files will be stored in `C:\certbot\etc\letsencrypt` and `C:\certbot\var\lib\letsencrypt` directories.
|
||||
|
||||
- If you have same trouble before running certbot, run this project
|
||||
`Helper_projects/spring-boot-https-main` insted of jambotron project.
|
||||
|
||||
|
||||
- TODO: Add cron job to renew certificate automatically.
|
||||
|
||||
### Reference Documentation
|
||||
For further reference, please consider the following sections:
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
HELP.md
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM openjdk:20 as build
|
||||
WORKDIR /app
|
||||
COPY . ./
|
||||
RUN microdnf install findutils
|
||||
RUN ./gradlew build -x test
|
||||
|
||||
FROM openjdk:20-jdk-slim
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/build/libs/spring-boot-https-0.0.1.jar .
|
||||
CMD ["java", "-jar", "spring-boot-https-0.0.1.jar"]
|
||||
@@ -0,0 +1 @@
|
||||
https://dev.to/beksultandev/how-to-add-https-support-to-your-spring-boot-app-2h53
|
||||
@@ -0,0 +1,29 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'org.springframework.boot' version '3.1.5'
|
||||
id 'io.spring.dependency-management' version '1.1.3'
|
||||
}
|
||||
|
||||
group = 'dev.beksultan'
|
||||
version = '0.0.1'
|
||||
|
||||
java {
|
||||
sourceCompatibility = '17'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
}
|
||||
|
||||
tasks.named('bootBuildImage') {
|
||||
builder = 'paketobuildpacks/builder-jammy-base:latest'
|
||||
}
|
||||
|
||||
tasks.named('test') {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
spring-boot-https:
|
||||
container_name: spring-boot-https
|
||||
image: beksultancs/spring-boot-https:1
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8081:80"
|
||||
- "8443:443"
|
||||
volumes:
|
||||
- certs:/certs
|
||||
environment:
|
||||
- SERVER_PORT=443
|
||||
- FULLCHAINPEM=/certs/live/jambotron.run.place/fullchain.pem
|
||||
- PRIVKEYPEM=/certs/live/jambotron.run.place/privkey.pem
|
||||
volumes:
|
||||
certs:
|
||||
external: true
|
||||
Binary file not shown.
+7
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed 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.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
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
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
@@ -0,0 +1,92 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem 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, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1 @@
|
||||
rootProject.name = 'spring-boot-https'
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package https;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@SpringBootApplication
|
||||
@RestController
|
||||
public class SpringBootHttpsApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SpringBootHttpsApplication.class, args);
|
||||
}
|
||||
|
||||
@GetMapping("/hello")
|
||||
public String hello() {
|
||||
return "Hello World! 🚀";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
server.ssl.enabled=true
|
||||
server.ssl.certificate=${FULLCHAINPEM}
|
||||
server.ssl.certificate-private-key=${PRIVKEYPEM}
|
||||
@@ -0,0 +1,5 @@
|
||||
server:
|
||||
ssl:
|
||||
enabled: true
|
||||
certificate: ${FULLCHAINPEM}
|
||||
certificate-private-key: ${PRIVKEYPEM}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package https;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class SpringBootHttpsApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
+69
-46
@@ -15,6 +15,7 @@ java {
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
@@ -36,17 +37,7 @@ dependencies {
|
||||
implementation("org.flywaydb:flyway-database-postgresql:10.4.1")
|
||||
|
||||
implementation 'org.springframework.ai:spring-ai-zhipuai-spring-boot-starter:1.0.0-M6'
|
||||
|
||||
//implementation 'org.springframework.ai:spring-ai-starter-model-zhipuai'
|
||||
//implementation 'org.springframework.ai:spring-ai-zhipuai-spring-boot-starter'
|
||||
//implementation platform("org.springframework.ai:spring-ai-bom:1.0.0-SNAPSHOT")
|
||||
// Replace the following with the starter dependencies of specific modules you wish to use
|
||||
//implementation 'org.springframework.ai:spring-ai-zhipuai'
|
||||
//implementation 'org.springframework.boot:spring-boot-starter-webflux'
|
||||
//implementation("org.springframework.ai:spring-ai-spring-boot-autoconfigure:1.0.0-M6")
|
||||
// https://mvnrepository.com/artifact/org.springframework.ai/spring-ai-retry
|
||||
//implementation("org.springframework.ai:spring-ai-retry:1.0.0")
|
||||
//implementation group: 'org.springframework.ai', name: 'spring-ai-spring-boot-autoconfigure', version: '1.0.0-M6'
|
||||
implementation 'org.springframework:spring-mock:2.0.8'
|
||||
}
|
||||
|
||||
apply plugin: 'io.spring.dependency-management'
|
||||
@@ -56,46 +47,78 @@ apply plugin: 'io.spring.dependency-management'
|
||||
}
|
||||
}
|
||||
|
||||
// copyResouces( type:Copy
|
||||
|
||||
//tasks copyResouces( type:Copy){
|
||||
//
|
||||
// def buildDir = "${project.buildDir}"
|
||||
//
|
||||
// from "{$buildDir}/jambotron-ui/dist/jambotron-ui/browser" // Source directory
|
||||
// into "{$buildDir}/build/resources" // Target directory
|
||||
// include '**/*'
|
||||
//}
|
||||
|
||||
apply plugin: 'java'
|
||||
|
||||
/*tasks.register('copyResources', Copy) {
|
||||
from 'jambotron-ui/dist/jambotron-ui/browser'
|
||||
into 'src/main/resources/public'
|
||||
}*/
|
||||
//processResources.dependsOn(copyResources)
|
||||
//compileJava.dependsOn(copyResources)
|
||||
//bootRun.dependsOn("flywayMigrate")
|
||||
|
||||
//flyway {
|
||||
// user='admin'
|
||||
// password= 'postgrespw'
|
||||
// url = 'jdbc:postgresql://localhost:5432/jambotronDB'
|
||||
// driver = 'org.postgresql.Driver'
|
||||
// baselineOnMigrate = true
|
||||
// locations = ['filesystem:src/main/resources/db.migration/']
|
||||
//}
|
||||
|
||||
/*sourceSets {
|
||||
main {
|
||||
resources {
|
||||
srcDir 'jambotron-ui/dist/jambotron-ui/browser'
|
||||
tasks.register("bootRun_Dev") {
|
||||
group = "_jambotron_build"
|
||||
description = "Runs the Spring Boot application with the dev profile"
|
||||
doFirst {
|
||||
tasks.bootRun.configure {
|
||||
systemProperty("spring.profiles.active", "dev")
|
||||
}
|
||||
}
|
||||
}*/
|
||||
finalizedBy("bootRun")
|
||||
}
|
||||
|
||||
tasks.register('copyAngularBuild', Copy) {
|
||||
//dependsOn buildAngular
|
||||
|
||||
|
||||
tasks.register('buildAngular_dev', Exec) {
|
||||
group = "_jambotron_build"
|
||||
description = "Builds the Angular application in development mode"
|
||||
|
||||
workingDir './jambotron-ui' // Path to your Angular project (e.g., './angular-app')
|
||||
executable 'npm.cmd' // Use 'npm' for Unix-like systems or 'npm.cmd' for Windows
|
||||
|
||||
// Your Angular build command
|
||||
//do not forget build_dev used port 8082 with proxy for connect to backend rest api
|
||||
args = ['run', 'build_dev']
|
||||
|
||||
}
|
||||
|
||||
tasks.register('buildAngular_prod', Exec) {
|
||||
group = "_jambotron_build"
|
||||
|
||||
workingDir './jambotron-ui' // Path to your Angular project (e.g., './angular-app')
|
||||
executable 'npm.cmd' // Use 'npm' for Unix-like systems or 'npm.cmd' for Windows
|
||||
|
||||
// Your Angular build command
|
||||
args = ['run', 'build']
|
||||
|
||||
}
|
||||
|
||||
|
||||
tasks.register('deleteStaticFolder_prod', Delete) {
|
||||
group = "_jambotron_build"
|
||||
|
||||
dependsOn buildAngular_prod
|
||||
def dirName = "src/main/resources/static"
|
||||
file(dirName).list().each {
|
||||
f ->
|
||||
delete "${dirName}/${f}"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('deleteStaticFolder_dev', Delete) {
|
||||
group = "_jambotron_build"
|
||||
|
||||
dependsOn buildAngular_dev
|
||||
def dirName = "src/main/resources/static"
|
||||
file(dirName).list().each {
|
||||
f ->
|
||||
delete "${dirName}/${f}"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('copyAngularBuild_prod', Copy) {
|
||||
group = "_jambotron_build"
|
||||
|
||||
dependsOn deleteStaticFolder_prod
|
||||
from 'jambotron-ui/dist/jambotron-ui' // Path to your Angular dist folder
|
||||
into "src/main/resources/static" // Path INSIDE the WAR
|
||||
}
|
||||
tasks.register('copyAngularBuild_dev', Copy) {
|
||||
group = "_jambotron_build"
|
||||
dependsOn deleteStaticFolder_dev
|
||||
from 'jambotron-ui/dist/jambotron-ui' // Path to your Angular dist folder
|
||||
into "src/main/resources/static" // Path INSIDE the WAR
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -20,7 +20,6 @@
|
||||
"outputPath": "dist/jambotron-ui",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
@@ -39,15 +38,45 @@
|
||||
],
|
||||
"styles": [
|
||||
"@angular/material/prebuilt-themes/azure-blue.css",
|
||||
"src/styles.scss"
|
||||
"src/styles.scss",
|
||||
"node_modules/prismjs/themes/prism-okaidia.css",
|
||||
"node_modules/prismjs/plugins/line-numbers/prism-line-numbers.css",
|
||||
"node_modules/prismjs/plugins/line-highlight/prism-line-highlight.css",
|
||||
"node_modules/prismjs/plugins/command-line/prism-command-line.css",
|
||||
|
||||
"node_modules/bootstrap/dist/css/bootstrap.css",
|
||||
"node_modules/bootstrap-markdown/css/bootstrap-markdown.min.css",
|
||||
"node_modules/font-awesome/css/font-awesome.css"
|
||||
|
||||
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/viewerjs/dist/viewer.min.js"
|
||||
"node_modules/viewerjs/dist/viewer.min.js",
|
||||
"node_modules/prismjs/prism.js",
|
||||
"node_modules/prismjs/components/prism-csharp.min.js",
|
||||
"node_modules/prismjs/components/prism-css.min.js",
|
||||
"node_modules/prismjs/components/prism-javascript.min.js",
|
||||
"node_modules/prismjs/components/prism-typescript.min.js",
|
||||
"node_modules/prismjs/components/prism-java.js",
|
||||
"node_modules/prismjs/plugins/line-numbers/prism-line-numbers.js",
|
||||
"node_modules/prismjs/plugins/line-highlight/prism-line-highlight.js",
|
||||
"node_modules/prismjs/plugins/command-line/prism-command-line.js",
|
||||
|
||||
|
||||
"node_modules/jquery/dist/jquery.js",
|
||||
"node_modules/bootstrap-markdown/js/bootstrap-markdown.js"
|
||||
|
||||
]
|
||||
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
@@ -65,14 +94,21 @@
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
"sourceMap": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.development.ts"
|
||||
}
|
||||
],
|
||||
"index": "src/index_dev.html"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "jambotron-ui:build:production"
|
||||
},
|
||||
|
||||
Generated
+202
@@ -24,8 +24,12 @@
|
||||
"@ng-bootstrap/ng-bootstrap": "19.0.0",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@primeng/themes": "^19.1.3",
|
||||
"angular-markdown-editor": "^3.1.1",
|
||||
"bootstrap": "5.3.6",
|
||||
"cropperjs": "^2.0.1",
|
||||
"express": "^5.1.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"ngx-filesaver": "^20.0.0",
|
||||
"ngx-markdown": "^20.0.0",
|
||||
"ngx-scrollbar": "18.0.0",
|
||||
"primeng": "^19.1.3",
|
||||
@@ -40,6 +44,7 @@
|
||||
"@angular/cli": "^20.0.2",
|
||||
"@angular/compiler-cli": "^20.0.0",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^20.17.19",
|
||||
"jasmine-core": "~5.7.0",
|
||||
@@ -2683,6 +2688,126 @@
|
||||
"node": ">=0.1.90"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element/-/element-2.0.1.tgz",
|
||||
"integrity": "sha512-Jn1hR7XWzWQM/QfXRGMGzdkJ2gG/UcLdQPZQ7OKs0JiFfRzKpzu4u/nYrXHeH3MM2iOslLqh2kqYju6mjZLMJQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/utils": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-canvas": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-canvas/-/element-canvas-2.0.1.tgz",
|
||||
"integrity": "sha512-OKxq/O0HL9W2JegOsc2zh1NRpERZcLM5+M8aQ/eXdmMcfi1lzosPftag3Irp6pTsVpwV6B6ypIxKESzJ4ci9Fw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.1",
|
||||
"@cropper/utils": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-crosshair": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-crosshair/-/element-crosshair-2.0.1.tgz",
|
||||
"integrity": "sha512-bS5msU9cTU/jf1/kDw+QJmEM9/rw8IgOdpolR85iMVUCR8sRcLa0wgom42MBHcpBYB6hvL5YfiOeXZ7lHIYMpw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.1",
|
||||
"@cropper/utils": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-grid": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-grid/-/element-grid-2.0.1.tgz",
|
||||
"integrity": "sha512-ayqCvYQJ+GVT31HhFpttzHabW1T/LsIwLJY5PLTMG0cEZLw/E8ihg8mxctjZbo852D7oEePbz6/2SeuCb1018Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.1",
|
||||
"@cropper/utils": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-handle": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-handle/-/element-handle-2.0.1.tgz",
|
||||
"integrity": "sha512-fdifyyPIaR9S2eQ7qPHuM8fX8uToAfBsi8vQlR9EM+oJkDNil0uO4rWyArLWEtlr0/q7U0OvsufcuJ7ffqfmpg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.1",
|
||||
"@cropper/utils": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-image": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-image/-/element-image-2.0.1.tgz",
|
||||
"integrity": "sha512-gPj5Sl2T8Cno198Cz3F3TDfcYoALW3yJ3fV6PHXmhMnX8sBkL7J441do7Vwkg0mEd2CogCtTLAf+p7ljdV0kgA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.1",
|
||||
"@cropper/element-canvas": "^2.0.1",
|
||||
"@cropper/utils": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-selection": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-selection/-/element-selection-2.0.1.tgz",
|
||||
"integrity": "sha512-atv+Aeq2N2eWawelIRPGh1kYFdNrpb0QkUPPheGxz1ImfxpLdcHO9gb9T5noQijizUW2G0pNvts4ZaITQ0I71Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.1",
|
||||
"@cropper/element-canvas": "^2.0.1",
|
||||
"@cropper/element-image": "^2.0.1",
|
||||
"@cropper/utils": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-shade": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-shade/-/element-shade-2.0.1.tgz",
|
||||
"integrity": "sha512-YIYgJ690NdFQ6wJLRFh/EySNVxGFKArncQ4FrsJ3yHU+ShgtOKz4FpjFLpqJRJB9swoVbD3WKTimGyzXrwjZrQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.1",
|
||||
"@cropper/element-canvas": "^2.0.1",
|
||||
"@cropper/element-selection": "^2.0.1",
|
||||
"@cropper/utils": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/element-viewer": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/element-viewer/-/element-viewer-2.0.1.tgz",
|
||||
"integrity": "sha512-HDj25l08pWi/AO6El/OqfQHBpBC4Lh5NEnQN1SOldsmxEwt27Ubv6ndDsF8LkTK7XPwjjZRpyQPyfig4w8L2JQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.1",
|
||||
"@cropper/element-canvas": "^2.0.1",
|
||||
"@cropper/element-image": "^2.0.1",
|
||||
"@cropper/element-selection": "^2.0.1",
|
||||
"@cropper/utils": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/elements": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/elements/-/elements-2.0.1.tgz",
|
||||
"integrity": "sha512-paFbBLXTKXNngn1yDi2ZIf+FO1pIEQXyBntmqOjuxqtG73KuEKv633wsJPFpj958bgcfSakgBbF80j+3nHbPug==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/element": "^2.0.1",
|
||||
"@cropper/element-canvas": "^2.0.1",
|
||||
"@cropper/element-crosshair": "^2.0.1",
|
||||
"@cropper/element-grid": "^2.0.1",
|
||||
"@cropper/element-handle": "^2.0.1",
|
||||
"@cropper/element-image": "^2.0.1",
|
||||
"@cropper/element-selection": "^2.0.1",
|
||||
"@cropper/element-shade": "^2.0.1",
|
||||
"@cropper/element-viewer": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@cropper/utils": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@cropper/utils/-/utils-2.0.1.tgz",
|
||||
"integrity": "sha512-A9RnAFmgNF5aZk5q2VZnFnHtXWu1kPyEN0LVsX8wJ2LBRu2nyETKwz+ZXVsVWliktToCaYojHKrS+6/HODyEZA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@discoveryjs/json-ext": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz",
|
||||
@@ -5822,6 +5947,13 @@
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/file-saver": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz",
|
||||
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/geojson": {
|
||||
"version": "7946.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||
@@ -6286,6 +6418,27 @@
|
||||
"ajv": "^8.8.2"
|
||||
}
|
||||
},
|
||||
"node_modules/angular-markdown-editor": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/angular-markdown-editor/-/angular-markdown-editor-3.1.1.tgz",
|
||||
"integrity": "sha512-0Ho8HZaR95guBwZHDPw9k4Ac2zKW4hr1kl676uaoBsMZ82YHcl3VNb2TTwwui5qtcUWYmdXA++2DmZInj2GIlA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bootstrap": ">=4.6.2",
|
||||
"bootstrap-markdown": "github:refactory-id/bootstrap-markdown",
|
||||
"font-awesome": "^4.7.0",
|
||||
"jquery": "^3.7.0",
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17.0",
|
||||
"npm": ">=6.14.13"
|
||||
},
|
||||
"funding": {
|
||||
"type": "ko_fi",
|
||||
"url": "https://ko-fi.com/ghiscoding"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-colors": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
|
||||
@@ -6623,6 +6776,11 @@
|
||||
"@popperjs/core": "^2.11.8"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap-markdown": {
|
||||
"version": "2.10.0",
|
||||
"resolved": "git+ssh://git@github.com/refactory-id/bootstrap-markdown.git#a496d34b9bd34451c8315a850472f794c8df7d53",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.12",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
@@ -7558,6 +7716,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cropperjs": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cropperjs/-/cropperjs-2.0.1.tgz",
|
||||
"integrity": "sha512-hiJwk2SCPZqxMA7aR3byzLpYUqOrQo+ihMk8k/WRm/xe/LX8wNzAIzMwEB/NEGJYA6sbewxW9TUlrRUYi/2Ipg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@cropper/elements": "^2.0.1",
|
||||
"@cropper/utils": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@@ -9091,6 +9259,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/file-saver": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
|
||||
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -9176,6 +9350,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/font-awesome": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
|
||||
"integrity": "sha512-U6kGnykA/6bFmg1M/oT9EkFeIYv7JlX3bozwQJWiiLz6L0w3F5vBVPxHlwyX/vtNq1ckcpRKOB9f2Qal/VtFpg==",
|
||||
"license": "(OFL-1.1 AND MIT)",
|
||||
"engines": {
|
||||
"node": ">=0.10.3"
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||
@@ -10307,6 +10490,12 @@
|
||||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jquery": {
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
|
||||
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -12015,6 +12204,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ngx-filesaver": {
|
||||
"version": "20.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ngx-filesaver/-/ngx-filesaver-20.0.0.tgz",
|
||||
"integrity": "sha512-84vRFGko1BmpyerAmLLggjx5ZiHvaWadJKlb2n0hOZlnzlTNZeI3zHKY9vqtIRkxCX70vWHTB8JwV5VvvkB2uQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/file-saver": "^2.0.0",
|
||||
"file-saver": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ngx-markdown": {
|
||||
"version": "20.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ngx-markdown/-/ngx-markdown-20.0.0.tgz",
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"start": "ng serve --proxy-config src/proxy.conf.json",
|
||||
"start_prod": "ng serve --configuration production",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"build_dev": "ng build --configuration development",
|
||||
"test": "ng test",
|
||||
"serve:ssr:jambotron-ui": "node dist/jambotron-ui/server/server.mjs"
|
||||
},
|
||||
@@ -22,16 +24,14 @@
|
||||
"@angular/platform-server": "^20.0.0",
|
||||
"@angular/router": "^20.0.0",
|
||||
"@angular/ssr": "^20.0.2",
|
||||
"@ant-design/icons-angular": "19.0.0",
|
||||
"@hreimer/angular-image-viewer": "^0.14.1",
|
||||
"@ng-bootstrap/ng-bootstrap": "19.0.0",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@primeng/themes": "^19.1.3",
|
||||
"angular-markdown-editor": "^3.1.1",
|
||||
"bootstrap": "5.3.6",
|
||||
"cropperjs": "^2.0.1",
|
||||
"express": "^5.1.0",
|
||||
"ngx-markdown": "^20.0.0",
|
||||
"ngx-scrollbar": "18.0.0",
|
||||
"primeng": "^19.1.3",
|
||||
"prismjs": "^1.30.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
@@ -43,6 +43,7 @@
|
||||
"@angular/cli": "^20.0.2",
|
||||
"@angular/compiler-cli": "^20.0.0",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"@types/node": "^20.17.19",
|
||||
"jasmine-core": "~5.7.0",
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<!--<p>admin.component works!</p>-->
|
||||
|
||||
<app-navigation
|
||||
class="pc-sidebar"
|
||||
[ngClass]="{
|
||||
'navbar-collapsed': navCollapsed,
|
||||
'mob-open': navCollapsedMob
|
||||
}"
|
||||
(NavCollapse)="this.navCollapsed = !this.navCollapsed"
|
||||
/>
|
||||
<app-nav-bar (NavCollapsedMob)="navMobClick()" (NavCollapse)="this.navCollapsed = !this.navCollapsed"/>
|
||||
<div class="pc-container">
|
||||
<div class="coded-wrapper">
|
||||
<div class="coded-content">
|
||||
<div class="coded-inner-content">
|
||||
<app-breadcrumb />
|
||||
<div class="main-body">
|
||||
<div class="page-wrapper">
|
||||
<router-outlet />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pc-menu-overlay" (click)="closeMenu()" (keydown)="handleKeyDown($event)" tabindex="0"></div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!--<div class="pc-container">
|
||||
<div class="coded-wrapper">
|
||||
<div class="coded-content">
|
||||
<div class="coded-inner-content">
|
||||
<app-breadcrumb />
|
||||
<div class="main-body">
|
||||
<div class="page-wrapper">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pc-menu-overlay" (click)="closeMenu()" (keydown)="handleKeyDown($event)" tabindex="0"></div>
|
||||
</div>-->
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
.example-container {
|
||||
width: 500px;
|
||||
height: 300px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.example-sidenav-content {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.example-sidenav {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.pc-sidebar{
|
||||
top: 120px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
|
||||
<mat-toolbar class="fixed-top">
|
||||
<button mat-raised-button routerLink="/" href="#">
|
||||
My App
|
||||
</button>
|
||||
<span class="example-spacer"></span>
|
||||
<button mat-raised-button routerLink="tutorials">
|
||||
Tutorials
|
||||
</button>
|
||||
<button mat-raised-button routerLink="add" *ngIf="isLoggedIn">
|
||||
Add tutorial
|
||||
</button>
|
||||
<span class="example-spacer"></span>
|
||||
@if (showAdminBoard) {
|
||||
<button mat-raised-button routerLink="admin" >
|
||||
<mat-icon>manage_accounts</mat-icon>
|
||||
Admin Bord
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (showAdminBoard) {
|
||||
<button mat-raised-button routerLink="system" >
|
||||
<mat-icon>settings_applications</mat-icon>
|
||||
System
|
||||
</button>
|
||||
}
|
||||
|
||||
<span class="example-spacer"></span>
|
||||
<button mat-raised-button routerLink="register" *ngIf="!isLoggedIn">
|
||||
<mat-icon>app_registration</mat-icon>
|
||||
Register
|
||||
</button>
|
||||
|
||||
<button mat-raised-button (click)="openDialog('100ms', '5ms')" *ngIf="!isLoggedIn">
|
||||
<mat-icon>login</mat-icon>
|
||||
Login
|
||||
</button>
|
||||
|
||||
<button mat-raised-button routerLink="profile" *ngIf="isLoggedIn">
|
||||
<mat-icon>account_circle</mat-icon>
|
||||
{{ username }}
|
||||
</button>
|
||||
|
||||
<button mat-raised-button (click)="logout()"*ngIf="isLoggedIn">
|
||||
<mat-icon>logout</mat-icon>
|
||||
Logout
|
||||
</button>
|
||||
|
||||
</mat-toolbar>
|
||||
|
||||
|
||||
<div class="router_outlet_padding">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
//#header_panel{
|
||||
// background-color: #181d1f;
|
||||
//}
|
||||
.example-spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
mat-toolbar{
|
||||
background-color: rgba(153,153,153,0.16);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.fixed-top {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 1000; // Ensure toolbar stays above other content
|
||||
}
|
||||
.router_outlet_padding{
|
||||
padding-top: 60px;
|
||||
scroll-padding-inline: 40%;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
// it(`should have as title 'spring-angular-ui'`, () => {
|
||||
// const fixture = TestBed.createComponent(AppComponent);
|
||||
// const app = fixture.componentInstance;
|
||||
// expect(app.title).toEqual('spring-angular-ui');
|
||||
// });
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement;
|
||||
expect(compiled.querySelector('.content span').textContent).toContain('spring-angular-ui app is running!');
|
||||
});
|
||||
});
|
||||
@@ -1,124 +0,0 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, inject, OnInit} from '@angular/core';
|
||||
import { TokenStorageService } from './services/token-storage.service';
|
||||
import {MatDialog} from "@angular/material/dialog";
|
||||
import {DialogComponent} from "./components/dialog/dialog.component";
|
||||
import {AuthService} from "./services/auth.service";
|
||||
import {Subscription} from "rxjs";
|
||||
import {EventBusService} from "./_shared/event-bus.service";
|
||||
import {RouterLink, RouterOutlet} from '@angular/router';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
import {MatToolbar} from '@angular/material/toolbar';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
imports: [
|
||||
RouterOutlet,
|
||||
MatIcon,
|
||||
MatButton,
|
||||
RouterLink,
|
||||
MatToolbar
|
||||
],
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent implements OnInit {
|
||||
readonly dialog = inject(MatDialog);
|
||||
//private authService = new AuthService(provideHttpClient())
|
||||
public dialogData : DialogData = {password: "", username: ""};
|
||||
|
||||
private roles: string[] = [];
|
||||
isLoggedIn = false;
|
||||
showAdminBoard = false;
|
||||
showModeratorBoard = false;
|
||||
username?: string;
|
||||
|
||||
eventBusSub?: Subscription;
|
||||
private errorMessage: any;
|
||||
private isLoginFailed: boolean = false;
|
||||
|
||||
constructor(
|
||||
private storageService: TokenStorageService,
|
||||
private authService: AuthService,
|
||||
private eventBusService: EventBusService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isLoggedIn = this.storageService.isLoggedIn();
|
||||
|
||||
if (this.isLoggedIn) {
|
||||
const user = this.storageService.getUser();
|
||||
this.roles = user.roles;
|
||||
|
||||
this.showAdminBoard = true;//this.roles.includes('ROLE_ADMIN');
|
||||
this.showModeratorBoard = this.roles.includes('ROLE_MODERATOR');
|
||||
|
||||
this.username = user.username;
|
||||
}
|
||||
|
||||
this.eventBusSub = this.eventBusService.on('logout', () => {
|
||||
this.logout();
|
||||
});
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.authService.logout().subscribe({
|
||||
next: res => {
|
||||
console.log(res);
|
||||
this.storageService.clean();
|
||||
|
||||
window.location.reload();
|
||||
},
|
||||
error: err => {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
openDialog(enterAnimationDuration: string, exitAnimationDuration: string) {
|
||||
|
||||
let dialogRef = this.dialog.open(DialogComponent, {
|
||||
width: '350px',
|
||||
enterAnimationDuration,
|
||||
exitAnimationDuration,
|
||||
data: {username: this.dialogData.username, password: this.dialogData.password}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
console.log('The dialog was closed');
|
||||
if(result== null){
|
||||
return;
|
||||
}
|
||||
this.dialogData = result;
|
||||
|
||||
this.authService.login(this.dialogData.username, this.dialogData.password).subscribe(
|
||||
data => {
|
||||
this.storageService.saveToken(data.accessToken);
|
||||
this.storageService.saveUser(data);
|
||||
|
||||
this.isLoginFailed = false;
|
||||
this.isLoggedIn = true;
|
||||
let user = this.storageService.getUser();
|
||||
this.roles = user.roles;
|
||||
//this.username = user.username;
|
||||
this.reloadPage();
|
||||
},
|
||||
err => {
|
||||
this.errorMessage = err.error.message;
|
||||
this.isLoginFailed = true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
reloadPage(): void {
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
}
|
||||
export interface DialogData {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
|
||||
import { provideServerRendering, withRoutes } from '@angular/ssr';
|
||||
import { appConfig } from './app.config';
|
||||
import { serverRoutes } from './app.routes.server';
|
||||
|
||||
const serverConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideServerRendering(withRoutes(serverRoutes))
|
||||
]
|
||||
};
|
||||
|
||||
export const config = mergeApplicationConfig(appConfig, serverConfig);
|
||||
@@ -14,13 +14,11 @@ import Aura from '@primeng/themes/aura';
|
||||
import {provideAnimations} from '@angular/platform-browser/animations';
|
||||
import {provideMarkdown} from 'ngx-markdown';
|
||||
|
||||
import 'prismjs';
|
||||
import 'prismjs/components/prism-typescript.min.js';
|
||||
import 'prismjs/plugins/line-numbers/prism-line-numbers.js';
|
||||
import 'prismjs/plugins/line-highlight/prism-line-highlight.js';
|
||||
import {authInterceptorProviders} from './helpers/auth.interceptor';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
authInterceptorProviders,
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideAnimations(),
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
<app-spinner></app-spinner>
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
@@ -7,15 +7,17 @@ import { BrowserAnimationsModule }
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import {HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
|
||||
|
||||
import {MainComponent} from './main-module/main.component/main.component';
|
||||
import {MainComponent} from './modules/main-module/main.component/main.component';
|
||||
import {authInterceptorProviders} from './helpers/auth.interceptor';
|
||||
import {AdminComponent} from './admin-module/admin.component/admin.component';
|
||||
import {AdminWelcomeComponent} from './admin-module/admin-welcome.component/admin-welcome.component';
|
||||
import {SettingsComponent} from './admin-module/settings.component/settings.component';
|
||||
import {SystemComponent} from './admin-module/system.component/system.component';
|
||||
import {AdminComponent} from './modules/admin-module/admin.component/admin.component';
|
||||
import {AdminWelcomeComponent} from './modules/admin-module/admin-welcome.component/admin-welcome.component';
|
||||
import {SettingsComponent} from './modules/admin-module/settings.component/settings.component';
|
||||
import {SystemComponent} from './modules/admin-module/system.component/system.component';
|
||||
import {AppRoutingModule} from './app.routes';
|
||||
import {App} from './app';
|
||||
import {CustomHttpInterceptor} from './helpers/custom-http-interceptor';
|
||||
import {AngularMarkdownEditorModule} from 'angular-markdown-editor';
|
||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||
|
||||
|
||||
@NgModule({
|
||||
@@ -32,7 +34,8 @@ import {CustomHttpInterceptor} from './helpers/custom-http-interceptor';
|
||||
AdminWelcomeComponent,
|
||||
SettingsComponent,
|
||||
SystemComponent,
|
||||
|
||||
AngularMarkdownEditorModule.forRoot({ iconlibrary: 'fa' }),
|
||||
MatFormFieldModule
|
||||
],
|
||||
providers: [
|
||||
authInterceptorProviders,provideHttpClient(withInterceptorsFromDi()),{
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { RenderMode, ServerRoute } from '@angular/ssr';
|
||||
|
||||
export const serverRoutes: ServerRoute[] = [
|
||||
{
|
||||
path: '**',
|
||||
renderMode: RenderMode.Prerender
|
||||
}
|
||||
];
|
||||
@@ -1,23 +1,21 @@
|
||||
import {RouterModule, Routes} from '@angular/router';
|
||||
import {AdminComponent} from './layouts/admin-layout/admin-layout.component';
|
||||
import {MainComponent} from './main-module/main.component/main.component';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {environment} from '../environments/environment';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'main/generate-image',
|
||||
redirectTo: environment.default_page,
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'main',
|
||||
loadChildren: () =>
|
||||
import('./main-module/main.module').then((m) => m.MainModule),
|
||||
import('./modules/main-module/main.module').then((m) => m.MainModule),
|
||||
}
|
||||
|
||||
];
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],//, { useHash: true }
|
||||
imports: [RouterModule.forRoot(routes, { useHash: true}) ],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AppRoutingModule {}
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import {SpinnerComponent} from './components/spinner/spinner.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.html',
|
||||
imports: [
|
||||
RouterOutlet
|
||||
RouterOutlet,
|
||||
SpinnerComponent
|
||||
],
|
||||
styleUrl: './app.scss'
|
||||
styleUrl: './app.scss',
|
||||
schemas:[CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class App {
|
||||
protected title = 'jambotron-ui';
|
||||
|
||||
constructor() { }
|
||||
constructor() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<!--<div>-->
|
||||
<!-- <div class="submit-form">-->
|
||||
<!-- <div *ngIf="!submitted">-->
|
||||
<!-- <div class="form-group">-->
|
||||
<!-- <label for="title">Title</label>-->
|
||||
<!-- <input-->
|
||||
<!-- type="text"-->
|
||||
<!-- class="form-control"-->
|
||||
<!-- id="title"-->
|
||||
<!-- required-->
|
||||
<!-- [(ngModel)]="tutorial.title"-->
|
||||
<!-- name="title"-->
|
||||
<!-- />-->
|
||||
<!-- </div>-->
|
||||
|
||||
<!-- <div class="form-group">-->
|
||||
<!-- <label for="description">Description</label>-->
|
||||
<!-- <input-->
|
||||
<!-- class="form-control"-->
|
||||
<!-- id="description"-->
|
||||
<!-- required-->
|
||||
<!-- [(ngModel)]="tutorial.description"-->
|
||||
<!-- name="description"-->
|
||||
<!-- />-->
|
||||
<!-- </div>-->
|
||||
|
||||
<!-- <button (click)="saveTutorial()" class="btn btn-success">Submit</button>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<!-- <div *ngIf="submitted">-->
|
||||
<!-- <h4>Tutorial was submitted successfully!</h4>-->
|
||||
<!-- <button class="btn btn-success" (click)="newTutorial()">Add</button>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!--</div>-->
|
||||
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Add tutorial</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content >
|
||||
<mat-form-field class="example-full-width">
|
||||
<mat-label>Title</mat-label>
|
||||
<input matInput required
|
||||
[(ngModel)]="tutorial.title">
|
||||
</mat-form-field>
|
||||
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Markdown text">
|
||||
<textarea class="variable-textarea" [(ngModel)]="tutorial.description"></textarea>
|
||||
<markdown class="variable-binding" [data]="tutorial.description"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Result">
|
||||
<markdown class="preview" [data]="tutorial.description"></markdown>
|
||||
</mat-tab>
|
||||
<mat-tab label="Example">
|
||||
<textarea class="variable-textarea" [(ngModel)]="markdown"></textarea>
|
||||
<markdown class="variable-binding" [data]="markdown"></markdown>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button matButton (click)="saveTutorial()" *ngIf="!submitted">Save</button>
|
||||
<div *ngIf="submitted">
|
||||
<h4>Tutorial was submitted successfully!</h4>
|
||||
<button matButton (click)="newTutorial()">Add new tutorial</button>
|
||||
</div>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
@@ -1,25 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AddTutorialComponent } from './add-tutorial.component';
|
||||
|
||||
describe('AddTutorialComponent', () => {
|
||||
let component: AddTutorialComponent;
|
||||
let fixture: ComponentFixture<AddTutorialComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ AddTutorialComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AddTutorialComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,96 +0,0 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, OnInit} from '@angular/core';
|
||||
import {Tutorial} from '../../models/tutorial.model';
|
||||
import {TutorialService} from '../../services/tutorial.service';
|
||||
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
||||
import {NgIf} from '@angular/common';
|
||||
import {MatTab, MatTabGroup} from '@angular/material/tabs';
|
||||
import {MarkdownComponent} from 'ngx-markdown';
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-tutorial',
|
||||
templateUrl: './add-tutorial.component.html',
|
||||
styleUrls: ['./add-tutorial.component.scss'],
|
||||
imports: [
|
||||
MatCardActions,
|
||||
FormsModule,
|
||||
MatCard,
|
||||
MatCardHeader,
|
||||
MatCardContent,
|
||||
MatFormField,
|
||||
MatLabel,
|
||||
|
||||
MatButton,
|
||||
MatInput,
|
||||
NgIf,
|
||||
MatLabel,
|
||||
MatTabGroup,
|
||||
MatTab,
|
||||
MarkdownComponent,
|
||||
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class AddTutorialComponent implements OnInit {
|
||||
|
||||
tutorial: Tutorial = {
|
||||
title: '',
|
||||
description: '',
|
||||
published: false
|
||||
};
|
||||
submitted = false
|
||||
|
||||
markdown = `## Markdown __rulez__!
|
||||
---
|
||||
|
||||
### Syntax highlight
|
||||
\`\`\`typescript
|
||||
const language = 'typescript';
|
||||
\`\`\`
|
||||
|
||||
### Lists
|
||||
1. Ordered list
|
||||
2. Another bullet point
|
||||
- Unordered list
|
||||
- Another unordered bullet
|
||||
|
||||
### Blockquote
|
||||
> Blockquote to the max`;
|
||||
|
||||
|
||||
|
||||
constructor(private tutorialService: TutorialService) {
|
||||
this.tutorial.description = this.markdown;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
saveTutorial(): void {
|
||||
const data = {
|
||||
title: this.tutorial.title,
|
||||
description: this.tutorial.description
|
||||
};
|
||||
|
||||
this.tutorialService.create(data)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
this.submitted = true;
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
newTutorial(): void {
|
||||
this.submitted = false;
|
||||
this.tutorial = {
|
||||
title: '',
|
||||
description: '',
|
||||
published: false
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<h2>Comments</h2>
|
||||
<p class="comment">
|
||||
Building for the web is fantastic!
|
||||
</p>
|
||||
<p class="comment">
|
||||
The new template syntax is great
|
||||
</p>
|
||||
<p class="comment">
|
||||
I agree with the other comments!
|
||||
</p>
|
||||
@@ -1,6 +0,0 @@
|
||||
.comment {
|
||||
padding: 15px;
|
||||
margin-left: 30px;
|
||||
background-color: paleturquoise;
|
||||
border-radius: 20px;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-article-comments',
|
||||
imports: [],
|
||||
templateUrl: './article-comments.html',
|
||||
styleUrl: './article-comments.scss'
|
||||
})
|
||||
export class ArticleComments {
|
||||
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<div class="col-md-6">
|
||||
<h4>Users List</h4>
|
||||
<ul class="list-group">
|
||||
<li
|
||||
class="list-group-item"
|
||||
*ngFor="let row of rowData; let i = index"
|
||||
|
||||
>
|
||||
{{ row.username }}
|
||||
|
||||
{{ row.email}}
|
||||
{{ row.password}}
|
||||
|
||||
<form>
|
||||
<mat-form-field class="example-chip-list">
|
||||
<mat-chip-grid #chipGrid aria-label="Role selection">
|
||||
@for (role of row.roles; track $index) {
|
||||
<mat-chip-row (removed)="remove(role)">
|
||||
{{role.name}}
|
||||
<button matChipRemove [attr.aria-label]="'remove ' + role.name">
|
||||
<mat-icon>cancel</mat-icon>
|
||||
</button>
|
||||
</mat-chip-row>
|
||||
}
|
||||
</mat-chip-grid>
|
||||
<input
|
||||
name="currentFruit"
|
||||
placeholder="Add role..."
|
||||
#fruitInput
|
||||
[(ngModel)]="currentRole"
|
||||
[matChipInputFor]="chipGrid"
|
||||
[matAutocomplete]="auto"
|
||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||
[formControl]="myControl"
|
||||
(matChipInputTokenEnd)="add($event)"
|
||||
(input)="change($event,filteredRoles(row.roles))"
|
||||
/>
|
||||
<mat-autocomplete [formControl]="ac" autoActiveFirstOption #auto="matAutocomplete" (optionSelected)="selected(row.roles,$event); ">
|
||||
@for (role of filteredRoles(row.roles); track role) {
|
||||
<mat-option [value]="role">{{role.name}}</mat-option>
|
||||
}
|
||||
</mat-autocomplete>
|
||||
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
import {Component, computed, CUSTOM_ELEMENTS_SCHEMA, model, OnInit} from '@angular/core';
|
||||
|
||||
import {User} from "../../models/user.model";
|
||||
import {MatChipGrid, MatChipInput, MatChipInputEvent, MatChipRow} from "@angular/material/chips";
|
||||
import {
|
||||
MatAutocomplete,
|
||||
MatAutocompleteSelectedEvent,
|
||||
MatAutocompleteTrigger,
|
||||
MatOption
|
||||
} from "@angular/material/autocomplete";
|
||||
import {COMMA, ENTER} from "@angular/cdk/keycodes";
|
||||
import {AsyncPipe, NgForOf} from "@angular/common";
|
||||
import {FormControl, ReactiveFormsModule} from "@angular/forms";
|
||||
import {Observable, startWith} from "rxjs";
|
||||
import {map} from "rxjs/operators";
|
||||
import {UserService} from '../../services/user.service';
|
||||
import {RolesService} from '../../services/roles.service';
|
||||
import {Role} from '../../models/role';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {MatFormField} from '@angular/material/form-field';
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-board-admin',
|
||||
templateUrl: './board-admin.component.html',
|
||||
styleUrls: ['./board-admin.component.scss'],
|
||||
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
imports: [
|
||||
MatAutocomplete,
|
||||
ReactiveFormsModule,
|
||||
MatOption,
|
||||
NgForOf,
|
||||
MatFormField,
|
||||
MatChipGrid,
|
||||
|
||||
MatChipRow,
|
||||
MatIcon,
|
||||
MatChipInput,
|
||||
MatAutocompleteTrigger
|
||||
]
|
||||
})
|
||||
export class BoardAdminComponent implements OnInit {
|
||||
private gridApi: any;
|
||||
|
||||
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
|
||||
rowData?: User[];
|
||||
allRoles: any;
|
||||
|
||||
readonly currentRole = model('');
|
||||
|
||||
protected myControl = new FormControl('');
|
||||
protected ac = new FormControl('');
|
||||
|
||||
|
||||
constructor(private userService: UserService, private roleService: RolesService ) {
|
||||
this.getAllRoles();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
//this.myControl.valueChanges.pipe(
|
||||
// startWith(''),
|
||||
// map(value => this._filter(value || '')),
|
||||
//);
|
||||
|
||||
this.userService.getAdminBoard().subscribe(
|
||||
(data : any) => {
|
||||
this.rowData = data;
|
||||
},
|
||||
(err : any)=> {
|
||||
this.rowData = JSON.parse(err.error).message;
|
||||
}
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
filteredRoles(roles : Role[]):any {
|
||||
return this.allRoles.filter(
|
||||
(r:Role) => !roles.some((item) => item.id === r.id),
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// private _filter(value: string): string[] {
|
||||
// const filterValue = value.toLowerCase();
|
||||
// this.ac.
|
||||
// return this.options.filter(option => option.toLowerCase().includes(filterValue));
|
||||
//}
|
||||
|
||||
getAllRoles():any{
|
||||
this.roleService.getAllRoles().subscribe(
|
||||
(data : any) => {
|
||||
this.allRoles = data;
|
||||
},
|
||||
(err : any)=> {
|
||||
this.allRoles = JSON.parse(err.error).message;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
remove(role: string): void {
|
||||
// this.fruits.update(fruits => {
|
||||
// const index = fruits.indexOf(fruit);
|
||||
// if (index < 0) {
|
||||
// return fruits;
|
||||
// }
|
||||
|
||||
// fruits.splice(index, 1);
|
||||
// this.announcer.announce(`Removed ${fruit}`);
|
||||
// return [...fruits];
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
add(event: MatChipInputEvent): void {
|
||||
const value = (event.value || '').trim();
|
||||
|
||||
// Add our fruit
|
||||
if (value) {
|
||||
// this.fruits.update(fruits => [...fruits, value]);
|
||||
}
|
||||
|
||||
// Clear the input value
|
||||
this.currentRole.set('');
|
||||
}
|
||||
|
||||
selected(roles : Role[], event: MatAutocompleteSelectedEvent): void {
|
||||
|
||||
roles.push(event.option.value);
|
||||
this.currentRole.set('');
|
||||
event.option.deselect();
|
||||
}
|
||||
|
||||
|
||||
change($event: Event, roles: Role[]) {
|
||||
|
||||
roles.filter(
|
||||
(r:Role) => !roles.some((item) => item.name?.toLowerCase() === r.id),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<p>board-moderator works!</p>
|
||||
@@ -1,25 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BoardModeratorComponent } from './board-moderator.component';
|
||||
|
||||
describe('BoardModeratorComponent', () => {
|
||||
let component: BoardModeratorComponent;
|
||||
let fixture: ComponentFixture<BoardModeratorComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ BoardModeratorComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BoardModeratorComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-board-moderator',
|
||||
templateUrl: './board-moderator.component.html',
|
||||
styleUrls: ['./board-moderator.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class BoardModeratorComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<p>board-user works!</p>
|
||||
@@ -1,25 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BoardUserComponent } from './board-user.component';
|
||||
|
||||
describe('BoardUserComponent', () => {
|
||||
let component: BoardUserComponent;
|
||||
let fixture: ComponentFixture<BoardUserComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ BoardUserComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BoardUserComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-board-user',
|
||||
templateUrl: './board-user.component.html',
|
||||
styleUrls: ['./board-user.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class BoardUserComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
@for (breadcrumb of navigationList; track breadcrumb; let last = $last) {
|
||||
@if (last && breadcrumb.breadcrumbs !== false) {
|
||||
<div class="page-header">
|
||||
<div class="page-block">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-12">
|
||||
<ul class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
@if (type === 'theme2') {
|
||||
<a [routerLink]="['/main/home']" title="Home" class="home"><i class="feather icon-home"></i></a>
|
||||
}
|
||||
@if (type === 'theme1') {
|
||||
<a [routerLink]="['/main/home']" class="home">Home</a>
|
||||
}
|
||||
</li>
|
||||
@for (breadcrumb of navigationList; track breadcrumb) {
|
||||
@if (breadcrumb.url !== false) {
|
||||
<li class="breadcrumb-item">
|
||||
<a [routerLink]="breadcrumb.url" class="f-14 f-w-600">{{ breadcrumb.title }}</a>
|
||||
</li>
|
||||
}
|
||||
@if (breadcrumb.url === false && breadcrumb.type !== 'group') {
|
||||
<li class="breadcrumb-item">
|
||||
<a href="javascript:">{{ breadcrumb.title }}</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="page-header-title">
|
||||
@for (breadcrumb of navigationList; track breadcrumb; let last = $last) {
|
||||
@if (last) {
|
||||
<h2 class="mb-0 f-w-600 mt-2">{{ breadcrumb.title }}</h2>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
// Angular Import
|
||||
import { Component, Input, inject, input } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NavigationEnd, Router, RouterModule, Event } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
// project import
|
||||
//import { NavigationItem, NavigationItems } from 'src/app/theme/layouts/admin-layout/navigation/navigation';
|
||||
|
||||
// icons
|
||||
import { IconService } from '@ant-design/icons-angular';
|
||||
import { GlobalOutline, NodeExpandOutline } from '@ant-design/icons-angular/icons';
|
||||
import {NavigationItem, NavigationItems} from '../../layouts/admin-layout/navigation/navigation';
|
||||
|
||||
interface titleType {
|
||||
// eslint-disable-next-line
|
||||
url: any;
|
||||
title: string;
|
||||
breadcrumbs: unknown;
|
||||
type: string;
|
||||
link?: string | undefined;
|
||||
description?: string | undefined;
|
||||
path?: string | undefined;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-breadcrumb',
|
||||
imports: [CommonModule, RouterModule],
|
||||
templateUrl: './breadcrumb.component.html',
|
||||
styleUrls: ['./breadcrumb.component.scss']
|
||||
})
|
||||
export class BreadcrumbComponent {
|
||||
private route = inject(Router);
|
||||
private titleService = inject(Title);
|
||||
private iconService = inject(IconService);
|
||||
|
||||
// public props
|
||||
@Input() type: string;
|
||||
dashboard = input(true);
|
||||
Component = input(false);
|
||||
|
||||
navigations: NavigationItem[];
|
||||
ComponentNavigations: NavigationItem[] = [];
|
||||
breadcrumbList: Array<string> = [];
|
||||
navigationList!: titleType[];
|
||||
componentList!: titleType[];
|
||||
|
||||
// constructor
|
||||
constructor() {
|
||||
this.navigations = NavigationItems;
|
||||
this.type = 'theme1';
|
||||
this.setBreadcrumb();
|
||||
this.iconService.addIcon(...[GlobalOutline, NodeExpandOutline]);
|
||||
}
|
||||
|
||||
// public method
|
||||
setBreadcrumb() {
|
||||
this.route.events.subscribe((router: Event) => {
|
||||
if (router instanceof NavigationEnd) {
|
||||
const activeLink = router.url;
|
||||
const breadcrumbList = this.filterNavigation(this.navigations, activeLink);
|
||||
|
||||
this.navigationList = breadcrumbList;//breadcrumbList.slice(breadcrumbList.length - 1, breadcrumbList.length);
|
||||
const title = breadcrumbList[breadcrumbList.length - 1]?.title || 'Welcome';
|
||||
this.titleService.setTitle(title );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
filterNavigation(navItems: NavigationItem[], activeLink: string): titleType[] {
|
||||
for (const navItem of navItems) {
|
||||
if (navItem.type === 'item' && 'url' in navItem && navItem.url === activeLink) {
|
||||
return [
|
||||
{
|
||||
url: 'url' in navItem ? navItem.url : false,
|
||||
title: navItem.title,
|
||||
link: navItem.link,
|
||||
description: navItem.description,
|
||||
path: navItem.path,
|
||||
breadcrumbs: 'breadcrumbs' in navItem ? navItem.breadcrumbs : true,
|
||||
type: navItem.type
|
||||
}
|
||||
];
|
||||
}
|
||||
if ((navItem.type === 'group' || navItem.type === 'collapse') && 'children' in navItem) {
|
||||
const breadcrumbList = this.filterNavigation(navItem.children!, activeLink);
|
||||
if (breadcrumbList.length > 0) {
|
||||
breadcrumbList.unshift({
|
||||
url: 'url' in navItem ? navItem.url : false,
|
||||
title: navItem.title,
|
||||
link: navItem.link,
|
||||
path: navItem.path,
|
||||
description: navItem.description,
|
||||
breadcrumbs: 'breadcrumbs' in navItem ? navItem.breadcrumbs : true,
|
||||
type: navItem.type
|
||||
});
|
||||
return breadcrumbList;
|
||||
}
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<div class="card" [ngClass]="cardClass()">
|
||||
@if (showHeader()) {
|
||||
<div class="card-header d-flex align-items-center justify-content-between" [ngClass]="headerClass()">
|
||||
<div>
|
||||
<h5>{{ cardTitle() }}</h5>
|
||||
<ng-container *ngTemplateOutlet="headerTitleTemplate"></ng-container>
|
||||
</div>
|
||||
<ng-container *ngTemplateOutlet="headerOptionsTemplate"></ng-container>
|
||||
</div>
|
||||
}
|
||||
@if (showContent()) {
|
||||
<div class="card-body" [ngClass]="blockClass()" [style.padding.px]="padding()">
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -1,58 +0,0 @@
|
||||
// Angular import
|
||||
import { Component, ContentChild, ElementRef, TemplateRef, input } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-card',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './card.component.html',
|
||||
styleUrls: ['./card.component.scss']
|
||||
})
|
||||
export class CardComponent {
|
||||
// public props
|
||||
/**
|
||||
* Title of card. It will be visible at left side of card header
|
||||
*/
|
||||
cardTitle = input<string>();
|
||||
|
||||
/**
|
||||
* Class to be applied at card level
|
||||
*/
|
||||
cardClass = input<string>();
|
||||
|
||||
/**
|
||||
* To hide content from card
|
||||
*/
|
||||
showContent = input(true);
|
||||
|
||||
/**
|
||||
* Class to be applied at card content.
|
||||
*/
|
||||
blockClass = input<string>();
|
||||
|
||||
/**
|
||||
* Class to be applied on card header
|
||||
*/
|
||||
headerClass = input<string>();
|
||||
|
||||
/**
|
||||
* To hide header from card
|
||||
*/
|
||||
showHeader = input(true);
|
||||
|
||||
/**
|
||||
* padding around card content. default in px
|
||||
*/
|
||||
padding = input(20); // set default to 24 px
|
||||
|
||||
/**
|
||||
* Template reference of header actions on custom header
|
||||
*/
|
||||
@ContentChild('headerOptionsTemplate') headerOptionsTemplate!: TemplateRef<ElementRef>;
|
||||
|
||||
/**
|
||||
* Template reference of header actions besides title at left
|
||||
*/
|
||||
@ContentChild('headerTitleTemplate') headerTitleTemplate!: TemplateRef<ElementRef>;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import {ChangeDetectionStrategy, Component, EventEmitter, Inject, inject, Output, signal} from '@angular/core';
|
||||
import {MatButtonModule} from "@angular/material/button";
|
||||
import {
|
||||
MAT_DIALOG_DATA,
|
||||
MatDialogActions,
|
||||
MatDialogClose,
|
||||
MatDialogContent,
|
||||
MatDialogRef,
|
||||
MatDialogTitle
|
||||
} from "@angular/material/dialog";
|
||||
|
||||
|
||||
import {MatFormField, MatLabel, MatSuffix} from "@angular/material/form-field";
|
||||
import {MatInput} from "@angular/material/input";
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {MatIcon} from "@angular/material/icon";
|
||||
import {DialogLoginData} from '../../main-module/main.component/main.component';
|
||||
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||
|
||||
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-dialog',
|
||||
imports: [MatButtonModule, MatLabel, MatDialogActions, MatDialogClose, MatDialogTitle, MatDialogContent, MatFormField, MatInput, FormsModule, MatIcon, MatSuffix],
|
||||
|
||||
templateUrl: './dialog.component.html',
|
||||
styleUrl: './dialog.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DialogComponent {
|
||||
readonly dialogRef = inject(MatDialogRef<DialogComponent>);
|
||||
hide = signal(true);
|
||||
|
||||
@Output() loginClicked = new EventEmitter<any>();
|
||||
@Output() signupClicked = new EventEmitter<any>();
|
||||
|
||||
private _snackBar = inject(MatSnackBar);
|
||||
durationInSeconds = 5;
|
||||
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) public data:DialogLoginData) {
|
||||
|
||||
}
|
||||
|
||||
openLoginFailedSnackBar(errorMessage : string = "Login failed.") {
|
||||
this._snackBar.open(errorMessage , "", {
|
||||
duration: this.durationInSeconds * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
onNoClick() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
|
||||
clickEvent(event: MouseEvent) {
|
||||
this.hide.set(!this.hide());
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
login() {
|
||||
this.loginClicked.emit(this.data);
|
||||
|
||||
}
|
||||
|
||||
signup() {
|
||||
this.signupClicked.emit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<div>
|
||||
<mat-toolbar>
|
||||
<input matInput [value]="fileName" />
|
||||
<button
|
||||
mat-button
|
||||
color="primary"
|
||||
[disabled]="!currentFile"
|
||||
(click)="upload()"
|
||||
>
|
||||
Upload
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
<input
|
||||
type="file"
|
||||
id="fileInput"
|
||||
(change)="selectFile($event)"
|
||||
name="fileInput"
|
||||
/>
|
||||
</div>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@if (progress) {
|
||||
<mat-toolbar class="progress-bar">
|
||||
<mat-progress-bar color="accent" [value]="progress"></mat-progress-bar>
|
||||
<span class="progress">{{ progress }}%</span>
|
||||
</mat-toolbar>
|
||||
}
|
||||
|
||||
@if (message) {
|
||||
<div class="message">
|
||||
{{ message }}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fileInfo){
|
||||
<img src="{{fileInfo.url}}" alt=""/>
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
.progress-bar {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
#fileInput {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
height: 100%;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.mat-toolbar-single-row {
|
||||
height: auto !important;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mat-toolbar-single-row button {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.mat-form-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mat-mdc-form-field {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.message {
|
||||
background-color: #ddd;
|
||||
padding: 15px;
|
||||
color: #333;
|
||||
border: #aaa solid 1px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
img{
|
||||
max-width: -webkit-fill-available;
|
||||
}
|
||||
+6
-8
@@ -1,20 +1,18 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
import { FileUploadComponent } from './file-upload.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
describe('FileUploadComponent', () => {
|
||||
let component: FileUploadComponent;
|
||||
let fixture: ComponentFixture<FileUploadComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ LoginComponent ]
|
||||
imports: [FileUploadComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
fixture = TestBed.createComponent(FileUploadComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
import {Component, EventEmitter, OnInit, Output} from '@angular/core';
|
||||
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
|
||||
import {MatList, MatListItem} from '@angular/material/list';
|
||||
import {AsyncPipe} from '@angular/common';
|
||||
import {MatToolbar} from '@angular/material/toolbar';
|
||||
import {MatProgressBar} from '@angular/material/progress-bar';
|
||||
import {MatFormField, MatInput} from '@angular/material/input';
|
||||
import {Observable} from 'rxjs';
|
||||
import {HttpEventType, HttpResponse} from '@angular/common/http';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
import {UserApiService} from '../../modules/user-module/user-api.service';
|
||||
import {FileInfo} from '../../models/file-info';
|
||||
|
||||
@Component({
|
||||
selector: 'app-file-upload',
|
||||
imports: [
|
||||
MatToolbar,
|
||||
MatProgressBar,
|
||||
MatFormField,
|
||||
MatButton,
|
||||
MatInput
|
||||
],
|
||||
templateUrl: './file-upload.component.html',
|
||||
styleUrl: './file-upload.component.scss'
|
||||
})
|
||||
export class FileUploadComponent {
|
||||
currentFile?: File;
|
||||
progress = 0;
|
||||
message = '';
|
||||
|
||||
fileName = 'Select File';
|
||||
|
||||
@Output() fileInfo: FileInfo | undefined;
|
||||
@Output() onImageUploaded = new EventEmitter<FileInfo>();
|
||||
constructor(private userApiService: UserApiService) {}
|
||||
|
||||
selectFile(event: any): void {
|
||||
this.progress = 0;
|
||||
this.message = '';
|
||||
|
||||
if (event.target.files && event.target.files[0]) {
|
||||
const file: File = event.target.files[0];
|
||||
this.currentFile = file;
|
||||
this.fileName = this.currentFile.name;
|
||||
} else {
|
||||
this.fileName = 'Select File';
|
||||
}
|
||||
}
|
||||
|
||||
upload(): void {
|
||||
if (this.currentFile) {
|
||||
this.userApiService.upload(this.currentFile).subscribe({
|
||||
next: (event: any) => {
|
||||
if (event.type === HttpEventType.UploadProgress) {
|
||||
this.progress = Math.round((100 * event.loaded) / event.total);
|
||||
} else if (event instanceof HttpResponse) {
|
||||
this.message = event.body.message;
|
||||
this.fileInfo =event.body.fileInfo;
|
||||
this.onImageUploaded.emit(this.fileInfo);
|
||||
console.log(event.body);
|
||||
}
|
||||
},
|
||||
error: (err: any) => {
|
||||
console.log(err);
|
||||
this.progress = 0;
|
||||
|
||||
if (err.error && err.error.message) {
|
||||
this.message = err.error.message;
|
||||
} else {
|
||||
this.message = 'Could not upload the file!';
|
||||
this.onImageUploaded.emit(undefined);
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
this.currentFile = undefined;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-18
@@ -1,11 +1,11 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, Input, OnInit, Renderer2} from '@angular/core';
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, OnInit, Renderer2} from '@angular/core';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card';
|
||||
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
||||
import {AsyncPipe, NgIf, NgOptimizedImage} from '@angular/common';
|
||||
import {AsyncPipe} from '@angular/common';
|
||||
import {Image} from '../../models/image';
|
||||
import {ZhipuaiImageService} from '../../services/zhipuai-image.service';
|
||||
import {ZhipuaiImageService} from '../../modules/ai-module/zhipuai-image.service';
|
||||
import {MatProgressSpinner} from '@angular/material/progress-spinner';
|
||||
import {SpinnerService} from '../../services/spinner.service';
|
||||
|
||||
@@ -25,9 +25,7 @@ import Viewer from 'viewerjs';
|
||||
MatInput,
|
||||
MatLabel,
|
||||
MatProgressSpinner,
|
||||
AsyncPipe,
|
||||
|
||||
|
||||
AsyncPipe
|
||||
],
|
||||
templateUrl: './generate-image.component.html',
|
||||
styleUrl: './generate-image.component.scss',
|
||||
@@ -52,11 +50,6 @@ export class GenerateImageComponent implements OnInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
const img = this.renderer.selectRootElement('img');
|
||||
this.viewer = new Viewer(img, {
|
||||
inline: true,
|
||||
});
|
||||
this.viewer.zoomTo(1);
|
||||
|
||||
}
|
||||
|
||||
@@ -79,11 +72,4 @@ export class GenerateImageComponent implements OnInit {
|
||||
this.viewer.update();
|
||||
})
|
||||
}
|
||||
|
||||
zoomPlus(){
|
||||
this.viewer.zoomTo(5);
|
||||
}
|
||||
zoomMinus(){
|
||||
this.viewer.zoomTo(-5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<p>dfsdgdsgsdg</p>
|
||||
|
||||
<p>dfsdgdsgsdg</p>
|
||||
<p>dfsdgdsgsdg</p>
|
||||
<p>dfsdgdsgsdg</p>
|
||||
<p>dfsdgdsgsdg</p>
|
||||
<p>dfsdgdsgsdg</p><p>dfsdgdsgsdg</p>
|
||||
<p>dfsdgdsgsdg</p>
|
||||
<p>dfsdgdsgsdg</p>
|
||||
<p>dfsdgdsgsdg</p>
|
||||
<p>dfsdgdsgsdg</p>
|
||||
<p>dfsdgdsgsdg</p><p>dfsdgdsgsdg</p>
|
||||
<p>dfsdgdsgsdg</p>
|
||||
<p>dfsdgdsgsdg</p>
|
||||
<p>dfsdgdsgsdg</p>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<!--
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Add tutorial</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content *ngIf="!submitted">
|
||||
<mat-form-field class="example-full-width">
|
||||
<mat-label>Title</mat-label>
|
||||
<input matInput [(ngModel)]="query">
|
||||
</mat-form-field>
|
||||
<!– <kendo-editor [(ngModel)]="tutorial.description" ></kendo-editor>–>
|
||||
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button matButton (click)="generateImage()" >Save</button>
|
||||
<div>
|
||||
<h4>Tutorial was submitted successfully!</h4>
|
||||
|
||||
</div>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
|
||||
<div>
|
||||
<angular-image-viewer [src]="images" [(config)]="config" [(index)]="imageIndexOne"
|
||||
[screenHeightOccupied]='0' (customImageEvent)="handleEvent($event)" >
|
||||
</angular-image-viewer>
|
||||
</div>
|
||||
-->
|
||||
@@ -1,76 +0,0 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
||||
import {AngularImageViewerModule, CustomImageEvent, ImageViewerConfig} from '@hreimer/angular-image-viewer';
|
||||
|
||||
import {ZhipuaiImageService} from '../../services/zhipuai-image.service';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
|
||||
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {MatCard, MatCardActions, MatCardContent, MatCardHeader} from '@angular/material/card';
|
||||
import {Image} from '../../models/image';
|
||||
|
||||
@Component({
|
||||
selector: 'app-home.component',
|
||||
imports: [
|
||||
|
||||
AngularImageViewerModule,
|
||||
FormsModule
|
||||
],
|
||||
templateUrl: './home.component.html',
|
||||
styleUrl: './home.component.scss',
|
||||
schemas :[CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
|
||||
export class HomeComponent {
|
||||
|
||||
query : string = '';
|
||||
|
||||
images = [];
|
||||
image: Image = {
|
||||
url: ''
|
||||
};
|
||||
imageIndexOne = 0;
|
||||
|
||||
config: ImageViewerConfig = { customBtns: [{ name: 'print', icon: {
|
||||
classes: 'fas fa-paperclip',
|
||||
text: 'link'
|
||||
} }, { name: 'link', icon: {
|
||||
classes: 'fas fa-paperclip',
|
||||
text: 'link'
|
||||
} }] };
|
||||
|
||||
submitted: boolean = false;
|
||||
|
||||
constructor(private zhipuaiImageService: ZhipuaiImageService) {
|
||||
|
||||
}
|
||||
|
||||
handleEvent(event: CustomImageEvent) {
|
||||
console.log(`${event.name} has been click on img ${event.imageIndex + 1}`);
|
||||
|
||||
switch (event.name) {
|
||||
case 'print':
|
||||
console.log('run print logic');
|
||||
break;
|
||||
}
|
||||
}
|
||||
generateImage(){
|
||||
this.zhipuaiImageService.generate(this.query).subscribe(
|
||||
data => {
|
||||
this.image = data;
|
||||
// @ts-ignore
|
||||
this.images = [this.image.url];
|
||||
|
||||
|
||||
console.log(data);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
<div class="col-md-12">
|
||||
<div class="card card-container">
|
||||
<img
|
||||
id="profile-img"
|
||||
src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
|
||||
class="profile-img-card"
|
||||
/>
|
||||
<form
|
||||
*ngIf="!isLoggedIn"
|
||||
name="form"
|
||||
(ngSubmit)="f.form.valid && onSubmit()"
|
||||
#f="ngForm"
|
||||
novalidate
|
||||
>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="username"
|
||||
[(ngModel)]="form.username"
|
||||
required
|
||||
#username="ngModel"
|
||||
/>
|
||||
<div
|
||||
class="alert alert-danger"
|
||||
role="alert"
|
||||
*ngIf="username.errors && f.submitted"
|
||||
>
|
||||
Username is required!
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="password"
|
||||
[(ngModel)]="form.password"
|
||||
required
|
||||
minlength="6"
|
||||
#password="ngModel"
|
||||
/>
|
||||
<div
|
||||
class="alert alert-danger"
|
||||
role="alert"
|
||||
*ngIf="password.errors && f.submitted"
|
||||
>
|
||||
<div *ngIf="password.errors['required']">Password is required</div>
|
||||
<div *ngIf="password.errors['minlength']">
|
||||
Password must be at least 6 characters
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary btn-block">
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div
|
||||
class="alert alert-danger"
|
||||
role="alert"
|
||||
*ngIf="f.submitted && isLoginFailed"
|
||||
>
|
||||
Login failed: {{ errorMessage }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-success" *ngIf="isLoggedIn">
|
||||
Logged in as {{ roles }}.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {NgIf} from "@angular/common";
|
||||
import {AuthService} from '../../services/auth.service';
|
||||
import {TokenStorageService} from '../../services/token-storage.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
imports: [
|
||||
FormsModule,
|
||||
NgIf
|
||||
],
|
||||
styleUrls: ['./login.component.scss']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
|
||||
form: any = {
|
||||
username: null,
|
||||
password: null
|
||||
};
|
||||
isLoggedIn = false;
|
||||
isLoginFailed = false;
|
||||
errorMessage = '';
|
||||
roles: string[] = [];
|
||||
|
||||
constructor(private authService: AuthService, private tokenStorage: TokenStorageService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
if (this.tokenStorage.getToken()) {
|
||||
this.isLoggedIn = true;
|
||||
this.roles = this.tokenStorage.getUser().roles;
|
||||
}
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
const { username, password } = this.form;
|
||||
|
||||
this.authService.login(username, password).subscribe(
|
||||
data => {
|
||||
this.tokenStorage.saveToken(data.accessToken);
|
||||
this.tokenStorage.saveUser(data);
|
||||
|
||||
this.isLoginFailed = false;
|
||||
this.isLoggedIn = true;
|
||||
this.roles = this.tokenStorage.getUser().roles;
|
||||
this.reloadPage();
|
||||
},
|
||||
err => {
|
||||
this.errorMessage = err.error.message;
|
||||
this.isLoginFailed = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
reloadPage(): void {
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<div class="container" *ngIf="currentUser; else loggedOut">
|
||||
<header class="jumbotron">
|
||||
<h3>
|
||||
<strong>{{ currentUser.username }}</strong> Profile
|
||||
</h3>
|
||||
</header>
|
||||
<p>
|
||||
<strong>Token:</strong>
|
||||
{{ currentUser.accessToken.substring(0, 20) }} ...
|
||||
{{ currentUser.accessToken.substr(currentUser.accessToken.length - 20) }}
|
||||
</p>
|
||||
<p>
|
||||
<strong>Email:</strong>
|
||||
{{ currentUser.email }}
|
||||
</p>
|
||||
<strong>Roles:</strong>
|
||||
<ul>
|
||||
<li *ngFor="let role of currentUser.roles">
|
||||
{{ role }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ng-template #loggedOut>
|
||||
Please login.
|
||||
</ng-template>
|
||||
@@ -1,25 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProfileComponent } from './profile.component';
|
||||
|
||||
describe('ProfileComponent', () => {
|
||||
let component: ProfileComponent;
|
||||
let fixture: ComponentFixture<ProfileComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ProfileComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProfileComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import {TokenStorageService} from '../../services/token-storage.service';
|
||||
import {NgForOf, NgIf} from '@angular/common';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-profile',
|
||||
templateUrl: './profile.component.html',
|
||||
imports: [
|
||||
NgForOf,
|
||||
NgIf
|
||||
],
|
||||
styleUrls: ['./profile.component.scss']
|
||||
})
|
||||
export class ProfileComponent implements OnInit {
|
||||
currentUser: any;
|
||||
|
||||
constructor(private token: TokenStorageService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.currentUser = this.token.getUser();
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
<div class="col-md-12">
|
||||
<div id="register">
|
||||
<img id="foto"
|
||||
id="profile-img"
|
||||
src="//ssl.gstatic.com/accounts/ui/avatar_2x.png"
|
||||
class="profile-img-card"
|
||||
/>
|
||||
<form
|
||||
*ngIf="!isSuccessful"
|
||||
name="form"
|
||||
(ngSubmit)="f.form.valid && onSubmit()"
|
||||
#f="ngForm"
|
||||
novalidate
|
||||
>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="username"
|
||||
[(ngModel)]="form.username"
|
||||
[ngModelOptions]="{standalone: true}"
|
||||
required
|
||||
minlength="3"
|
||||
maxlength="20"
|
||||
#username="ngModel"
|
||||
/>
|
||||
<div class="alert-danger" *ngIf="username.errors && f.submitted">
|
||||
<div *ngIf="username.errors['required']">Username is required</div>
|
||||
<div *ngIf="username.errors['minlength']">
|
||||
Username must be at least 3 characters
|
||||
</div>
|
||||
<div *ngIf="username.errors['maxlength']">
|
||||
Username must be at most 20 characters
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
class="form-control"
|
||||
id="email"
|
||||
[(ngModel)]="form.email"
|
||||
[ngModelOptions]="{standalone: true}"
|
||||
required
|
||||
email
|
||||
#email="ngModel"
|
||||
/>
|
||||
<div class="alert-danger" *ngIf="email.errors && f.submitted">
|
||||
<div *ngIf="email.errors['required']">Email is required</div>
|
||||
<div *ngIf="email.errors['email']">
|
||||
Email must be a valid email address
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="password"
|
||||
[(ngModel)]="form.password"
|
||||
[ngModelOptions]="{standalone: true}"
|
||||
required
|
||||
minlength="6"
|
||||
#password="ngModel"
|
||||
/>
|
||||
<div class="alert-danger" *ngIf="password.errors && f.submitted">
|
||||
<div *ngIf="password.errors['required']">Password is required</div>
|
||||
<div *ngIf="password.errors['minlength']">
|
||||
Password must be at least 6 characters
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary btn-block">Sign Up</button>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning" *ngIf="f.submitted && isSignUpFailed">
|
||||
Signup failed!<br />{{ errorMessage }}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="alert alert-success" *ngIf="isSuccessful">
|
||||
Your registration is successful!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,25 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RegisterComponent } from './register.component';
|
||||
|
||||
describe('RegisterComponent', () => {
|
||||
let component: RegisterComponent;
|
||||
let fixture: ComponentFixture<RegisterComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ RegisterComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RegisterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import {AuthService} from '../../services/auth.service';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {NgIf} from '@angular/common';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-register',
|
||||
templateUrl: './register.component.html',
|
||||
imports: [
|
||||
FormsModule,
|
||||
NgIf
|
||||
],
|
||||
styleUrls: ['./register.component.scss']
|
||||
})
|
||||
export class RegisterComponent implements OnInit {
|
||||
form: any = {
|
||||
username: null,
|
||||
email: null,
|
||||
password: null
|
||||
};
|
||||
isSuccessful = false;
|
||||
isSignUpFailed = false;
|
||||
errorMessage = '';
|
||||
|
||||
constructor(private authService: AuthService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
onSubmit(): void {
|
||||
const { username, email, password } = this.form;
|
||||
|
||||
this.authService.register(username, email, password).subscribe(
|
||||
data => {
|
||||
console.log(data);
|
||||
this.isSuccessful = true;
|
||||
this.isSignUpFailed = false;
|
||||
},
|
||||
err => {
|
||||
this.errorMessage = err.error.message;
|
||||
this.isSignUpFailed = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
<div class="navbar-wrapper">
|
||||
<div class="m-header">
|
||||
<mat-label>Jambotron</mat-label>
|
||||
<a href="javascript:" class="b-brand">
|
||||
<img src="assets/images/logo-dark.svg" alt="theme-logo" class="logo logo-dark logo-lg" />
|
||||
</a>
|
||||
</div>
|
||||
<!-- <app-nav-content (NavCollapsedMob)="navCollapseMob()" class="scroll-div w-100 compact"></app-nav-content>-->
|
||||
<mat-nav-list>
|
||||
<a mat-list-item routerLink="user-welcome">
|
||||
<span class="entry">
|
||||
<mat-icon>house</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >Dashboard</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
<a mat-list-item routerLink="tutorials-list">
|
||||
<span class="entry">
|
||||
<mat-icon>newspaper</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >Tutorials</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
<a mat-list-item routerLink="ai-models">
|
||||
<span class="entry">
|
||||
<mat-icon>house</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >AI tools</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
</mat-nav-list>
|
||||
<div class="example-action-buttons">
|
||||
<button matButton (click)="accordion().openAll()">Expand All</button>
|
||||
<button matButton (click)="accordion().closeAll()">Collapse All</button>
|
||||
<!-- #docregion multi -->
|
||||
<mat-accordion class="example-headers-align" multi>
|
||||
<!-- #enddocregion multi -->
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>
|
||||
<a mat-list-item routerLink="user-welcome">
|
||||
<span class="entry">
|
||||
<mat-icon>house</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >Dashboard</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
</mat-panel-title>
|
||||
|
||||
</mat-expansion-panel-header>
|
||||
<mat-nav-list>
|
||||
<a mat-list-item routerLink="user-welcome">
|
||||
<span class="entry">
|
||||
<mat-icon>house</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >Dashboard</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
<a mat-list-item routerLink="tutorials-list">
|
||||
<span class="entry">
|
||||
<mat-icon>newspaper</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >Tutorials</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
<a mat-list-item routerLink="ai-models">
|
||||
<span class="entry">
|
||||
<mat-icon>house</mat-icon>
|
||||
@if (!isCollapsed) {
|
||||
<span >AI tools</span>
|
||||
}
|
||||
</span>
|
||||
</a>
|
||||
</mat-nav-list>
|
||||
</mat-expansion-panel>
|
||||
<!-- #docregion disabled -->
|
||||
<mat-expansion-panel disabled>
|
||||
<!-- #enddocregion disabled -->
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title> Destination </mat-panel-title>
|
||||
<mat-panel-description>
|
||||
Type the country name
|
||||
<mat-icon>map</mat-icon>
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Country</mat-label>
|
||||
<input matInput />
|
||||
</mat-form-field>
|
||||
</mat-expansion-panel>
|
||||
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title> Day of the trip </mat-panel-title>
|
||||
<mat-panel-description>
|
||||
Inform the date you wish to travel
|
||||
<mat-icon>date_range</mat-icon>
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Date</mat-label>
|
||||
<input matInput [matDatepicker]="picker" (focus)="picker.open()" readonly />
|
||||
</mat-form-field>
|
||||
<mat-datepicker #picker></mat-datepicker>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
.entry{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding:0.75rem;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.example-action-buttons {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.example-headers-align .mat-expansion-panel-header-description {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.example-headers-align .mat-mdc-form-field + .mat-mdc-form-field {
|
||||
margin-left: 8px;
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import {ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA, viewChild} from '@angular/core';
|
||||
import {MatTree, MatTreeNode} from '@angular/material/tree';
|
||||
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
||||
import {MatDivider, MatListItem, MatNavList} from '@angular/material/list';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {RouterLink} from '@angular/router';
|
||||
import {
|
||||
MatAccordion, MatExpansionModule,
|
||||
MatExpansionPanel,
|
||||
MatExpansionPanelDescription,
|
||||
MatExpansionPanelTitle
|
||||
} from '@angular/material/expansion';
|
||||
import {MatDatepicker, MatDatepickerInput} from '@angular/material/datepicker';
|
||||
import {provideNativeDateAdapter} from '@angular/material/core';
|
||||
import {MatButton} from '@angular/material/button';
|
||||
|
||||
@Component({
|
||||
selector: 'app-side-bar',
|
||||
imports: [
|
||||
MatTree,
|
||||
MatTreeNode,
|
||||
MatLabel,
|
||||
MatNavList,
|
||||
MatListItem,
|
||||
MatIcon,
|
||||
RouterLink,
|
||||
MatExpansionPanel,
|
||||
MatExpansionPanelTitle,
|
||||
MatExpansionPanelDescription,
|
||||
MatFormField,
|
||||
MatInput,
|
||||
MatDatepickerInput,
|
||||
MatDatepicker,
|
||||
MatDivider,
|
||||
MatAccordion,
|
||||
MatButton,
|
||||
MatExpansionModule
|
||||
],
|
||||
templateUrl: './side-bar.component.html',
|
||||
styleUrl: './side-bar.component.scss',
|
||||
schemas:[CUSTOM_ELEMENTS_SCHEMA],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [provideNativeDateAdapter()],
|
||||
})
|
||||
|
||||
|
||||
|
||||
export class SideBarComponent {
|
||||
isCollapsed = false;
|
||||
|
||||
accordion = viewChild.required(MatAccordion);
|
||||
}
|
||||
|
||||
const TREE_DATA: TreeNode[] = [
|
||||
{
|
||||
name: 'Fruit',
|
||||
children: [{name: 'Apple'}, {name: 'Banana'}, {name: 'Fruit loops'}],
|
||||
},
|
||||
{
|
||||
name: 'Vegetables',
|
||||
children: [
|
||||
{
|
||||
name: 'Green',
|
||||
children: [{name: 'Broccoli'}, {name: 'Brussels sprouts'}],
|
||||
},
|
||||
{
|
||||
name: 'Orange',
|
||||
children: [{name: 'Pumpkins'}, {name: 'Carrots'}],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
interface TreeNode {
|
||||
name: string;
|
||||
children?: TreeNode[];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>System</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-tab-group>
|
||||
<mat-tab label="Custom beans">
|
||||
Content 1
|
||||
<table mat-table
|
||||
[dataSource]="customBeans" multiTemplateDataRows="true" class="mat-elevation-z8">
|
||||
@for (column of displayedColumns; track column) {
|
||||
<ng-container matColumnDef="{{column}}">
|
||||
<th mat-header-cell *matHeaderCellDef>{{column}}</th>
|
||||
<td mat-cell *matCellDef="let element ; let k = dataIndex;">{{element[column]}}</td>
|
||||
</ng-container>
|
||||
}
|
||||
<ng-container matColumnDef="expand">
|
||||
<th mat-header-cell *matHeaderCellDef aria-label="row actions"> </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button
|
||||
matIconButton
|
||||
aria-label="expand row"
|
||||
(click)="toggle(element); $event.stopPropagation()"
|
||||
class="example-toggle-button"
|
||||
[class.example-toggle-button-expanded]="isExpanded(element)">
|
||||
<mat-icon>keyboard_arrow_down</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
|
||||
<ng-container matColumnDef="expandedDetail">
|
||||
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplayWithExpand.length">
|
||||
<div class="example-element-detail-wrapper"
|
||||
[class.example-element-detail-wrapper-expanded]="isExpanded(element)">
|
||||
<div class="example-element-detail">
|
||||
|
||||
<mat-list role="list">
|
||||
<mat-list-item role="listitem">Name: {{element.name}}</mat-list-item>
|
||||
<mat-list-item role="listitem">Type: {{element.type}}</mat-list-item>
|
||||
</mat-list>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columnsToDisplayWithExpand"></tr>
|
||||
<tr mat-row *matRowDef="let element; columns: columnsToDisplayWithExpand;"
|
||||
class="example-element-row"
|
||||
[class.example-expanded-row]="isExpanded(element)"
|
||||
(click)="toggle(element)">
|
||||
</tr>
|
||||
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
|
||||
</table>
|
||||
|
||||
</mat-tab>
|
||||
<mat-tab label="All beans">
|
||||
Content 2
|
||||
<table mat-table
|
||||
[dataSource]="allBeans" multiTemplateDataRows="true" class="mat-elevation-z8">
|
||||
@for (column of displayedColumns; track column) {
|
||||
<ng-container matColumnDef="{{column}}">
|
||||
<th mat-header-cell *matHeaderCellDef>{{column}}</th>
|
||||
<td mat-cell *matCellDef="let element ; let k = dataIndex;">{{element[column]}}</td>
|
||||
</ng-container>
|
||||
}
|
||||
<ng-container matColumnDef="expand">
|
||||
<th mat-header-cell *matHeaderCellDef aria-label="row actions"> </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button
|
||||
matIconButton
|
||||
aria-label="expand row"
|
||||
(click)="toggle(element); $event.stopPropagation()"
|
||||
class="example-toggle-button"
|
||||
[class.example-toggle-button-expanded]="isExpanded(element)">
|
||||
<mat-icon>keyboard_arrow_down</mat-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
|
||||
<ng-container matColumnDef="expandedDetail">
|
||||
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplayWithExpand.length">
|
||||
<div class="example-element-detail-wrapper"
|
||||
[class.example-element-detail-wrapper-expanded]="isExpanded(element)">
|
||||
<div class="example-element-detail">
|
||||
|
||||
<mat-list role="list">
|
||||
<mat-list-item role="listitem">Name: {{element.name}}</mat-list-item>
|
||||
<mat-list-item role="listitem">Type: {{element.type}}</mat-list-item>
|
||||
</mat-list>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columnsToDisplayWithExpand"></tr>
|
||||
<tr mat-row *matRowDef="let element; columns: columnsToDisplayWithExpand;"
|
||||
class="example-element-row"
|
||||
[class.example-expanded-row]="isExpanded(element)"
|
||||
(click)="toggle(element)">
|
||||
</tr>
|
||||
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
|
||||
</table>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
@@ -1,113 +0,0 @@
|
||||
mat-card{
|
||||
margin: 20px;
|
||||
background-color: rgba(240, 248, 255, 0.7);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
mat-card-title{
|
||||
color: cyan;
|
||||
}
|
||||
|
||||
mat-card-subtitle{
|
||||
color: #009dff;
|
||||
}
|
||||
|
||||
mat-tab{
|
||||
color:black;
|
||||
}
|
||||
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
color:lightgrey;
|
||||
}
|
||||
|
||||
|
||||
mat-list{
|
||||
//background-color: #1389d3;
|
||||
color:black;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
mat-list-item{
|
||||
margin: 10px 10px 10px 10px;
|
||||
background-color: #447694;
|
||||
|
||||
color: #009dff;
|
||||
}
|
||||
|
||||
tr.example-detail-row {
|
||||
height: 0;
|
||||
background: #fffdfd;
|
||||
}
|
||||
|
||||
tr.example-element-row {
|
||||
cursor: pointer;
|
||||
color: #1389d3;
|
||||
background: #fffdfd;
|
||||
}
|
||||
|
||||
tr.example-element-row:not(.example-expanded-row):hover {
|
||||
background: whitesmoke;
|
||||
}
|
||||
|
||||
tr.example-element-row:not(.example-expanded-row):active {
|
||||
background: #efefef;
|
||||
}
|
||||
|
||||
.example-element-row td {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.example-element-detail-wrapper {
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
grid-template-columns: 100%;
|
||||
transition: grid-template-rows 225ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.example-element-detail-wrapper-expanded {
|
||||
grid-template-rows: 1fr;
|
||||
|
||||
}
|
||||
|
||||
.example-element-detail {
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.example-element-diagram {
|
||||
min-width: 80px;
|
||||
border: 2px solid black;
|
||||
padding: 8px;
|
||||
font-weight: lighter;
|
||||
margin: 8px 0;
|
||||
height: 104px;
|
||||
}
|
||||
|
||||
.example-element-symbol {
|
||||
font-weight: bold;
|
||||
font-size: 40px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.example-element-description {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.example-element-description-attribution {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.example-toggle-button {
|
||||
transition: transform 225ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.example-toggle-button-expanded {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
import { Component } from '@angular/core';
|
||||
import {Bean} from '../../models/Bean';
|
||||
import {SystemService} from '../../services/system.service';
|
||||
import {
|
||||
MatCard,
|
||||
MatCardContent,
|
||||
MatCardHeader,
|
||||
MatCardSubtitle,
|
||||
MatCardTitle,
|
||||
MatCardTitleGroup
|
||||
} from '@angular/material/card';
|
||||
import {MatDivider} from '@angular/material/divider';
|
||||
import {
|
||||
MatCell,
|
||||
MatCellDef,
|
||||
MatColumnDef,
|
||||
MatHeaderCell,
|
||||
MatHeaderCellDef, MatHeaderRow,
|
||||
MatHeaderRowDef, MatRow, MatRowDef,
|
||||
MatTable
|
||||
} from '@angular/material/table';
|
||||
import {MatIconButton} from '@angular/material/button';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
import {MatList, MatListItem} from '@angular/material/list';
|
||||
import {MatTab, MatTabGroup} from '@angular/material/tabs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-system.component',
|
||||
imports: [
|
||||
MatCard,
|
||||
MatCardSubtitle,
|
||||
MatCardContent,
|
||||
MatCardHeader,
|
||||
MatCardTitle,
|
||||
MatDivider,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCell,
|
||||
MatCell,
|
||||
MatIconButton,
|
||||
MatIcon,
|
||||
MatHeaderCellDef,
|
||||
MatCellDef,
|
||||
MatHeaderRowDef,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
MatHeaderRow,
|
||||
MatList,
|
||||
MatListItem,
|
||||
MatTabGroup,
|
||||
MatTab
|
||||
],
|
||||
templateUrl: './system.component.html',
|
||||
styleUrl: './system.component.scss'
|
||||
})
|
||||
export class SystemComponent {
|
||||
|
||||
// @ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
// @ViewChild(MatSort) sort: MatSort;
|
||||
|
||||
customBeans:Bean[] = [] ;
|
||||
allBeans:Bean[] = [] ;
|
||||
displayedColumns: string[] = ['shortName', 'typeShortName', 'scope'];
|
||||
|
||||
columnsToDisplayWithExpand = [...this.displayedColumns, 'expand'];
|
||||
|
||||
expandedElement: Bean | null = null;
|
||||
|
||||
resultsLength = 0;
|
||||
isLoadingResults = true;
|
||||
isRateLimitReached = false;
|
||||
|
||||
constructor(private systemService: SystemService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.retrieveBeans();
|
||||
}
|
||||
|
||||
retrieveBeans(): void {
|
||||
this.systemService.getCustomBeans()
|
||||
.subscribe(
|
||||
(data: Bean[]) => {
|
||||
this.customBeans = data;
|
||||
console.log(data);
|
||||
},
|
||||
(error: any) => {
|
||||
console.log(error);
|
||||
});
|
||||
this.systemService.getAllBeans()
|
||||
.subscribe(
|
||||
(data: Bean[]) => {
|
||||
this.allBeans = data;
|
||||
console.log(data);
|
||||
},
|
||||
(error: any) => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
/** Checks whether an element is expanded. */
|
||||
isExpanded(element: Bean) {
|
||||
return this.expandedElement === element;
|
||||
}
|
||||
|
||||
/** Toggles the expanded state of an element. */
|
||||
toggle(element: Bean) {
|
||||
this.expandedElement = this.isExpanded(element) ? null : element;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ngAfterViewInit() {
|
||||
//this.exampleDatabase = new ExampleHttpDatabase(this._httpClient);
|
||||
|
||||
// If the user changes the sort order, reset back to the first page.
|
||||
/* this.sort.sortChange.subscribe(() => (this.paginator.pageIndex = 0));
|
||||
|
||||
merge(this.sort.sortChange, this.paginator.page)
|
||||
.pipe(
|
||||
startWith({}),
|
||||
switchMap(() => {
|
||||
this.isLoadingResults = true;
|
||||
return this.exampleDatabase!.getRepoIssues(
|
||||
this.sort.active,
|
||||
this.sort.direction,
|
||||
this.paginator.pageIndex,
|
||||
).pipe(catchError(() => observableOf(null)));
|
||||
}),
|
||||
map(data => {
|
||||
// Flip flag to show that loading has finished.
|
||||
this.isLoadingResults = false;
|
||||
this.isRateLimitReached = data === null;
|
||||
|
||||
if (data === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Only refresh the result length if there is new data. In case of rate
|
||||
// limit errors, we do not want to reset the paginator to zero, as that
|
||||
// would prevent users from re-triggering requests.
|
||||
this.resultsLength = data.total_count;
|
||||
return data.items;
|
||||
}),
|
||||
)
|
||||
.subscribe(data => (this.data = data));*/
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
<div>
|
||||
<div *ngIf="currentTutorial.id" class="edit-form">
|
||||
<h4>Tutorial</h4>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="title">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="title"
|
||||
[(ngModel)]="currentTutorial.title"
|
||||
name="title"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="description">Description</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="description"
|
||||
[(ngModel)]="currentTutorial.description"
|
||||
name="description"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label><strong>Status:</strong></label>
|
||||
{{ currentTutorial.published ? "Published" : "Pending" }}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<button
|
||||
class="badge badge-primary mr-2"
|
||||
*ngIf="currentTutorial.published"
|
||||
(click)="updatePublished(false)"
|
||||
>
|
||||
UnPublish
|
||||
</button>
|
||||
<button
|
||||
*ngIf="!currentTutorial.published"
|
||||
class="badge badge-primary mr-2"
|
||||
(click)="updatePublished(true)"
|
||||
>
|
||||
Publish
|
||||
</button>
|
||||
|
||||
<button class="badge badge-danger mr-2" (click)="deleteTutorial()">
|
||||
Delete
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="badge badge-success mb-2"
|
||||
(click)="updateTutorial()"
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
<p>{{ message }}</p>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!currentTutorial.id">
|
||||
<br />
|
||||
<p>Cannot access this Tutorial...</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,4 +0,0 @@
|
||||
.edit-form {
|
||||
max-width: 400px;
|
||||
margin: auto;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TutorialDetailsComponent } from './tutorial-details.component';
|
||||
|
||||
describe('TutorialDetailsComponent', () => {
|
||||
let component: TutorialDetailsComponent;
|
||||
let fixture: ComponentFixture<TutorialDetailsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ TutorialDetailsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TutorialDetailsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,96 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import {Tutorial} from '../../models/tutorial.model';
|
||||
import {TutorialService} from '../../services/tutorial.service';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {NgIf} from '@angular/common';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorial-details',
|
||||
templateUrl: './tutorial-details.component.html',
|
||||
imports: [
|
||||
FormsModule,
|
||||
NgIf
|
||||
],
|
||||
styleUrls: ['./tutorial-details.component.scss']
|
||||
})
|
||||
export class TutorialDetailsComponent implements OnInit {
|
||||
|
||||
currentTutorial: Tutorial = {
|
||||
title: '',
|
||||
description: '',
|
||||
published: false
|
||||
};
|
||||
message = '';
|
||||
|
||||
constructor(
|
||||
private tutorialService: TutorialService,
|
||||
private route: ActivatedRoute,
|
||||
private router: Router) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.message = '';
|
||||
this.getTutorial(this.route.snapshot.params['id']);
|
||||
}
|
||||
|
||||
getTutorial(id: string): void {
|
||||
this.tutorialService.get(id)
|
||||
.subscribe(
|
||||
data => {
|
||||
this.currentTutorial = data;
|
||||
console.log(data);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
updatePublished(status: boolean): void {
|
||||
const data = {
|
||||
title: this.currentTutorial.title,
|
||||
description: this.currentTutorial.description,
|
||||
published: status
|
||||
};
|
||||
|
||||
this.message = '';
|
||||
|
||||
this.tutorialService.update(this.currentTutorial.id, data)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.currentTutorial.published = status;
|
||||
console.log(response);
|
||||
this.message = response.message ? response.message : 'The status was updated successfully!';
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
updateTutorial(): void {
|
||||
this.message = '';
|
||||
|
||||
this.tutorialService.update(this.currentTutorial.id, this.currentTutorial)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
this.message = response.message ? response.message : 'This tutorial was updated successfully!';
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
deleteTutorial(): void {
|
||||
this.tutorialService.delete(this.currentTutorial.id)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
this.router.navigate(['/tutorials']);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
<div class="list row">
|
||||
<div class="col-md-8">
|
||||
<div class="input-group mb-3">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Search by title"
|
||||
[(ngModel)]="title"
|
||||
/>
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
class="btn btn-outline-secondary"
|
||||
type="button"
|
||||
(click)="searchTitle()"
|
||||
>
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4>Tutorials List</h4>
|
||||
<ul class="list-group">
|
||||
<li
|
||||
class="list-group-item"
|
||||
*ngFor="let tutorial of tutorials; let i = index"
|
||||
[class.active]="i == currentIndex"
|
||||
(click)="setActiveTutorial(tutorial, i)"
|
||||
>
|
||||
{{ tutorial.title }}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<button class="m-3 btn btn-sm btn-danger" (click)="removeAllTutorials()">
|
||||
Remove All
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div *ngIf="currentTutorial.id">
|
||||
<h4>Tutorial</h4>
|
||||
<div>
|
||||
<label><strong>Title:</strong></label> {{ currentTutorial.title }}
|
||||
</div>
|
||||
<div>
|
||||
<label><strong>Description:</strong></label>
|
||||
{{ currentTutorial.description }}
|
||||
</div>
|
||||
<div>
|
||||
<label><strong>Status:</strong></label>
|
||||
{{ currentTutorial.published ? "Published" : "Pending" }}
|
||||
</div>
|
||||
|
||||
<a class="badge badge-warning" routerLink="/tutorials/{{ currentTutorial.id }}">
|
||||
Edit
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div *ngIf="!currentTutorial">
|
||||
<br />
|
||||
<p>Please click on a Tutorial...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +0,0 @@
|
||||
.list {
|
||||
text-align: left;
|
||||
max-width: 750px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
|
||||
import {Tutorial} from '../../models/tutorial.model';
|
||||
import {TutorialService} from '../../services/tutorial.service';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {RouterLink} from '@angular/router';
|
||||
import {NgIf} from '@angular/common';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorials-list',
|
||||
templateUrl: './tutorials-list.component.html',
|
||||
imports: [
|
||||
FormsModule,
|
||||
RouterLink,
|
||||
NgIf
|
||||
],
|
||||
styleUrls: ['./tutorials-list.component.scss']
|
||||
})
|
||||
export class TutorialsListComponent implements OnInit {
|
||||
|
||||
tutorials?: Tutorial[];
|
||||
currentTutorial: Tutorial = {};
|
||||
currentIndex = -1;
|
||||
title = '';
|
||||
|
||||
constructor(private tutorialService: TutorialService) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.retrieveTutorials();
|
||||
}
|
||||
|
||||
retrieveTutorials(): void {
|
||||
this.tutorialService.getAll()
|
||||
.subscribe(
|
||||
data => {
|
||||
this.tutorials = data;
|
||||
console.log(data);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
refreshList(): void {
|
||||
this.retrieveTutorials();
|
||||
this.currentTutorial = {};
|
||||
this.currentIndex = -1;
|
||||
}
|
||||
|
||||
setActiveTutorial(tutorial: Tutorial, index: number): void {
|
||||
this.currentTutorial = tutorial;
|
||||
this.currentIndex = index;
|
||||
}
|
||||
|
||||
removeAllTutorials(): void {
|
||||
this.tutorialService.deleteAll()
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log(response);
|
||||
this.refreshList();
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
|
||||
searchTitle(): void {
|
||||
this.currentTutorial = {};
|
||||
this.currentIndex = -1;
|
||||
|
||||
this.tutorialService.findByTitle(this.title)
|
||||
.subscribe(
|
||||
data => {
|
||||
this.tutorials = data;
|
||||
console.log(data);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import {Component, CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
|
||||
import {Tutorial} from '../../models/tutorial.model';
|
||||
import {TutorialService} from '../../services/tutorial.service';
|
||||
|
||||
import {MatCard, MatCardContent, MatCardHeader} from '@angular/material/card';
|
||||
import {MarkdownComponent} from 'ngx-markdown';
|
||||
import {authInterceptorProviders} from '../../helpers/auth.interceptor';
|
||||
import {HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
|
||||
import {CustomHttpInterceptor} from '../../helpers/custom-http-interceptor';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorials.component',
|
||||
@@ -14,13 +17,18 @@ import {MarkdownComponent} from 'ngx-markdown';
|
||||
],
|
||||
templateUrl: './tutorials.component.html',
|
||||
styleUrl: './tutorials.component.scss',
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
providers: [authInterceptorProviders,{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: CustomHttpInterceptor,
|
||||
multi: true
|
||||
}],
|
||||
})
|
||||
export class TutorialsComponent {
|
||||
tutorials?: Tutorial[];
|
||||
|
||||
/*
|
||||
constructor(private tutorialService: TutorialService) {
|
||||
this.retrieveTutorials();
|
||||
// this.retrieveTutorials();
|
||||
}
|
||||
|
||||
// ngOnInit(): void {
|
||||
@@ -28,14 +36,14 @@ export class TutorialsComponent {
|
||||
// }
|
||||
|
||||
retrieveTutorials(): void {
|
||||
this.tutorialService.getAll()
|
||||
.subscribe(
|
||||
data => {
|
||||
this.tutorials = data;
|
||||
console.log(data);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
this.tutorialService.getAllPublic().subscribe(
|
||||
(data : Tutorial[]) =>{
|
||||
this.tutorials = data;
|
||||
console.log(data);
|
||||
},
|
||||
error => {
|
||||
console.log(error);
|
||||
}
|
||||
);
|
||||
}*/
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { ColumnResizeDirective } from './column-resize.directive';
|
||||
|
||||
describe('ColumnResizeDirective', () => {
|
||||
it('should create an instance', () => {
|
||||
const directive = new ColumnResizeDirective();
|
||||
expect(directive).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -1,114 +0,0 @@
|
||||
import {
|
||||
Directive,
|
||||
ElementRef,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Renderer2,
|
||||
NgZone,
|
||||
Input,
|
||||
} from '@angular/core';
|
||||
import { Subject, fromEvent, takeUntil } from 'rxjs';
|
||||
|
||||
@Directive({
|
||||
selector: '[appColumnResize]',
|
||||
})
|
||||
export class ColumnResizeDirective implements OnInit, OnDestroy {
|
||||
@Input() resizableTable: HTMLElement | null = null;
|
||||
|
||||
private isResizing = false;
|
||||
private startX!: number;
|
||||
private startWidth!: number;
|
||||
private column: HTMLElement;
|
||||
private table: HTMLElement | null = null;
|
||||
private resizer!: HTMLElement;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private el: ElementRef,
|
||||
private renderer: Renderer2,
|
||||
private zone: NgZone
|
||||
) {
|
||||
this.column = this.el.nativeElement;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.table = this.resizableTable || this.findParentTable(this.column);
|
||||
|
||||
if (!this.table) {
|
||||
console.error(
|
||||
'Parent table not found. Make sure the directive is applied to a th element within a table.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.createResizer();
|
||||
this.initializeResizeListener();
|
||||
}
|
||||
|
||||
private createResizer() {
|
||||
this.resizer = this.renderer.createElement('div');
|
||||
this.renderer.addClass(this.resizer, 'column-resizer');
|
||||
this.renderer.setStyle(this.resizer, 'position', 'absolute');
|
||||
this.renderer.setStyle(this.resizer, 'right', '0');
|
||||
this.renderer.setStyle(this.resizer, 'top', '0');
|
||||
this.renderer.setStyle(this.resizer, 'height', '100%');
|
||||
this.renderer.setStyle(this.resizer, 'width', '5px');
|
||||
this.renderer.setStyle(this.resizer, 'cursor', 'col-resize');
|
||||
this.renderer.appendChild(this.column, this.resizer);
|
||||
}
|
||||
|
||||
private initializeResizeListener() {
|
||||
this.zone.runOutsideAngular(() => {
|
||||
fromEvent(this.resizer, 'mousedown')
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((e: Event) => this.onMouseDown(e as MouseEvent));
|
||||
|
||||
fromEvent(document, 'mousemove')
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((e: Event) => this.onMouseMove(e as MouseEvent));
|
||||
|
||||
fromEvent(document, 'mouseup')
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(() => this.onMouseUp());
|
||||
});
|
||||
}
|
||||
|
||||
private onMouseDown(e: MouseEvent) {
|
||||
e.preventDefault();
|
||||
this.isResizing = true;
|
||||
this.startX = e.pageX;
|
||||
this.startWidth = this.column.offsetWidth;
|
||||
this.renderer.addClass(this.column, 'resizing');
|
||||
if (this.table) {
|
||||
this.renderer.addClass(this.table, 'resizing');
|
||||
}
|
||||
}
|
||||
|
||||
private onMouseMove(e: MouseEvent) {
|
||||
if (!this.isResizing) return;
|
||||
const width = this.startWidth + (e.pageX - this.startX);
|
||||
this.renderer.setStyle(this.column, 'width', `${width}px`);
|
||||
}
|
||||
|
||||
private onMouseUp() {
|
||||
if (!this.isResizing) return;
|
||||
this.isResizing = false;
|
||||
this.renderer.removeClass(this.column, 'resizing');
|
||||
if (this.table) {
|
||||
this.renderer.removeClass(this.table, 'resizing');
|
||||
}
|
||||
}
|
||||
|
||||
private findParentTable(element: HTMLElement): HTMLElement | null {
|
||||
while (element && element.tagName !== 'TABLE') {
|
||||
element = element.parentElement as HTMLElement;
|
||||
if (!element) return null;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { GlobalConstants } from './global-constants';
|
||||
|
||||
describe('GlobalConstants', () => {
|
||||
it('should create an instance', () => {
|
||||
expect(new GlobalConstants()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import {environment} from '../environments/environment';
|
||||
|
||||
export class GlobalConstants {
|
||||
|
||||
public static readonly TITLE = (() => {
|
||||
return environment.title;
|
||||
})();
|
||||
|
||||
public static readonly DEFAULT_PAGE = (() => {
|
||||
return environment.default_page;
|
||||
})();
|
||||
|
||||
public static readonly API_URL = (() => {
|
||||
// ... calculate the value and return it
|
||||
|
||||
if(environment.production) {
|
||||
return `${environment.host_name}/api`;
|
||||
}else {
|
||||
//return `${environment.host_name}:${environment.port}/api`;
|
||||
return `/api`; // in serve mode used proxy (proxy.conf.json)
|
||||
}
|
||||
|
||||
})();
|
||||
}
|
||||
@@ -1,26 +1,56 @@
|
||||
import { HTTP_INTERCEPTORS, HttpEvent } from '@angular/common/http';
|
||||
import {HTTP_INTERCEPTORS, HttpErrorResponse, HttpEvent} from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
|
||||
|
||||
import { TokenStorageService } from '../services/token-storage.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import {catchError, Observable, throwError} from 'rxjs';
|
||||
import {EventBusService} from '../_shared/event-bus.service';
|
||||
import {EventData} from '../_shared/event.class';
|
||||
import {environment} from '../../environments/environment';
|
||||
|
||||
const TOKEN_HEADER_KEY = 'Authorization'; // for Spring Boot back-end
|
||||
|
||||
@Injectable()
|
||||
export class AuthInterceptor implements HttpInterceptor {
|
||||
constructor(private token: TokenStorageService) { }
|
||||
private isRefreshing = false;
|
||||
enviorment = environment;
|
||||
|
||||
constructor(private tokenStorageService: TokenStorageService, private eventBusService: EventBusService) { }
|
||||
|
||||
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
let authReq = req;
|
||||
const token = this.token.getToken();
|
||||
if (token != null) {
|
||||
authReq = req.clone({ headers: req.headers.set(TOKEN_HEADER_KEY, 'Bearer ' + token) });
|
||||
|
||||
// req = req.clone({
|
||||
// withCredentials: true,
|
||||
// });
|
||||
|
||||
return next.handle(req).pipe(
|
||||
catchError((error) => {
|
||||
if (
|
||||
error instanceof HttpErrorResponse &&
|
||||
!req.url.includes('auth/signin') &&
|
||||
(error.status === 401) /// must be only 401 without 500 and 0
|
||||
) {
|
||||
return this.handle401Error(req, next);
|
||||
}
|
||||
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
|
||||
if (!this.isRefreshing) {
|
||||
this.isRefreshing = true;
|
||||
|
||||
if (this.tokenStorageService.isLoggedIn()) {
|
||||
this.eventBusService.emit(new EventData('logout', null));
|
||||
}
|
||||
}
|
||||
return next.handle(authReq);
|
||||
|
||||
return next.handle(request);
|
||||
}
|
||||
}
|
||||
|
||||
export const authInterceptorProviders = [
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
|
||||
];
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http';
|
||||
import {HttpEvent, HttpEventType, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse} from '@angular/common/http';
|
||||
import {Observable, tap} from 'rxjs';
|
||||
import {SpinnerService} from '../services/spinner.service';
|
||||
import {Injectable} from '@angular/core';
|
||||
@@ -19,6 +19,7 @@ export class CustomHttpInterceptor implements HttpInterceptor {
|
||||
}
|
||||
}, (error) => {
|
||||
this.spinnerService.hide();
|
||||
}));
|
||||
}))
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<app-navigation
|
||||
class="pc-sidebar"
|
||||
[ngClass]="{
|
||||
'navbar-collapsed': navCollapsed,
|
||||
'mob-open': navCollapsedMob
|
||||
}"
|
||||
(NavCollapse)="this.navCollapsed = !this.navCollapsed"
|
||||
/>
|
||||
<app-nav-bar (NavCollapsedMob)="navMobClick()" (NavCollapse)="this.navCollapsed = !this.navCollapsed" />
|
||||
<!--<app-nav-bar>
|
||||
|
||||
</app-nav-bar>-->
|
||||
<!--<app-navigation></app-navigation>-->
|
||||
<div class="pc-container">
|
||||
<div class="coded-wrapper">
|
||||
<div class="coded-content">
|
||||
<div class="coded-inner-content">
|
||||
<app-breadcrumb />
|
||||
<div class="main-body">
|
||||
<div class="page-wrapper">
|
||||
<router-outlet />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pc-menu-overlay" (click)="closeMenu()" (keydown)="handleKeyDown($event)" tabindex="0"></div>
|
||||
</div>
|
||||
<footer class="pc-footer">
|
||||
<div class="footer-wrapper container-fluid">
|
||||
<div class="row">
|
||||
<div class="col my-1">
|
||||
<p class="m-0">
|
||||
Copyright ©
|
||||
<a href="https://codedthemes.com" target="_blank">CodedThemes</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-auto my-1">
|
||||
<ul class="list-inline footer-link mb-0">
|
||||
<li class="list-inline-item"><a href="https://codedthemes.com/" target="_blank">Home</a></li>
|
||||
<li class="list-inline-item">
|
||||
<a href="https://codedthemes.com/privacy-policy/" target="_blank">Privacy Policy</a>
|
||||
</li>
|
||||
<li class="list-inline-item"><a href="https://codedthemes.support-hub.io/" target="_blank">Contact us</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<app-configuration />
|
||||
@@ -1,51 +0,0 @@
|
||||
// Angular import
|
||||
import { Component } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
// Project import
|
||||
|
||||
import { NavBarComponent } from './nav-bar/nav-bar.component';
|
||||
import { NavigationComponent } from './navigation/navigation.component';
|
||||
import { ConfigurationComponent } from './configuration/configuration.component';
|
||||
import {BreadcrumbComponent} from '../../components/breadcrumb/breadcrumb.component';
|
||||
//import { BreadcrumbComponent } from 'src/app/theme/shared/components/breadcrumb/breadcrumb.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin',
|
||||
imports: [CommonModule, BreadcrumbComponent, NavigationComponent, NavBarComponent, RouterModule, ConfigurationComponent, NavigationComponent],
|
||||
templateUrl: './admin-layout.component.html',
|
||||
styleUrls: ['./admin-layout.component.scss']
|
||||
})
|
||||
export class AdminComponent {
|
||||
// public props
|
||||
navCollapsed: boolean = true;
|
||||
navCollapsedMob: boolean = true;
|
||||
|
||||
// public method
|
||||
navMobClick() {
|
||||
if (this.navCollapsedMob && !document.querySelector('app-navigation.pc-sidebar')?.classList.contains('mob-open')) {
|
||||
this.navCollapsedMob = !this.navCollapsedMob;
|
||||
setTimeout(() => {
|
||||
this.navCollapsedMob = !this.navCollapsedMob;
|
||||
}, 100);
|
||||
} else {
|
||||
this.navCollapsedMob = !this.navCollapsedMob;
|
||||
}
|
||||
if (document.querySelector('app-navigation.pc-sidebar')?.classList.contains('navbar-collapsed')) {
|
||||
document.querySelector('app-navigation.pc-sidebar')?.classList.remove('navbar-collapsed');
|
||||
}
|
||||
}
|
||||
|
||||
handleKeyDown(event: KeyboardEvent): void {
|
||||
if (event.key === 'Escape') {
|
||||
this.closeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
closeMenu() {
|
||||
if (document.querySelector('app-navigation.pc-sidebar')?.classList.contains('mob-open')) {
|
||||
document.querySelector('app-navigation.pc-sidebar')?.classList.remove('mob-open');
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user