mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-15 16:20:03 +00:00
Switch to Hilla for UI
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
This directory is automatically generated by Vaadin and contains the pre-compiled
|
||||
frontend files/resources for your project (frontend development bundle).
|
||||
|
||||
It should be added to Version Control System and committed, so that other developers
|
||||
do not have to compile it again.
|
||||
|
||||
Frontend development bundle is automatically updated when needed:
|
||||
- an npm/pnpm package is added with @NpmPackage or directly into package.json
|
||||
- CSS, JavaScript or TypeScript files are added with @CssImport, @JsModule or @JavaScript
|
||||
- Vaadin add-on with front-end customizations is added
|
||||
- Custom theme imports/assets added into 'theme.json' file
|
||||
- Exported web component is added.
|
||||
|
||||
If your project development needs a hot deployment of the frontend changes,
|
||||
you can switch Flow to use Vite development server (default in Vaadin 23.3 and earlier versions):
|
||||
- set `vaadin.frontend.hotdeploy=true` in `application.properties`
|
||||
- configure `vaadin-maven-plugin`:
|
||||
```
|
||||
<configuration>
|
||||
<frontendHotdeploy>true</frontendHotdeploy>
|
||||
</configuration>
|
||||
```
|
||||
- configure `jetty-maven-plugin`:
|
||||
```
|
||||
<configuration>
|
||||
<systemProperties>
|
||||
<vaadin.frontend.hotdeploy>true</vaadin.frontend.hotdeploy>
|
||||
</systemProperties>
|
||||
</configuration>
|
||||
```
|
||||
|
||||
Read more [about Vaadin development mode](https://vaadin.com/docs/next/configuration/development-mode/#pre-compiled-front-end-bundle-for-faster-start-up).
|
||||
Binary file not shown.
@@ -1,90 +0,0 @@
|
||||
package de.grimsi.gameyfin.layouts;
|
||||
|
||||
import com.flowingcode.vaadin.addons.fontawesome.FontAwesome;
|
||||
import com.vaadin.flow.component.ClickEvent;
|
||||
import com.vaadin.flow.component.ComponentEventListener;
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.component.applayout.AppLayout;
|
||||
import com.vaadin.flow.component.avatar.Avatar;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
import com.vaadin.flow.component.contextmenu.MenuItem;
|
||||
import com.vaadin.flow.component.contextmenu.SubMenu;
|
||||
import com.vaadin.flow.component.dependency.CssImport;
|
||||
import com.vaadin.flow.component.dependency.JsModule;
|
||||
import com.vaadin.flow.component.html.Image;
|
||||
import com.vaadin.flow.component.menubar.MenuBar;
|
||||
import com.vaadin.flow.component.menubar.MenuBarVariant;
|
||||
import com.vaadin.flow.component.notification.Notification;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.spring.security.AuthenticationContext;
|
||||
import de.grimsi.gameyfin.resources.PublicResources;
|
||||
import de.grimsi.gameyfin.services.ThemeService;
|
||||
import de.grimsi.gameyfin.setup.SetupService;
|
||||
import de.grimsi.gameyfin.views.SetupView;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.vaadin.firitin.util.style.LumoProps;
|
||||
|
||||
import static de.grimsi.gameyfin.users.util.Utils.isAdmin;
|
||||
|
||||
@JsModule("./scripts/prefers-color-scheme.js")
|
||||
@CssImport("./styles/header.css")
|
||||
public class MainLayout extends AppLayout {
|
||||
|
||||
public MainLayout(AuthenticationContext authContext,
|
||||
@Autowired SetupService setupService,
|
||||
@Autowired ThemeService themeService) {
|
||||
|
||||
if (!setupService.isSetupCompleted()) {
|
||||
UI.getCurrent().navigate(SetupView.class);
|
||||
UI.getCurrent().close();
|
||||
}
|
||||
|
||||
UserDetails user = authContext.getAuthenticatedUser(UserDetails.class).get();
|
||||
|
||||
Image logo = new Image(PublicResources.GAMEYFIN_LOGO.path, "Gameyfin Logo");
|
||||
logo.addClassName("header-logo");
|
||||
|
||||
Button toggleTheme = new Button(FontAwesome.Solid.CIRCLE_HALF_STROKE.create());
|
||||
toggleTheme.addThemeVariants(ButtonVariant.LUMO_ICON);
|
||||
toggleTheme.addClickListener(listener -> themeService.toggleTheme());
|
||||
|
||||
Avatar avatar = new Avatar(user.getUsername());
|
||||
avatar.setAbbreviation(user.getUsername().substring(0, 2).toUpperCase());
|
||||
avatar.setColorIndex(user.getUsername().chars().map(i -> i % 6).findFirst().getAsInt());
|
||||
|
||||
MenuBar menu = new MenuBar();
|
||||
menu.addThemeVariants(MenuBarVariant.LUMO_ICON);
|
||||
MenuItem item = menu.addItem(avatar);
|
||||
SubMenu subMenu = item.getSubMenu();
|
||||
subMenu.addItem(menuItem(FontAwesome.Solid.USER, "Profile", l -> Notification.show("Profile")));
|
||||
if (isAdmin(user)) {
|
||||
subMenu.addItem(menuItem(FontAwesome.Solid.COG, "Administration", l -> Notification.show("Administration")));
|
||||
}
|
||||
subMenu.addItem(menuItem(FontAwesome.Solid.QUESTION_CIRCLE, "Help", l -> Notification.show("Help")));
|
||||
subMenu.addItem(menuItem(FontAwesome.Solid.SIGN_OUT, "Sign out", l -> authContext.logout()));
|
||||
|
||||
HorizontalLayout horizontalLayout = new HorizontalLayout();
|
||||
horizontalLayout.setAlignItems(FlexComponent.Alignment.END);
|
||||
horizontalLayout.add(logo, toggleTheme, menu);
|
||||
|
||||
addToNavbar(horizontalLayout);
|
||||
}
|
||||
|
||||
private HorizontalLayout menuItem(FontAwesome.Solid icon, String title, ComponentEventListener<ClickEvent<HorizontalLayout>> listener) {
|
||||
FontAwesome.Solid.Icon i = icon.create();
|
||||
i.setSize(LumoProps.ICON_SIZE_S.var());
|
||||
|
||||
HorizontalLayout h = new HorizontalLayout();
|
||||
h.setAlignItems(FlexComponent.Alignment.CENTER);
|
||||
h.setJustifyContentMode(FlexComponent.JustifyContentMode.START);
|
||||
|
||||
h.add(i);
|
||||
h.add(title);
|
||||
h.addClickListener(listener);
|
||||
|
||||
return h;
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package de.grimsi.gameyfin.layouts;
|
||||
|
||||
import com.flowingcode.vaadin.addons.fontawesome.FontAwesome;
|
||||
import com.vaadin.flow.component.applayout.AppLayout;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
import com.vaadin.flow.component.dependency.CssImport;
|
||||
import com.vaadin.flow.component.html.Image;
|
||||
import com.vaadin.flow.component.orderedlayout.FlexComponent;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import de.grimsi.gameyfin.resources.PublicResources;
|
||||
import de.grimsi.gameyfin.services.ThemeService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@CssImport("./styles/header.css")
|
||||
public class SetupLayout extends AppLayout {
|
||||
|
||||
public SetupLayout(@Autowired ThemeService themeService) {
|
||||
Image logo = new Image(PublicResources.GAMEYFIN_LOGO.path, "Gameyfin Logo");
|
||||
logo.addClassName("header-logo");
|
||||
|
||||
Button toggleTheme = new Button(FontAwesome.Solid.CIRCLE_HALF_STROKE.create());
|
||||
toggleTheme.addThemeVariants(ButtonVariant.LUMO_ICON);
|
||||
toggleTheme.addClickListener(listener -> themeService.toggleTheme());
|
||||
|
||||
HorizontalLayout horizontalLayout = new HorizontalLayout();
|
||||
horizontalLayout.setWidthFull();
|
||||
horizontalLayout.setAlignSelf(FlexComponent.Alignment.END);
|
||||
horizontalLayout.add(logo, toggleTheme);
|
||||
|
||||
addToNavbar(horizontalLayout);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package de.grimsi.gameyfin.resources;
|
||||
|
||||
public enum PublicResources {
|
||||
GAMEYFIN_LOGO("public/images/Logo.svg");
|
||||
|
||||
public final String path;
|
||||
|
||||
PublicResources(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package de.grimsi.gameyfin.services;
|
||||
|
||||
import com.vaadin.flow.component.notification.Notification;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ThemeService {
|
||||
public void toggleTheme() {
|
||||
Notification.show("Not implemented");
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package de.grimsi.gameyfin.views;
|
||||
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.component.html.Image;
|
||||
import com.vaadin.flow.component.login.LoginForm;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.BeforeEnterEvent;
|
||||
import com.vaadin.flow.router.BeforeEnterObserver;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||
import de.grimsi.gameyfin.resources.PublicResources;
|
||||
import de.grimsi.gameyfin.setup.SetupService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
|
||||
@Route("login")
|
||||
@PageTitle("Login")
|
||||
@AnonymousAllowed
|
||||
public class LoginView extends VerticalLayout implements BeforeEnterObserver {
|
||||
|
||||
private final LoginForm login = new LoginForm();
|
||||
|
||||
public LoginView(@Autowired SetupService setupService) {
|
||||
if (!setupService.isSetupCompleted()) {
|
||||
UI.getCurrent().navigate(SetupView.class);
|
||||
UI.getCurrent().close();
|
||||
}
|
||||
Image logo = new Image(PublicResources.GAMEYFIN_LOGO.path, "Gameyfin");
|
||||
logo.setHeight("100px");
|
||||
|
||||
login.setAction("login");
|
||||
|
||||
add(logo);
|
||||
add(login);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeEnter(BeforeEnterEvent beforeEnterEvent) {
|
||||
if (beforeEnterEvent.getLocation()
|
||||
.getQueryParameters()
|
||||
.getParameters()
|
||||
.containsKey("error")
|
||||
) {
|
||||
login.setError(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package de.grimsi.gameyfin.views;
|
||||
|
||||
import com.vaadin.flow.component.html.H1;
|
||||
import com.vaadin.flow.component.html.Pre;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import de.grimsi.gameyfin.layouts.MainLayout;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
|
||||
|
||||
@Route(value = "", layout = MainLayout.class)
|
||||
@PageTitle("Gameyfin")
|
||||
@PermitAll
|
||||
public class MainView extends VerticalLayout {
|
||||
|
||||
public MainView() {
|
||||
add(new H1("Gameyfin main page"));
|
||||
add(new Pre("Work in progress"));
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package de.grimsi.gameyfin.views;
|
||||
|
||||
import com.vaadin.flow.component.Text;
|
||||
import com.vaadin.flow.component.UI;
|
||||
import com.vaadin.flow.component.formlayout.FormLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.component.textfield.PasswordField;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
import com.vaadin.flow.router.PageTitle;
|
||||
import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.server.auth.AnonymousAllowed;
|
||||
import de.grimsi.gameyfin.layouts.SetupLayout;
|
||||
import de.grimsi.gameyfin.setup.SetupService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
|
||||
@Route(value = "/setup", layout = SetupLayout.class)
|
||||
@PageTitle("Setup")
|
||||
@AnonymousAllowed
|
||||
public class SetupView extends VerticalLayout {
|
||||
|
||||
public SetupView(@Autowired SetupService setupService) {
|
||||
if (setupService.isSetupCompleted()) {
|
||||
UI.getCurrent().navigate(LoginView.class);
|
||||
UI.getCurrent().close();
|
||||
}
|
||||
|
||||
setWidthFull();
|
||||
setAlignItems(Alignment.CENTER);
|
||||
|
||||
add(new Text("Looks like it's your first time starting Gameyfin. Let's continue setting up your very own instance 🙂"));
|
||||
|
||||
TextField username = new TextField("Username");
|
||||
username.focus();
|
||||
PasswordField passwordField = new PasswordField("Password");
|
||||
PasswordField passwordFieldRepeat = new PasswordField("Password (repeated)");
|
||||
|
||||
FormLayout form = new FormLayout();
|
||||
form.add(new Text("Let's start with creating a super admin account. This account will have full permissions."));
|
||||
form.add(username, passwordField, passwordFieldRepeat);
|
||||
|
||||
add(form);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-8
@@ -1,7 +1,6 @@
|
||||
package de.grimsi.gameyfin.config
|
||||
|
||||
import com.vaadin.flow.spring.security.VaadinWebSecurity
|
||||
import de.grimsi.gameyfin.views.LoginView
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
@@ -15,7 +14,8 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
class SecurityConfiguration : VaadinWebSecurity() {
|
||||
class SecurityConfig : VaadinWebSecurity() {
|
||||
|
||||
@Throws(Exception::class)
|
||||
override fun configure(http: HttpSecurity) {
|
||||
// Configure your static resources with public access before calling super.configure(HttpSecurity) as it adds final anyRequest matcher
|
||||
@@ -23,18 +23,14 @@ class SecurityConfiguration : VaadinWebSecurity() {
|
||||
auth.requestMatchers(AntPathRequestMatcher("/public/**")).permitAll()
|
||||
}
|
||||
|
||||
// Configure your static resources with public access before calling
|
||||
// super.configure(HttpSecurity) as it adds final anyRequest matcher
|
||||
|
||||
super.configure(http)
|
||||
|
||||
// This is important to register your login view to the navigation access control mechanism:
|
||||
setLoginView(http, LoginView::class.java)
|
||||
setLoginView(http, "/login")
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
public override fun configure(web: WebSecurity) {
|
||||
super.configure(web)
|
||||
web.ignoring().requestMatchers(AntPathRequestMatcher("/images/**"))
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -26,17 +26,21 @@ class SetupDataLoader(
|
||||
log.info { "We will now set up some data..." }
|
||||
|
||||
setupRoles()
|
||||
//setupUser()
|
||||
setupUsers()
|
||||
|
||||
log.info { "Setup completed..." }
|
||||
}
|
||||
|
||||
fun setupUser() {
|
||||
fun setupUsers() {
|
||||
val superadmin = User("admin")
|
||||
superadmin.password = "admin"
|
||||
superadmin.roles = listOf(roleRepository.findByRolename(Roles.SUPERADMIN.roleName)!!)
|
||||
|
||||
userService.registerUser(superadmin)
|
||||
|
||||
val user = User("user")
|
||||
user.password = "user"
|
||||
user.roles = listOf(roleRepository.findByRolename(Roles.USER.roleName)!!)
|
||||
userService.registerUser(user)
|
||||
}
|
||||
|
||||
fun setupRoles() {
|
||||
|
||||
@@ -3,12 +3,14 @@ package de.grimsi.gameyfin.setup
|
||||
import jakarta.servlet.*
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.core.annotation.Order
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.stereotype.Component
|
||||
import java.io.IOException
|
||||
|
||||
|
||||
//@Order(1)
|
||||
//@Component
|
||||
@Order(1)
|
||||
@Component
|
||||
class SetupFilter(
|
||||
private val setupService: SetupService
|
||||
) : Filter {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package de.grimsi.gameyfin.users
|
||||
|
||||
import de.grimsi.gameyfin.users.dto.UserInfo
|
||||
import dev.hilla.Endpoint
|
||||
import jakarta.annotation.security.PermitAll
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
import org.springframework.security.core.context.SecurityContextHolder
|
||||
|
||||
@Endpoint
|
||||
class UserEndpoint {
|
||||
|
||||
@PermitAll
|
||||
fun getUserInfo(): UserInfo {
|
||||
val auth: Authentication = SecurityContextHolder.getContext().authentication
|
||||
val authorities: List<String> = auth.authorities.map { g: GrantedAuthority -> g.authority }
|
||||
return UserInfo(auth.name, authorities)
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class UserService(
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
getAuthorities(user.roles)
|
||||
toAuthorities(user.roles)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class UserService(
|
||||
return userRepository.save(user)
|
||||
}
|
||||
|
||||
private fun getAuthorities(roles: Collection<Role>): List<GrantedAuthority> {
|
||||
private fun toAuthorities(roles: Collection<Role>): List<GrantedAuthority> {
|
||||
return roles.map { r -> SimpleGrantedAuthority(r.rolename) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package de.grimsi.gameyfin.users.dto
|
||||
|
||||
data class UserInfo(
|
||||
val name: String,
|
||||
val authorities: List<String>
|
||||
)
|
||||
@@ -6,24 +6,25 @@ import jakarta.validation.constraints.NotNull
|
||||
|
||||
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
class User(
|
||||
@NotNull
|
||||
@field:NotNull
|
||||
var username: String,
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||
var id: Long? = null,
|
||||
|
||||
@NotNull
|
||||
@field:NotNull
|
||||
var password: String? = null,
|
||||
|
||||
@Nullable
|
||||
@field:Nullable
|
||||
var email: String? = null,
|
||||
|
||||
var enabled: Boolean = true,
|
||||
|
||||
@Embedded
|
||||
@Nullable
|
||||
@field:Nullable
|
||||
var avatar: Avatar? = null,
|
||||
|
||||
@ManyToMany(fetch = FetchType.EAGER)
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1,14 +1,28 @@
|
||||
logging.level:
|
||||
org.atmosphere: warn
|
||||
|
||||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
session:
|
||||
tracking-modes: cookie
|
||||
|
||||
spring:
|
||||
# Workaround for https://github.dev/hilla/issues/842
|
||||
devtools.restart.additional-exclude: dev/hilla/openapi.json
|
||||
jpa:
|
||||
defer-datasource-initialization: true
|
||||
mustache:
|
||||
check-template-location: false
|
||||
sql.init.mode: always
|
||||
application:
|
||||
name: Gameyfin
|
||||
|
||||
vaadin:
|
||||
launch-browser: true
|
||||
# To improve the performance during development.
|
||||
# For more information https://vaadin.com/docs/flow/spring/tutorial-spring-configuration.html#special-configuration-parameters
|
||||
whitelisted-packages:
|
||||
- com.vaadin
|
||||
- org.vaadin
|
||||
- dev.hilla
|
||||
- com.flowingcode
|
||||
frontend:
|
||||
hotdeploy: false
|
||||
|
||||
spring:
|
||||
jpa:
|
||||
properties:
|
||||
hibernate:
|
||||
globally_quoted_identifiers: true
|
||||
- dev.hilla
|
||||
@@ -0,0 +1,9 @@
|
||||
${AnsiBackground.DEFAULT}
|
||||
${AnsiColor.BLUE} _____ ${AnsiColor.MAGENTA} ___ _
|
||||
${AnsiColor.BLUE} / ___/ ___ _ __ _ ___ __ __${AnsiColor.MAGENTA} / _/ (_) ___
|
||||
${AnsiColor.BLUE}/ (_ / / _ `/ / ' \/ -_) / // /${AnsiColor.MAGENTA} / _/ / / / _ \
|
||||
${AnsiColor.BLUE}\___/ \_,_/ /_/_/_/\__/ \_, / ${AnsiColor.MAGENTA}/_/ /_/ /_//_/
|
||||
${AnsiColor.BLUE} /___/
|
||||
${AnsiColor.DEFAULT}
|
||||
${spring.application.name} ${application.version}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package de.grimsi.gameyfin
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
|
||||
@SpringBootTest
|
||||
class GameyfinApplicationTests {
|
||||
|
||||
@Test
|
||||
fun contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user