From 9005f4d663ef0dce554d81f47a2eda061273e0d9 Mon Sep 17 00:00:00 2001 From: Gleb Gorelov Date: Mon, 14 Sep 2026 11:47:53 +0400 Subject: [PATCH 01/33] [AI Chat] Initial Commit --- content/modules/ai-chat/pages/getting-started.adoc | 2 ++ content/modules/ai-chat/pages/index.adoc | 2 ++ content/modules/ai-chat/partials/nav.adoc | 2 ++ 3 files changed, 6 insertions(+) create mode 100644 content/modules/ai-chat/pages/getting-started.adoc create mode 100644 content/modules/ai-chat/pages/index.adoc create mode 100644 content/modules/ai-chat/partials/nav.adoc diff --git a/content/modules/ai-chat/pages/getting-started.adoc b/content/modules/ai-chat/pages/getting-started.adoc new file mode 100644 index 00000000..34a4eaa6 --- /dev/null +++ b/content/modules/ai-chat/pages/getting-started.adoc @@ -0,0 +1,2 @@ += Getting Started + diff --git a/content/modules/ai-chat/pages/index.adoc b/content/modules/ai-chat/pages/index.adoc new file mode 100644 index 00000000..d497a088 --- /dev/null +++ b/content/modules/ai-chat/pages/index.adoc @@ -0,0 +1,2 @@ += AI Chat Components + diff --git a/content/modules/ai-chat/partials/nav.adoc b/content/modules/ai-chat/partials/nav.adoc new file mode 100644 index 00000000..02f31e95 --- /dev/null +++ b/content/modules/ai-chat/partials/nav.adoc @@ -0,0 +1,2 @@ +* xref:ai-chat:index.adoc[] +** xref:ai-chat:getting-started.adoc[] From 59798cc81d055690850fc6a83df16eb03147ff61 Mon Sep 17 00:00:00 2001 From: Gleb Gorelov Date: Mon, 14 Sep 2026 11:50:06 +0400 Subject: [PATCH 02/33] [AI Chat] Init Project --- .../ai-chat/examples/ai-chat-ex1/.gitignore | 31 ++ .../ai-chat/examples/ai-chat-ex1/README.md | 34 +++ .../ai-chat/examples/ai-chat-ex1/build.gradle | 68 +++++ .../examples/ai-chat-ex1/gradle.properties | 2 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48462 bytes .../gradle/wrapper/gradle-wrapper.properties | 9 + .../ai-chat/examples/ai-chat-ex1/gradlew | 248 ++++++++++++++++ .../ai-chat/examples/ai-chat-ex1/gradlew.bat | 82 ++++++ .../examples/ai-chat-ex1/settings.gradle | 1 + .../ai-chat-ex1/src/main/frontend/index.html | 20 ++ .../company/demo/AiChatEx1Application.java | 60 ++++ .../java/com/company/demo/entity/User.java | 189 ++++++++++++ .../AiChatEx1SecurityConfiguration.java | 49 ++++ .../demo/security/DatabaseUserRepository.java | 31 ++ .../company/demo/security/FullAccessRole.java | 23 ++ .../company/demo/security/UiMinimalRole.java | 20 ++ .../company/demo/view/login/LoginView.java | 140 +++++++++ .../com/company/demo/view/main/MainView.java | 118 ++++++++ .../demo/view/user/UserDetailView.java | 94 ++++++ .../company/demo/view/user/UserListView.java | 14 + .../META-INF/resources/icons/icon.png | Bin 0 -> 11979 bytes .../META-INF/resources/public/images/logo.png | Bin 0 -> 27095 bytes .../themes/ai-chat-ex1-aura/ai-chat-ex1.css | 1 + .../themes/ai-chat-ex1-aura/styles.css | 4 + .../ai-chat-ex1-aura/view/login-view.css | 9 + .../view/main-view-top-menu.css | 57 ++++ .../ai-chat-ex1-aura/view/main-view.css | 273 ++++++++++++++++++ .../themes/ai-chat-ex1-lumo/ai-chat-ex1.css | 1 + .../themes/ai-chat-ex1-lumo/styles.css | 4 + .../ai-chat-ex1-lumo/view/login-view.css | 5 + .../view/main-view-top-menu.css | 54 ++++ .../ai-chat-ex1-lumo/view/main-view.css | 233 +++++++++++++++ .../src/main/resources/application.properties | 38 +++ .../com/company/demo/liquibase/changelog.xml | 16 + .../liquibase/changelog/010-init-user.xml | 81 ++++++ .../main/resources/com/company/demo/menu.xml | 8 + .../com/company/demo/messages_en.properties | 39 +++ .../company/demo/view/login/login-view.xml | 20 ++ .../com/company/demo/view/main/main-view.xml | 59 ++++ .../demo/view/user/user-detail-view.xml | 41 +++ .../company/demo/view/user/user-list-view.xml | 78 +++++ .../test_support/AuthenticatedAsAdmin.java | 30 ++ .../java/com/company/demo/user/UserTest.java | 59 ++++ .../com/company/demo/user/UserUiTest.java | 91 ++++++ .../resources/application-test.properties | 3 + 45 files changed, 2437 insertions(+) create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/.gitignore create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/README.md create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/build.gradle create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/gradle.properties create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/gradle/wrapper/gradle-wrapper.jar create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/gradle/wrapper/gradle-wrapper.properties create mode 100755 content/modules/ai-chat/examples/ai-chat-ex1/gradlew create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/gradlew.bat create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/settings.gradle create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/frontend/index.html create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/AiChatEx1Application.java create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/entity/User.java create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/AiChatEx1SecurityConfiguration.java create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/DatabaseUserRepository.java create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/FullAccessRole.java create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/UiMinimalRole.java create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/login/LoginView.java create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/main/MainView.java create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/user/UserDetailView.java create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/user/UserListView.java create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/icons/icon.png create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/public/images/logo.png create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/ai-chat-ex1.css create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/styles.css create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/view/login-view.css create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/view/main-view-top-menu.css create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/view/main-view.css create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/ai-chat-ex1.css create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/styles.css create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/view/login-view.css create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/view/main-view-top-menu.css create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/view/main-view.css create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/application.properties create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/liquibase/changelog.xml create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/liquibase/changelog/010-init-user.xml create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/menu.xml create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/messages_en.properties create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/view/login/login-view.xml create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/view/main/main-view.xml create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/view/user/user-detail-view.xml create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/view/user/user-list-view.xml create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/test/java/com/company/demo/test_support/AuthenticatedAsAdmin.java create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/test/java/com/company/demo/user/UserTest.java create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/test/java/com/company/demo/user/UserUiTest.java create mode 100644 content/modules/ai-chat/examples/ai-chat-ex1/src/test/resources/application-test.properties diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/.gitignore b/content/modules/ai-chat/examples/ai-chat-ex1/.gitignore new file mode 100644 index 00000000..57ec2854 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/.gitignore @@ -0,0 +1,31 @@ +.jmix + +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/** +!**/src/test/** + +# Remove this line if you want to follow recommendations in src/main/bundles/README.md +src/main/bundles/ + +### IntelliJ IDEA ### +.idea/* +!.idea/encodings.xml +*.iws +*.iml +*.ipr +out/ +/target/ + +### VS Code ### +.vscode/ + +# The following files are generated/updated by vaadin-gradle-plugin +node_modules/ +src/main/frontend/generated/ +pnpmfile.js +vite.generated.ts +webpack.generated.js + +.DS_Store diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/README.md b/content/modules/ai-chat/examples/ai-chat-ex1/README.md new file mode 100644 index 00000000..cc20ebb4 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/README.md @@ -0,0 +1,34 @@ +# Ai Chat Ex1 + +This is a web application based on the [Jmix](https://www.jmix.io) framework. + +## Getting Started + +The newly created project requires Java 21 and uses the embedded HSQL database. + +Use the following resources to learn more about Jmix: +* [Jmix Documentation](https://docs.jmix.io) +* [Online Demo Applications](https://www.jmix.io/live-demo) +* [Jmix AI Assistant](https://ai-assistant.jmix.io) (also available in the **Jmix AI** tool window of IntelliJ IDEA) + +## Development + +- [Setup](https://docs.jmix.io/jmix/setup.html) your development environment. +- [Open](https://docs.jmix.io/jmix/studio/project.html#opening-existing-project) the project in the IDE. +- If you want to use AI agents to develop the application, check out the [Jmix AI Agent Guidelines](https://github.com/jmix-framework/jmix-agent-guidelines) repository. + +## Running + +To start the application, use the **Ai Chat Ex1 Jmix Application** run configuration in your IDE, or run the following command in the project root directory: + +```bash +./gradlew bootRun +``` + +The application will be available at . + +The default user credentials are: +* Login: `admin` +* Password: `admin` + +**WARNING**: Change admin password and remove `ui.login.defaultUsername` and `ui.login.defaultPassword` application properties when deploying the application to production. \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/build.gradle b/content/modules/ai-chat/examples/ai-chat-ex1/build.gradle new file mode 100644 index 00000000..62d3fa63 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/build.gradle @@ -0,0 +1,68 @@ +plugins { + id 'io.jmix' version '3.1.0-M1' + id 'java' + id 'org.jetbrains.gradle.plugin.idea-ext' version '1.4.1' +} + +apply plugin: 'org.springframework.boot' +apply plugin: 'com.vaadin' + +jmix { + bomVersion = '3.1.999-SNAPSHOT' +} + +group = 'com.company' +version = '0.0.1-SNAPSHOT' + +repositories { + mavenCentral() + mavenLocal() + maven { + url = 'https://nexus.jmix.io/repository/public' + } +} + +dependencies { + implementation 'io.jmix.core:jmix-core-starter' + implementation 'io.jmix.data:jmix-eclipselink-starter' + implementation 'io.jmix.security:jmix-security-starter' + implementation 'io.jmix.security:jmix-security-flowui-starter' + implementation 'io.jmix.security:jmix-security-data-starter' + implementation 'io.jmix.localfs:jmix-localfs-starter' + implementation 'io.jmix.flowui:jmix-flowui-starter' + implementation 'io.jmix.flowui:jmix-flowui-data-starter' + implementation 'io.jmix.flowui:jmix-flowui-themes' + implementation 'io.jmix.datatools:jmix-datatools-starter' + implementation 'io.jmix.datatools:jmix-datatools-flowui-starter' + + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'com.vaadin:vaadin-dev' + + runtimeOnly 'org.hsqldb:hsqldb' + + testImplementation('org.springframework.boot:spring-boot-starter-test') { + exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' + } + testImplementation 'io.jmix.flowui:jmix-flowui-test-assist' +} + +configurations.implementation { + exclude group: 'com.vaadin', module: 'hilla' + exclude group: 'com.vaadin', module: 'hilla-dev' + exclude group: 'com.vaadin', module: 'copilot' +} + +test { + useJUnitPlatform() +} + +idea { + module { + excludeDirs.addAll(files '.jmix', 'node_modules', 'src/main/frontend/generated/', 'src/main/bundles') + } +} + +vaadin { + optimizeBundle = false + productionMode = false +} diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/gradle.properties b/content/modules/ai-chat/examples/ai-chat-ex1/gradle.properties new file mode 100644 index 00000000..8b4371be --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/gradle.properties @@ -0,0 +1,2 @@ +hilla.active=false +org.gradle.jvmargs=-Xmx2g -Dfile.encoding=UTF-8 \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/gradle/wrapper/gradle-wrapper.jar b/content/modules/ai-chat/examples/ai-chat-ex1/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..b1b8ef56b44f16b14dc800fa8103a6d89abb526f GIT binary patch literal 48462 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 R2np~f1o-$>Q}y+){|A@R9n$~+ literal 0 HcmV?d00001 diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/gradle/wrapper/gradle-wrapper.properties b/content/modules/ai-chat/examples/ai-chat-ex1/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..df6a6ad7 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/gradlew b/content/modules/ai-chat/examples/ai-chat-ex1/gradlew new file mode 100755 index 00000000..b9bb139f --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# 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/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/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 -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || 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 + + + +# 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" ) + + 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, 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" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# 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" "$@" diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/gradlew.bat b/content/modules/ai-chat/examples/ai-chat-ex1/gradlew.bat new file mode 100644 index 00000000..24c62d56 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/gradlew.bat @@ -0,0 +1,82 @@ +@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 +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +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. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/settings.gradle b/content/modules/ai-chat/examples/ai-chat-ex1/settings.gradle new file mode 100644 index 00000000..91787dbe --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'ai-chat-ex1' diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/frontend/index.html b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/frontend/index.html new file mode 100644 index 00000000..2b20a458 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/frontend/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + + +
+ + diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/AiChatEx1Application.java b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/AiChatEx1Application.java new file mode 100644 index 00000000..e97cfa22 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/AiChatEx1Application.java @@ -0,0 +1,60 @@ +package com.company.demo; + +import com.google.common.base.Strings; +import com.vaadin.flow.component.dependency.StyleSheet; +import com.vaadin.flow.component.page.AppShellConfigurator; +import com.vaadin.flow.component.page.Push; +import com.vaadin.flow.server.PWA; +import com.vaadin.flow.theme.aura.Aura; +import io.jmix.flowui.theme.aura.JmixAura; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.jdbc.autoconfigure.DataSourceProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.context.event.EventListener; +import org.springframework.core.env.Environment; + +import javax.sql.DataSource; + +@Push +@StyleSheet(Aura.STYLESHEET) +@StyleSheet(JmixAura.STYLESHEET) +@StyleSheet("themes/ai-chat-ex1-aura/styles.css") +@PWA(name = "Ai Chat Ex1", shortName = "Ai Chat Ex1", offline = false) +@SpringBootApplication +public class AiChatEx1Application implements AppShellConfigurator { + + @Autowired + private Environment environment; + + public static void main(String[] args) { + SpringApplication.run(AiChatEx1Application.class, args); + } + + @Bean + @Primary + @ConfigurationProperties("main.datasource") + DataSourceProperties dataSourceProperties() { + return new DataSourceProperties(); + } + + @Bean + @Primary + @ConfigurationProperties("main.datasource.hikari") + DataSource dataSource(final DataSourceProperties dataSourceProperties) { + return dataSourceProperties.initializeDataSourceBuilder().build(); + } + + @EventListener + public void printApplicationUrl(final ApplicationStartedEvent event) { + LoggerFactory.getLogger(AiChatEx1Application.class).info("Application started at " + + "http://localhost:" + + environment.getProperty("local.server.port") + + Strings.nullToEmpty(environment.getProperty("server.servlet.context-path"))); + } +} diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/entity/User.java b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/entity/User.java new file mode 100644 index 00000000..3faa5edd --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/entity/User.java @@ -0,0 +1,189 @@ +package com.company.demo.entity; + +import io.jmix.core.HasTimeZone; +import io.jmix.core.annotation.Secret; +import io.jmix.core.entity.annotation.JmixGeneratedValue; +import io.jmix.core.entity.annotation.SystemLevel; +import io.jmix.core.metamodel.annotation.DependsOnProperties; +import io.jmix.core.metamodel.annotation.InstanceName; +import io.jmix.core.metamodel.annotation.JmixEntity; +import io.jmix.security.authentication.JmixUserDetails; +import io.jmix.security.user.PasswordChangeRequired; +import org.springframework.security.core.GrantedAuthority; +import jakarta.persistence.*; +import jakarta.validation.constraints.Email; +import java.util.Collection; +import java.util.Collections; +import java.util.UUID; + +@JmixEntity +@Entity +@Table(name = "USER_", indexes = { + @Index(name = "IDX_USER__ON_USERNAME", columnList = "USERNAME", unique = true) +}) +public class User implements JmixUserDetails, HasTimeZone { + + @Id + @Column(name = "ID") + @JmixGeneratedValue + private UUID id; + + @Version + @Column(name = "VERSION", nullable = false) + private Integer version; + + @Column(name = "USERNAME", nullable = false) + private String username; + + @Secret + @SystemLevel + @Column(name = "PASSWORD") + private String password; + + @Column(name = "FIRST_NAME") + private String firstName; + + @Column(name = "LAST_NAME") + private String lastName; + + @Email + @Column(name = "EMAIL") + private String email; + + @Column(name = "ACTIVE") + private Boolean active = true; + + @Column(name = "TIME_ZONE_ID") + private String timeZoneId; + + @PasswordChangeRequired + @Column(name = "PASSWORD_CHANGE_REQUIRED") + private Boolean passwordChangeRequired = false; + + @Transient + private Collection authorities; + + public UUID getId() { + return id; + } + + public void setId(final UUID id) { + this.id = id; + } + + public Integer getVersion() { + return version; + } + + public void setVersion(final Integer version) { + this.version = version; + } + + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return username; + } + + public void setUsername(final String username) { + this.username = username; + } + + public Boolean getActive() { + return active; + } + + public void setActive(final Boolean active) { + this.active = active; + } + + public void setPassword(final String password) { + this.password = password; + } + + public String getEmail() { + return email; + } + + public void setEmail(final String email) { + this.email = email; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(final String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(final String lastName) { + this.lastName = lastName; + } + + public Boolean getPasswordChangeRequired() { + return passwordChangeRequired; + } + + public void setPasswordChangeRequired(final Boolean passwordChangeRequired) { + this.passwordChangeRequired = passwordChangeRequired; + } + + @Override + public Collection getAuthorities() { + return authorities != null ? authorities : Collections.emptyList(); + } + + @Override + public void setAuthorities(final Collection authorities) { + this.authorities = authorities; + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return Boolean.TRUE.equals(active); + } + + @InstanceName + @DependsOnProperties({"firstName", "lastName", "username"}) + public String getDisplayName() { + return String.format("%s %s [%s]", (firstName != null ? firstName : ""), + (lastName != null ? lastName : ""), username).trim(); + } + + @Override + public String getTimeZoneId() { + return timeZoneId; + } + + @Override + public boolean isAutoTimeZone() { + return true; + } + + public void setTimeZoneId(final String timeZoneId) { + this.timeZoneId = timeZoneId; + } +} \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/AiChatEx1SecurityConfiguration.java b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/AiChatEx1SecurityConfiguration.java new file mode 100644 index 00000000..7c2976cd --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/AiChatEx1SecurityConfiguration.java @@ -0,0 +1,49 @@ +package com.company.demo.security; + +import io.jmix.core.JmixSecurityFilterChainOrder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; + +/** + * This configuration complements standard security configurations that come from Jmix modules (security-flowui, oidc, + * authserver). + *

+ * You can configure custom API endpoints security by defining {@link SecurityFilterChain} beans in this class. + * In most cases, custom SecurityFilterChain must be applied first, so the proper + * {@link org.springframework.core.annotation.Order} should be defined for the bean. The order value from the + * {@link io.jmix.core.JmixSecurityFilterChainOrder#CUSTOM} is guaranteed to be smaller than any other filter chain + * order from Jmix. + *

+ * Example: + * + *

+ * @Bean
+ * @Order(JmixSecurityFilterChainOrder.CUSTOM)
+ * SecurityFilterChain publicFilterChain(HttpSecurity http) throws Exception {
+ *     http.securityMatcher("/public/**")
+ *             .authorizeHttpRequests(authorize ->
+ *                     authorize.anyRequest().permitAll()
+ *             );
+ *     return http.build();
+ * }
+ * 
+ * + * @see io.jmix.securityflowui.security.FlowuiVaadinWebSecurity + */ +@Configuration +public class AiChatEx1SecurityConfiguration { + + @Bean + @Order(JmixSecurityFilterChainOrder.CUSTOM) + SecurityFilterChain publicFilterChain(HttpSecurity http) throws Exception { + http.securityMatcher("/public/**") + .authorizeHttpRequests(authorize -> + authorize.anyRequest().permitAll() + ); + + return http.build(); + } +} \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/DatabaseUserRepository.java b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/DatabaseUserRepository.java new file mode 100644 index 00000000..89e16370 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/DatabaseUserRepository.java @@ -0,0 +1,31 @@ +package com.company.demo.security; + +import com.company.demo.entity.User; +import io.jmix.securitydata.user.AbstractDatabaseUserRepository; +import org.springframework.context.annotation.Primary; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Component; + +import java.util.Collection; + +@Primary +@Component("UserRepository") +public class DatabaseUserRepository extends AbstractDatabaseUserRepository { + + @Override + protected Class getUserClass() { + return User.class; + } + + @Override + protected void initSystemUser(final User systemUser) { + final Collection authorities = getGrantedAuthoritiesBuilder() + .addResourceRole(FullAccessRole.CODE) + .build(); + systemUser.setAuthorities(authorities); + } + + @Override + protected void initAnonymousUser(final User anonymousUser) { + } +} \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/FullAccessRole.java b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/FullAccessRole.java new file mode 100644 index 00000000..f380c5fb --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/FullAccessRole.java @@ -0,0 +1,23 @@ +package com.company.demo.security; + +import io.jmix.security.model.EntityAttributePolicyAction; +import io.jmix.security.model.EntityPolicyAction; +import io.jmix.security.role.annotation.EntityAttributePolicy; +import io.jmix.security.role.annotation.EntityPolicy; +import io.jmix.security.role.annotation.ResourceRole; +import io.jmix.security.role.annotation.SpecificPolicy; +import io.jmix.securityflowui.role.annotation.MenuPolicy; +import io.jmix.securityflowui.role.annotation.ViewPolicy; + +@ResourceRole(name = "Full Access", code = FullAccessRole.CODE) +public interface FullAccessRole { + + String CODE = "system-full-access"; + + @EntityPolicy(entityName = "*", actions = {EntityPolicyAction.ALL}) + @EntityAttributePolicy(entityName = "*", attributes = "*", action = EntityAttributePolicyAction.MODIFY) + @ViewPolicy(viewIds = "*") + @MenuPolicy(menuIds = "*") + @SpecificPolicy(resources = "*") + void fullAccess(); +} \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/UiMinimalRole.java b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/UiMinimalRole.java new file mode 100644 index 00000000..c557d27b --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/security/UiMinimalRole.java @@ -0,0 +1,20 @@ +package com.company.demo.security; + +import io.jmix.security.model.SecurityScope; +import io.jmix.security.role.annotation.ResourceRole; +import io.jmix.security.role.annotation.SpecificPolicy; +import io.jmix.securityflowui.role.UiMinimalPolicies; +import io.jmix.securityflowui.role.annotation.ViewPolicy; + +@ResourceRole(name = "UI: minimal access", code = UiMinimalRole.CODE, scope = SecurityScope.UI) +public interface UiMinimalRole extends UiMinimalPolicies { + + String CODE = "ui-minimal"; + + @ViewPolicy(viewIds = "MainView") + void main(); + + @ViewPolicy(viewIds = "LoginView") + @SpecificPolicy(resources = "ui.loginToUi") + void login(); +} diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/login/LoginView.java b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/login/LoginView.java new file mode 100644 index 00000000..2d2b9183 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/login/LoginView.java @@ -0,0 +1,140 @@ +package com.company.demo.view.login; + +import com.vaadin.flow.component.UI; +import com.vaadin.flow.component.login.AbstractLogin.LoginEvent; +import com.vaadin.flow.component.login.LoginI18n; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.i18n.LocaleChangeEvent; +import com.vaadin.flow.i18n.LocaleChangeObserver; +import com.vaadin.flow.router.Route; +import com.vaadin.flow.server.VaadinSession; +import io.jmix.core.CoreProperties; +import io.jmix.core.MessageTools; +import io.jmix.core.security.AccessDeniedException; +import io.jmix.flowui.Notifications; +import io.jmix.flowui.component.loginform.JmixLoginForm; +import io.jmix.flowui.kit.component.ComponentUtils; +import io.jmix.flowui.kit.component.loginform.JmixLoginI18n; +import io.jmix.flowui.view.*; +import io.jmix.securityflowui.authentication.AuthDetails; +import io.jmix.securityflowui.authentication.LoginViewSupport; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.authentication.LockedException; + +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Route(value = "login") +@ViewController(id = "LoginView") +@ViewDescriptor(path = "login-view.xml") +public class LoginView extends StandardView implements LocaleChangeObserver { + + private static final Logger log = LoggerFactory.getLogger(LoginView.class); + + @Autowired + private CoreProperties coreProperties; + + @Autowired + private LoginViewSupport loginViewSupport; + + @Autowired + private MessageTools messageTools; + + @Autowired + private Notifications notifications; + + @ViewComponent + private JmixLoginForm login; + + @ViewComponent + private MessageBundle messageBundle; + + @Value("${ui.login.defaultUsername:}") + private String defaultUsername; + + @Value("${ui.login.defaultPassword:}") + private String defaultPassword; + + @Subscribe + public void onInit(final InitEvent event) { + initLocales(); + initDefaultCredentials(); + showWarningIfDefaultCredentials(); + } + + private void showWarningIfDefaultCredentials() { + if ("admin".equals(defaultPassword)) { + notifications.create("WARNING: Change admin password and remove ui.login.default* properties when deploying the application to production") + .withPosition(Notification.Position.BOTTOM_CENTER) + .withDuration(10000) + .show(); + } + } + + private void initLocales() { + LinkedHashMap locales = coreProperties.getAvailableLocales().stream() + .collect(Collectors.toMap(Function.identity(), messageTools::getLocaleDisplayName, (s1, s2) -> s1, + LinkedHashMap::new)); + + ComponentUtils.setItemsMap(login, locales); + + login.setSelectedLocale(VaadinSession.getCurrent().getLocale()); + } + + private void initDefaultCredentials() { + if (StringUtils.isNotBlank(defaultUsername)) { + login.setUsername(defaultUsername); + } + + if (StringUtils.isNotBlank(defaultPassword)) { + login.setPassword(defaultPassword); + } + } + + @Subscribe("login") + public void onLogin(final LoginEvent event) { + try { + loginViewSupport.authenticate( + AuthDetails.of(event.getUsername(), event.getPassword()) + .withLocale(login.getSelectedLocale()) + .withRememberMe(login.isRememberMe()) + ); + } catch (final BadCredentialsException | DisabledException | LockedException | AccessDeniedException e) { + log.warn("Login failed for user '{}': {}", event.getUsername(), e.toString()); + event.getSource().setError(true); + } + } + + @Override + public void localeChange(final LocaleChangeEvent event) { + UI.getCurrent().getPage().setTitle(messageBundle.getMessage("LoginView.title")); + + final JmixLoginI18n loginI18n = JmixLoginI18n.createDefault(); + + final JmixLoginI18n.JmixForm form = new JmixLoginI18n.JmixForm(); + form.setTitle(messageBundle.getMessage("loginForm.headerTitle")); + form.setUsername(messageBundle.getMessage("loginForm.username")); + form.setPassword(messageBundle.getMessage("loginForm.password")); + form.setSubmit(messageBundle.getMessage("loginForm.submit")); + form.setForgotPassword(messageBundle.getMessage("loginForm.forgotPassword")); + form.setRememberMe(messageBundle.getMessage("loginForm.rememberMe")); + loginI18n.setForm(form); + + final LoginI18n.ErrorMessage errorMessage = new LoginI18n.ErrorMessage(); + errorMessage.setTitle(messageBundle.getMessage("loginForm.errorTitle")); + errorMessage.setMessage(messageBundle.getMessage("loginForm.badCredentials")); + errorMessage.setUsername(messageBundle.getMessage("loginForm.errorUsername")); + errorMessage.setPassword(messageBundle.getMessage("loginForm.errorPassword")); + loginI18n.setErrorMessage(errorMessage); + + login.setI18n(loginI18n); + } +} diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/main/MainView.java b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/main/MainView.java new file mode 100644 index 00000000..1b5068d8 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/main/MainView.java @@ -0,0 +1,118 @@ +package com.company.demo.view.main; + +import com.company.demo.entity.User; +import com.google.common.base.Strings; +import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.avatar.Avatar; +import com.vaadin.flow.component.avatar.AvatarVariant; +import com.vaadin.flow.component.html.Div; +import com.vaadin.flow.component.html.Span; +import com.vaadin.flow.router.Route; +import io.jmix.core.Messages; +import io.jmix.core.usersubstitution.CurrentUserSubstitution; +import io.jmix.flowui.UiComponents; +import io.jmix.flowui.app.main.StandardMainView; +import io.jmix.flowui.view.Install; +import io.jmix.flowui.view.ViewController; +import io.jmix.flowui.view.ViewDescriptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; + +@Route("") +@ViewController(id = "MainView") +@ViewDescriptor(path = "main-view.xml") +public class MainView extends StandardMainView { + + @Autowired + private Messages messages; + @Autowired + private UiComponents uiComponents; + @Autowired + private CurrentUserSubstitution currentUserSubstitution; + + @Install(to = "userMenu", subject = "buttonRenderer") + private Component userMenuButtonRenderer(final UserDetails userDetails) { + if (!(userDetails instanceof User user)) { + return null; + } + + String userName = generateUserName(user); + + Div content = uiComponents.create(Div.class); + content.setClassName("user-menu-button-content"); + + Avatar avatar = createAvatar(userName); + + Span name = uiComponents.create(Span.class); + name.setText(userName); + name.setClassName("user-menu-text"); + + content.add(avatar, name); + + if (isSubstituted(user)) { + Span subtext = uiComponents.create(Span.class); + subtext.setText(messages.getMessage("userMenu.substituted")); + subtext.setClassName("user-menu-subtext"); + + content.add(subtext); + } + + return content; + } + + @Install(to = "userMenu", subject = "headerRenderer") + private Component userMenuHeaderRenderer(final UserDetails userDetails) { + if (!(userDetails instanceof User user)) { + return null; + } + + Div content = uiComponents.create(Div.class); + content.setClassName("user-menu-header-content"); + + String name = generateUserName(user); + + Avatar avatar = createAvatar(name); + avatar.addThemeVariants(AvatarVariant.LARGE); + + Span text = uiComponents.create(Span.class); + text.setText(name); + text.setClassName("user-menu-text"); + + content.add(avatar, text); + + if (name.equals(user.getUsername())) { + text.addClassName("user-menu-text-subtext"); + } else { + Span subtext = uiComponents.create(Span.class); + subtext.setText(user.getUsername()); + subtext.setClassName("user-menu-subtext"); + + content.add(subtext); + } + + return content; + } + + private Avatar createAvatar(String fullName) { + Avatar avatar = uiComponents.create(Avatar.class); + avatar.setName(fullName); + avatar.getElement().setAttribute("tabindex", "-1"); + avatar.setClassName("user-menu-avatar"); + + return avatar; + } + + private String generateUserName(User user) { + String userName = String.format("%s %s", + Strings.nullToEmpty(user.getFirstName()), + Strings.nullToEmpty(user.getLastName())) + .trim(); + + return userName.isEmpty() ? user.getUsername() : userName; + } + + private boolean isSubstituted(User user) { + UserDetails authenticatedUser = currentUserSubstitution.getAuthenticatedUser(); + return user != null && !authenticatedUser.getUsername().equals(user.getUsername()); + } +} diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/user/UserDetailView.java b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/user/UserDetailView.java new file mode 100644 index 00000000..1eb2d5db --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/user/UserDetailView.java @@ -0,0 +1,94 @@ +package com.company.demo.view.user; + +import com.company.demo.entity.User; +import com.company.demo.view.main.MainView; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.notification.Notification; +import com.vaadin.flow.component.notification.NotificationVariant; +import com.vaadin.flow.component.textfield.PasswordField; +import com.vaadin.flow.router.Route; +import io.jmix.core.EntityStates; +import io.jmix.flowui.Notifications; +import io.jmix.flowui.component.textfield.TypedTextField; +import io.jmix.flowui.view.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.List; +import java.util.Objects; +import java.util.TimeZone; + +@Route(value = "users/:id", layout = MainView.class) +@ViewController(id = "User.detail") +@ViewDescriptor(path = "user-detail-view.xml") +@EditedEntityContainer("userDc") +public class UserDetailView extends StandardDetailView { + + @ViewComponent + private TypedTextField usernameField; + @ViewComponent + private PasswordField passwordField; + @ViewComponent + private PasswordField confirmPasswordField; + @ViewComponent + private ComboBox timeZoneField; + @ViewComponent + private MessageBundle messageBundle; + @Autowired + private Notifications notifications; + + @Autowired + private EntityStates entityStates; + @Autowired + private PasswordEncoder passwordEncoder; + + private boolean newEntity; + + @Subscribe + public void onInit(final InitEvent event) { + timeZoneField.setItems(List.of(TimeZone.getAvailableIDs())); + } + + @Subscribe + public void onInitEntity(final InitEntityEvent event) { + usernameField.setReadOnly(false); + passwordField.setVisible(true); + confirmPasswordField.setVisible(true); + } + + @Subscribe + public void onReady(final ReadyEvent event) { + if (entityStates.isNew(getEditedEntity())) { + usernameField.focus(); + } + } + + @Subscribe + public void onValidation(final ValidationEvent event) { + if (entityStates.isNew(getEditedEntity()) + && !Objects.equals(passwordField.getValue(), confirmPasswordField.getValue())) { + event.getErrors().add(messageBundle.getMessage("passwordsDoNotMatch")); + } + } + + @Subscribe + public void onBeforeSave(final BeforeSaveEvent event) { + if (entityStates.isNew(getEditedEntity())) { + getEditedEntity().setPassword(passwordEncoder.encode(passwordField.getValue())); + + newEntity = true; + } + } + + @Subscribe + public void onAfterSave(final AfterSaveEvent event) { + if (newEntity) { + notifications.create(messageBundle.getMessage("noAssignedRolesNotification")) + .withThemeVariant(NotificationVariant.WARNING) + .withPosition(Notification.Position.TOP_END) + .show(); + + newEntity = false; + } + } +} \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/user/UserListView.java b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/user/UserListView.java new file mode 100644 index 00000000..8fd327b2 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/java/com/company/demo/view/user/UserListView.java @@ -0,0 +1,14 @@ +package com.company.demo.view.user; + +import com.company.demo.entity.User; +import com.company.demo.view.main.MainView; +import com.vaadin.flow.router.Route; +import io.jmix.flowui.view.*; + +@Route(value = "users", layout = MainView.class) +@ViewController(id = "User.list") +@ViewDescriptor(path = "user-list-view.xml") +@LookupComponent("usersDataGrid") +@DialogMode(width = "64em") +public class UserListView extends StandardListView { +} \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/icons/icon.png b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/icons/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..910199d984cd0a6a3cf6d69ad18a3ae7c0abe0ed GIT binary patch literal 11979 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 literal 0 HcmV?d00001 diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/public/images/logo.png b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/public/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..678fa53e96ca0058b8eeae0cb901e3f7e0c695e7 GIT binary patch literal 27095 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 D8e(oN literal 0 HcmV?d00001 diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/ai-chat-ex1.css b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/ai-chat-ex1.css new file mode 100644 index 00000000..d85e934f --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/ai-chat-ex1.css @@ -0,0 +1 @@ +/* Define your styles here */ \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/styles.css b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/styles.css new file mode 100644 index 00000000..afc9435c --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/styles.css @@ -0,0 +1,4 @@ +@import url('view/main-view.css'); +@import url('view/main-view-top-menu.css'); +@import url('view/login-view.css'); +@import url('ai-chat-ex1.css'); \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/view/login-view.css b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/view/login-view.css new file mode 100644 index 00000000..cbab2bef --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/view/login-view.css @@ -0,0 +1,9 @@ +/* Predefined styles for the LoginView */ + +.jmix-login-main-layout jmix-login-form::part(form) { + --vaadin-login-form-border-radius: var(--vaadin-radius-l); + --vaadin-login-form-background: var(--aura-surface-color); + + box-shadow: var(--aura-shadow-s); + border: 1px solid var(--vaadin-border-color); +} diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/view/main-view-top-menu.css b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/view/main-view-top-menu.css new file mode 100644 index 00000000..9fc6f093 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/view/main-view-top-menu.css @@ -0,0 +1,57 @@ +/* Predefined styles for the MainView with a HorizontalMenu */ + +.jmix-main-view-top-menu-app-layout { + --vaadin-app-layout-drawer-width: 0px; +} + +vaadin-app-layout.jmix-main-view-top-menu-app-layout::part(navbar) { + min-height: 0; + border-bottom: 0; +} + +.jmix-main-view-top-menu-navigation-bar-box { + padding: 0; + width: 100%; +} + +.jmix-main-view-top-menu-view-header-box { + width: 100%; + border-top: 1px solid var(--vaadin-border-color-secondary) +} + +.jmix-main-view-top-menu-navigation { + display: flex; + flex-grow: 1; + overflow: auto; +} + +.jmix-main-view-top-menu-header { + display: flex; + align-items: center; + width: 100%; + box-sizing: border-box; + gap: var(--vaadin-gap-m); +} + +.jmix-main-view-top-menu-logo-container { + display: flex; + margin: 0 var(--vaadin-padding-m); +} + +.jmix-main-view-top-menu-logo { + --logo-size: round(var(--aura-base-size) * 2.5 * 1px, 1px); + width: var(--logo-size); + height: var(--logo-size); +} + +.jmix-main-view-top-menu-user-box { + align-self: flex-end; + align-items: center; + margin: 0 var(--vaadin-padding-m); + max-width: 20em; +} + +.jmix-main-view-top-menu-view-title { + font-size: var(--aura-font-size-xl); + margin: var(--vaadin-padding-s) var(--vaadin-padding-m); +} diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/view/main-view.css b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/view/main-view.css new file mode 100644 index 00000000..77188e8f --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-aura/view/main-view.css @@ -0,0 +1,273 @@ +/* Predefined styles for the MainView */ + +.jmix-main-view-app-layout { + --vaadin-app-layout-drawer-width: 256px; +} + +vaadin-app-layout.jmix-main-view-app-layout:not([primary-section='navbar']) { + --vaadin-app-layout-navbar-padding-top: var(--aura-app-layout-inset); + --vaadin-app-layout-navbar-padding-inline-start: var(--aura-app-layout-inset); + --vaadin-app-layout-navbar-padding-inline-end: var(--aura-app-layout-inset); + --vaadin-app-layout-navbar-padding-bottom: 0px; + + transition: padding-bottom var(--vaadin-app-layout-transition-duration), + padding-inline-start var(--vaadin-app-layout-transition-duration), + padding-inline-end var(--vaadin-app-layout-transition-duration); +} + +vaadin-app-layout.jmix-main-view-app-layout[drawer-opened]:not([primary-section='navbar']) { + --vaadin-app-layout-navbar-padding-inline-start: 0px; +} + +vaadin-app-layout.jmix-main-view-app-layout:not([primary-section='navbar'])::part(navbar) { + transition: inset-inline-start var(--vaadin-app-layout-transition-duration), + padding var(--vaadin-app-layout-transition-duration); +} + +vaadin-app-layout.jmix-main-view-app-layout[has-navbar]:not([primary-section='navbar'])::part(content) { + border-top-width: 0; + border-start-start-radius: 0; + border-start-end-radius: 0; +} + +vaadin-app-layout.jmix-main-view-app-layout[has-navbar][has-drawer][drawer-opened]:not([overlay]):not([primary-section='navbar'])::part(content) { + border-start-start-radius: 0; +} + +vaadin-app-layout.jmix-main-view-app-layout:not([primary-section='navbar']) .jmix-main-view-header { + background: linear-gradient(var(--aura-surface-color), var(--aura-surface-color)), var(--aura-app-background); + background-clip: padding-box; + background-origin: border-box; + + border: var(--aura-app-layout-border-width) solid var(--vaadin-border-color-secondary); + border-top-width: min(var(--aura-app-layout-inset), var(--aura-app-layout-border-width)); + border-inline-width: min(var(--aura-app-layout-inset), var(--aura-app-layout-border-width)); + border-bottom-width: 1px; + border-start-start-radius: var(--_app-layout-radius); + border-start-end-radius: var(--_app-layout-radius); + + padding: var(--vaadin-padding-s) var(--vaadin-padding-m); +} + +vaadin-app-layout.jmix-main-view-app-layout:not([primary-section='navbar'])::part(navbar-bottom) { + border-top: 1px solid var(--vaadin-border-color-secondary); +} + +vaadin-app-layout.jmix-main-view-app-layout[has-drawer][drawer-opened]:not([overlay]):not([primary-section='navbar']) > .jmix-main-view-header { + border-inline-start-width: var(--aura-app-layout-border-width); +} + +.jmix-main-view-header { + display: flex; + align-items: center; + width: 100%; + box-sizing: border-box; + gap: var(--vaadin-gap-m); +} + +.jmix-main-view-title { + font-size: var(--aura-font-size-xl); +} + +.jmix-main-view-section { + display: flex; + flex-direction: column; + align-items: stretch; + max-height: 100%; + min-height: 100%; +} + +.jmix-main-view-application-title { + display: flex; + align-items: center; + margin: 0; + padding: var(--vaadin-padding-m); + font-size: var(--aura-font-size-l); +} + +.jmix-main-view-application-title-base-link { + color: var(--vaadin-text-color); + text-decoration: none; +} + +.jmix-main-view-navigation { + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: auto; +} + +.jmix-main-view-footer { + display: flex; + align-items: center; + gap: var(--vaadin-gap-m); + padding: var(--vaadin-padding-m); + padding-bottom: max(0px, var(--aura-app-layout-inset) - var(--vaadin-padding-m)); + border-top: 1px solid var(--vaadin-border-color-secondary); +} + +.jmix-main-view-footer .jmix-user-indicator { + flex-grow: 1; +} + +/* User Menu */ + +.jmix-main-view-user-menu { + margin-inline-start: auto; +} + +.user-menu-button-content, +.user-menu-header-content { + display: grid; + grid-template: "avatar text" + "avatar subtext"; + grid-template-columns: auto 1fr; + column-gap: var(--vaadin-gap-s); + + width: max-content; + box-sizing: border-box; + + color: var(--vaadin-text-color); + padding: var(--vaadin-padding-xs) var(--vaadin-padding-s); +} + +.user-menu-header-content { + width: 100%; + padding-inline-end: var(--vaadin-padding-l); +} + +.user-menu-button-content > .user-menu-avatar, +.user-menu-header-content > .user-menu-avatar { + grid-area: avatar; + align-self: center; +} + +.user-menu-button-content > .user-menu-text { + grid-row: text / subtext; +} + +.jmix-main-view-user-menu[theme~='substituted'] .user-menu-button-content > .user-menu-text { + grid-row: text; +} + +.user-menu-header-content > .user-menu-text { + grid-area: text; + + color: var(--vaadin-text-color); + font-weight: 700; + font-size: var(--aura-font-size-m); +} + +.user-menu-header-content > .user-menu-text-subtext { + grid-row: text / subtext; +} + +.user-menu-button-content > .user-menu-text, +.user-menu-header-content > .user-menu-text { + align-self: center; + text-align: start; + + width: 100%; + overflow: hidden; + text-overflow: ellipsis; +} + +.user-menu-button-content > .user-menu-subtext, +.user-menu-header-content > .user-menu-subtext { + grid-area: subtext; + align-self: center; + text-align: start; + + color: var(--vaadin-text-color-secondary); + font-size: var(--aura-font-size-xs); + + width: 100%; + overflow: hidden; + text-overflow: ellipsis; +} + +.jmix-main-view-user-menu:not([theme~='substituted']) .user-menu-button-content > .user-menu-subtext { + display: none; +} + +/* Initial Layout */ + +.jmix-initial-layout { + --title-size: round(var(--aura-font-size-m) * 2.5, 0.0625rem); + --title-color: var(--vaadin-text-color-secondary); + + width: 100%; + height: 100%; + + align-items: center; + justify-content: center; + + container-type: inline-size; + container-name: jmix-initial-layout; +} + +.jmix-initial-layout-content { + display: flex; + justify-content: space-between; + width: 100%; + max-width: 50rem; + padding: var(--vaadin-padding-xl); + box-sizing: border-box; +} + +.jmix-initial-layout-title { + position: relative; + + color: var(--title-color); + font-size: var(--title-size); + line-height: calc(4 * var(--title-size)); + box-sizing: border-box; +} + +.jmix-initial-layout-title:after { + position: absolute; + width: 100%; + height: 0.3rem; + content: ''; + background: var(--title-color); + top: 0; + left: 0; +} + +.jmix-initial-layout-logo { + --logo-size: calc(2.5 * var(--title-size)); + width: var(--logo-size); + height: var(--logo-size); +} + +@container jmix-initial-layout (max-width: 45rem) { + .jmix-initial-layout-content { + flex-direction: column-reverse; + align-items: center; + gap: var(--vaadin-gap-l); + } + + .jmix-initial-layout-title { + padding-top: var(--vaadin-padding-m); + line-height: var(--aura-line-height-m); + text-align: center; + } +} + +/* MenuFilterField component */ + +.jmix-main-view-navigation > .jmix-menu-filter-field { + margin: var(--vaadin-padding-s) var(--vaadin-padding-m); +} + +/* Search add-on */ + +.jmix-main-view-navigation > .jmix-search-field { + display: flex; + margin: auto var(--vaadin-padding-m) var(--vaadin-padding-s) var(--vaadin-padding-m); +} + +.jmix-main-view-header > .jmix-search-field { + margin-inline-start: auto; + margin-inline-end: var(--vaadin-padding-m); +} diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/ai-chat-ex1.css b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/ai-chat-ex1.css new file mode 100644 index 00000000..9ae8e5e9 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/ai-chat-ex1.css @@ -0,0 +1 @@ +/* Define your styles here */ diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/styles.css b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/styles.css new file mode 100644 index 00000000..afc9435c --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/styles.css @@ -0,0 +1,4 @@ +@import url('view/main-view.css'); +@import url('view/main-view-top-menu.css'); +@import url('view/login-view.css'); +@import url('ai-chat-ex1.css'); \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/view/login-view.css b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/view/login-view.css new file mode 100644 index 00000000..c04446c2 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/view/login-view.css @@ -0,0 +1,5 @@ +/* Predefined styles for the LoginView */ + +.jmix-login-main-layout { + background-color: var(--lumo-shade-5pct); +} \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/view/main-view-top-menu.css b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/view/main-view-top-menu.css new file mode 100644 index 00000000..766cc3d2 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/view/main-view-top-menu.css @@ -0,0 +1,54 @@ +/* Predefined styles for the MainView with a HorizontalMenu */ + +vaadin-app-layout.jmix-main-view-top-menu-app-layout::part(navbar) { + min-height: 0; + border-bottom: 0; +} + +.jmix-main-view-top-menu-navigation-bar-box { + padding: 0; + gap: 0; + width: 100%; +} + +.jmix-main-view-top-menu-navigation { + display: flex; + flex-grow: 1; + overflow: auto; +} + +.jmix-main-view-top-menu-header { + display: flex; + align-items: center; + width: 100%; + border-bottom: 1px solid var(--lumo-contrast-10pct) +} + +.jmix-main-view-top-menu-logo-container { + display: flex; + margin: 0 var(--lumo-space-m); +} + +.jmix-main-view-top-menu-logo { + width: var(--lumo-size-m); + height: var(--lumo-size-m); +} + +.jmix-main-view-top-menu-user-box { + align-self: flex-end; + align-items: center; + margin: 0 var(--lumo-space-m); + max-width: 20em; +} + +.jmix-main-view-top-menu-view-header-box { + border-bottom: 1px solid var(--lumo-contrast-10pct); + padding: 0; + width: 100%; +} + +.jmix-main-view-top-menu-view-title { + font-size: var(--lumo-font-size-l); + margin: var(--lumo-space-s) var(--lumo-space-m); +} + diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/view/main-view.css b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/view/main-view.css new file mode 100644 index 00000000..994c1e25 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/META-INF/resources/themes/ai-chat-ex1-lumo/view/main-view.css @@ -0,0 +1,233 @@ +/* Predefined styles for the MainView */ + +.jmix-main-view-header { + box-sizing: border-box; + display: flex; + height: var(--lumo-size-xl); + align-items: center; + width: 100%; +} + +.jmix-main-view-drawer-toggle { + color: var(--lumo-secondary-text-color); +} + +.jmix-main-view-title { + margin: 0; + font-size: var(--lumo-font-size-l); +} + +.jmix-main-view-section { + display: flex; + flex-direction: column; + align-items: stretch; + max-height: 100%; + min-height: 100%; +} + +.jmix-main-view-application-title { + display: flex; + align-items: center; + height: var(--lumo-size-xl); + margin: 0; + padding-left: var(--lumo-space-m); + padding-right: var(--lumo-space-m); + font-size: var(--lumo-font-size-m); +} + +.jmix-main-view-application-title-base-link { + color: var(--lumo-header-text-color); +} + +.jmix-main-view-application-title-base-link:hover { + text-decoration: none; +} + +.jmix-main-view-navigation { + display: flex; + flex-direction: column; + border-bottom: 1px solid; + border-color: var(--lumo-contrast-10pct); + flex-grow: 1; + overflow: auto; +} + +.jmix-main-view-footer { + display: flex; + align-items: center; + margin-bottom: var(--lumo-space-s); + margin-top: var(--lumo-space-s); + padding: var(--lumo-space-xs) var(--lumo-space-m); + gap: var(--lumo-space-m); +} + +.jmix-main-view-footer .jmix-user-indicator { + flex-grow: 1; +} + +/* User Menu */ + +.jmix-main-view-user-menu { + margin-inline-start: auto; + margin-inline-end: var(--lumo-space-m); +} + +.jmix-main-view-footer :is(.jmix-user-menu-button-content, .user-menu-button-content) { + width: calc(var(--vaadin-app-layout-drawer-width, 16em) - var(--lumo-space-m) * 2); +} + +.user-menu-button-content, +.user-menu-header-content { + display: grid; + grid-template: "avatar text" + "avatar subtext"; + grid-template-columns: auto 1fr; + column-gap: var(--lumo-space-s); + + width: max-content; + box-sizing: border-box; + + color: var(--lumo-body-text-color); + padding: var(--lumo-space-xs) var(--lumo-space-s); +} + +.user-menu-header-content { + width: 100%; + padding-inline-end: var(--lumo-space-l); +} + +.user-menu-button-content > .user-menu-avatar, +.user-menu-header-content > .user-menu-avatar { + grid-area: avatar; + align-self: center; +} + +.user-menu-button-content > .user-menu-text { + grid-row: text / subtext; +} + +.jmix-main-view-user-menu[theme~='substituted'] .user-menu-button-content > .user-menu-text { + grid-row: text; +} + +.user-menu-header-content > .user-menu-text { + grid-area: text; + + color: var(--lumo-body-text-color); + font-weight: 700; + font-size: var(--lumo-font-size-m); +} + +.user-menu-header-content > .user-menu-text-subtext { + grid-row: text / subtext; +} + +.user-menu-button-content > .user-menu-text, +.user-menu-header-content > .user-menu-text { + align-self: center; + text-align: start; + + width: 100%; + overflow: hidden; + text-overflow: ellipsis; +} + +.user-menu-button-content > .user-menu-subtext, +.user-menu-header-content > .user-menu-subtext { + grid-area: subtext; + align-self: center; + text-align: start; + + color: var(--lumo-secondary-text-color); + font-size: var(--lumo-font-size-xs); + + width: 100%; + overflow: hidden; + text-overflow: ellipsis; +} + +.jmix-main-view-user-menu:not([theme~='substituted']) .user-menu-button-content > .user-menu-subtext { + display: none; +} + +/* Initial Layout */ + +.jmix-initial-layout { + --title-size: var(--lumo-font-size-xxxl); + --title-color: var(--lumo-secondary-text-color); + + width: 100%; + height: 100%; + + align-items: center; + justify-content: center; + + container-type: inline-size; + container-name: jmix-initial-layout; +} + +.jmix-initial-layout-content { + display: flex; + justify-content: space-between; + width: 100%; + max-width: 50rem; + padding: var(--lumo-space-xl); + box-sizing: border-box; +} + +.jmix-initial-layout-title { + position: relative; + + color: var(--title-color); + font-size: var(--title-size); + line-height: calc(4 * var(--title-size)); + box-sizing: border-box; +} + +.jmix-initial-layout-title:after { + position: absolute; + width: 100%; + height: 0.3rem; + content: ''; + background: var(--title-color); + top: 0; + left: 0; +} + +.jmix-initial-layout-logo { + --logo-size: calc(2.5 * var(--title-size)); + width: var(--logo-size); + height: var(--logo-size); +} + +@container jmix-initial-layout (max-width: 45rem) { + .jmix-initial-layout-content { + flex-direction: column-reverse; + align-items: center; + gap: var(--lumo-space-l); + } + + .jmix-initial-layout-title { + padding-top: var(--lumo-space-m); + line-height: var(--lumo-line-height-m); + text-align: center; + } +} + +/* MenuFilterField component */ + +.jmix-main-view-navigation > .jmix-menu-filter-field { + margin: var(--lumo-space-s) var(--lumo-space-m); +} + +/* Search add-on */ + +.jmix-main-view-navigation > .jmix-search-field { + display: flex; + margin: auto var(--lumo-space-m) var(--lumo-space-s) var(--lumo-space-m); +} + +.jmix-main-view-header > .jmix-search-field { + margin-inline-start: auto; + margin-inline-end: var(--lumo-space-m); +} \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/application.properties b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/application.properties new file mode 100644 index 00000000..1dc84301 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/application.properties @@ -0,0 +1,38 @@ +main.datasource.url=jdbc:hsqldb:file:.jmix/hsqldb/aichatex1 +main.datasource.username=sa +main.datasource.password= + +main.liquibase.change-log=com/company/demo/liquibase/changelog.xml + +jmix.ui.login-view-id=LoginView +jmix.ui.main-view-id=MainView +jmix.ui.menu-config=com/company/demo/menu.xml +jmix.ui.composite-menu=true + +ui.login.defaultUsername=admin +ui.login.defaultPassword=admin + +jmix.core.available-locales=en + +# Launch the default browser when starting the application in development mode +vaadin.launch-browser=false + +# Enable colors in logs +spring.output.ansi.enabled=always + +# 'debug' level logs SQL generated by EclipseLink ORM +logging.level.eclipselink.logging.sql=info + +# 'debug' level logs data store operations +logging.level.io.jmix.core.datastore=info + +# 'debug' level logs access control constraints +logging.level.io.jmix.core.AccessLogger=debug + +# 'debug' level logs all Jmix debug output +logging.level.io.jmix=info + +# 'debug' or 'trace' levels help to troubleshoot security problems +logging.level.org.springframework.security=info + +logging.level.org.atmosphere=warn \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/liquibase/changelog.xml b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/liquibase/changelog.xml new file mode 100644 index 00000000..d9999f0f --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/liquibase/changelog.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/liquibase/changelog/010-init-user.xml b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/liquibase/changelog/010-init-user.xml new file mode 100644 index 00000000..261a3cd2 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/liquibase/changelog/010-init-user.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/menu.xml b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/menu.xml new file mode 100644 index 00000000..b7c83b0f --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/menu.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/messages_en.properties b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/messages_en.properties new file mode 100644 index 00000000..62e2fbca --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/messages_en.properties @@ -0,0 +1,39 @@ +databaseUniqueConstraintViolation.IDX_USER__ON_USERNAME=A user with the same username already exists + +com.company.demo.entity/User=User +com.company.demo.entity/User.id=ID +com.company.demo.entity/User.username=Username +com.company.demo.entity/User.firstName=First name +com.company.demo.entity/User.lastName=Last name +com.company.demo.entity/User.password=Password +com.company.demo.entity/User.email=Email +com.company.demo.entity/User.timeZoneId=Time zone +com.company.demo.entity/User.active=Active +com.company.demo.entity/User.passwordChangeRequired=Password change required +com.company.demo.entity/User.version=Version + +com.company.demo.view.main/MainView.title=Ai Chat Ex1 +com.company.demo.view.main/applicationTitle.text=Ai Chat Ex1 +com.company.demo.view.main/navigation.ariaLabel=Views +com.company.demo.view.main/drawerToggle.ariaLabel=Menu toggle + +com.company.demo.view.login/LoginView.title=Login - Ai Chat Ex1 +com.company.demo.view.login/loginForm.headerTitle=Ai Chat Ex1 +com.company.demo.view.login/loginForm.username=Username +com.company.demo.view.login/loginForm.password=Password +com.company.demo.view.login/loginForm.submit=Log in +com.company.demo.view.login/loginForm.forgotPassword=Forgot password +com.company.demo.view.login/loginForm.errorTitle=Login failed +com.company.demo.view.login/loginForm.badCredentials=Check that you have entered the correct username and password and try again +com.company.demo.view.login/loginForm.errorUsername=Username is required +com.company.demo.view.login/loginForm.errorPassword=Password is required +com.company.demo.view.login/loginForm.rememberMe=Remember me + +com.company.demo.view.user/UserListView.title=Users +com.company.demo.view.user/UserDetailView.title=User +com.company.demo.view.user/confirmPassword=Confirm password +com.company.demo.view.user/passwordsDoNotMatch=Passwords do not match +com.company.demo.view.user/additionalMenu=Additional +com.company.demo.view.user/noAssignedRolesNotification=Assign roles to the new user + +com.company.demo/menu.application.title=Application \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/view/login/login-view.xml b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/view/login/login-view.xml new file mode 100644 index 00000000..d369e5b4 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/view/login/login-view.xml @@ -0,0 +1,20 @@ + + + + +
+ + + + \ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/view/main/main-view.xml b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/view/main/main-view.xml new file mode 100644 index 00000000..a7f1e6ea --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/view/main/main-view.xml @@ -0,0 +1,59 @@ + + + + + + + + + + +
+

+ +

+ +
+
+ +
+

+ +

+ +
+
+
\ No newline at end of file diff --git a/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/view/user/user-detail-view.xml b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/view/user/user-detail-view.xml new file mode 100644 index 00000000..bd243529 --- /dev/null +++ b/content/modules/ai-chat/examples/ai-chat-ex1/src/main/resources/com/company/demo/view/user/user-detail-view.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +