diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 0000000..d36e593
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mvnw b/mvnw
old mode 100755
new mode 100644
diff --git a/pom.xml b/pom.xml
index fbe03aa..bcd7a9e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,11 +72,24 @@
spring-boot-starter-test
test
+
+ org.springframework.security
+ spring-security-ldap
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
com.vaadin
vaadin-testbench-junit5
test
+
+ org.slf4j
+ slf4j-api
+ 2.0.13
+
@@ -166,7 +179,7 @@
org.apache.maven.plugins
- maven-failsafe-plugin
+
diff --git a/src/main/java/com/example/application/api/ApiRedmine.java b/src/main/java/com/example/application/api/ApiRedmine.java
new file mode 100644
index 0000000..bcfd9ad
--- /dev/null
+++ b/src/main/java/com/example/application/api/ApiRedmine.java
@@ -0,0 +1,75 @@
+package com.example.application.api;
+
+import com.nimbusds.jose.shaded.gson.*;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.*;
+
+public class ApiRedmine {
+ private static final String REDMINE_URL = "http://localhost:3000";
+ private static final String API_KEY = "cf3be6168e66c99892c6212ea0bc64e8ab1c6848";
+ public static final Gson GSON = new Gson();
+
+ public static String createIssue(Map issueDetails) {
+ Map payload = new HashMap<>();
+ payload.put("issue", issueDetails);
+ String jsonPayload = GSON.toJson(payload);
+
+ try {
+ //Crear la solicitud HTTP POST
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(REDMINE_URL + "/issues.json"))
+ .header("Content-Type", "application/json")
+ .header("X-Redmine-API-Key", API_KEY)
+ .POST(HttpRequest.BodyPublishers.ofString(jsonPayload))
+ .build();
+
+ //Enviar la solicitud
+ HttpClient client = HttpClient.newHttpClient();
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+
+ return response.body();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return "Error: "+e.getMessage();
+ }
+ }
+
+ public List getTicketTypes() {
+ List ticketTypes = new ArrayList<>();
+ HttpClient client = HttpClient.newHttpClient();
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(REDMINE_URL + "/trackers.json"))
+ .header("Content-Type", "application/json")
+ .header("X-Redmine-API-Key", API_KEY)
+ .build();
+
+ try {
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ if (response.statusCode() == 200) {
+ String responseBody = response.body();
+ ticketTypes = parseTicketTypes(responseBody);
+ } else {
+ System.err.println("Error en la respuesta: " + response.statusCode());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return ticketTypes;
+ }
+
+ private List parseTicketTypes(String json) {
+ List names = new ArrayList<>();
+ JsonObject jsonObject = JsonParser.parseString(json).getAsJsonObject();
+ JsonArray trackers = jsonObject.getAsJsonArray("trackers");
+ for (JsonElement trackerElement : trackers) {
+ JsonObject tracker = trackerElement.getAsJsonObject();
+ String name = tracker.get("name").getAsString();
+ names.add(name);
+ }
+ return names;
+ }
+}
diff --git a/src/main/java/com/example/application/api/SecurityConfig.java b/src/main/java/com/example/application/api/SecurityConfig.java
new file mode 100644
index 0000000..dfd6d6c
--- /dev/null
+++ b/src/main/java/com/example/application/api/SecurityConfig.java
@@ -0,0 +1,56 @@
+package com.example.application.api;
+
+import com.example.application.views.login.LoginView;
+import com.vaadin.flow.component.UI;
+import com.vaadin.flow.server.VaadinServletRequest;
+import com.vaadin.flow.spring.security.VaadinWebSecurity;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig extends VaadinWebSecurity {
+
+
+ private static final String LOGOUT_SUCCESS_URL = "/";
+
+ @Override
+ protected void configure(HttpSecurity http) throws Exception {
+ super.configure(http);
+ setLoginView(http, LoginView.class);
+ }
+
+
+ @Autowired
+ public void configure(AuthenticationManagerBuilder auth) throws Exception {
+ ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider("JUMAPACELAYA.GOB.MX", "ldap://172.16.0.1");
+ provider.setConvertSubErrorCodesToExceptions(true);
+ provider.setUseAuthenticationRequestCredentials(true);
+ auth.authenticationProvider(provider);
+
+ }
+
+ public void logout() {
+ UI.getCurrent().getPage().setLocation(LOGOUT_SUCCESS_URL);
+ SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
+ logoutHandler.logout(
+ VaadinServletRequest.getCurrent().getHttpServletRequest(), null, null
+ );
+ }
+}
+
+/*
+ @Bean
+ AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
+ LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(contextSource, new BCryptPasswordEncoder());
+ factory.setUserDnPatterns("DC=JUMAPACELAYA,DC=GOB,DC=MX");
+ factory.setPasswordAttribute("pwd"); //pwd
+ return factory.createAuthenticationManager();
+ }
+*/
+
diff --git a/src/main/java/com/example/application/views/MainLayout.java b/src/main/java/com/example/application/views/MainLayout.java
index 3157304..71fdd94 100644
--- a/src/main/java/com/example/application/views/MainLayout.java
+++ b/src/main/java/com/example/application/views/MainLayout.java
@@ -1,13 +1,18 @@
package com.example.application.views;
-import com.example.application.views.about.AboutView;
-import com.example.application.views.helloworld.HelloWorldView;
+import com.example.application.api.SecurityConfig;
+import com.example.application.views.crearnuevoticket.CrearnuevoTicketView;
+import com.example.application.views.tickets.MisTicketsView;
import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle;
+import com.vaadin.flow.component.button.Button;
+import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.Footer;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Header;
+import com.vaadin.flow.component.orderedlayout.FlexComponent;
+import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.Scroller;
import com.vaadin.flow.component.sidenav.SideNav;
import com.vaadin.flow.component.sidenav.SideNavItem;
@@ -21,8 +26,10 @@ import org.vaadin.lineawesome.LineAwesomeIcon;
public class MainLayout extends AppLayout {
private H2 viewTitle;
+ private final SecurityConfig securityConfig;
- public MainLayout() {
+ public MainLayout(SecurityConfig securityConfig) {
+ this.securityConfig = securityConfig;
setPrimarySection(Section.DRAWER);
addDrawerContent();
addHeaderContent();
@@ -35,7 +42,21 @@ public class MainLayout extends AppLayout {
viewTitle = new H2();
viewTitle.addClassNames(LumoUtility.FontSize.LARGE, LumoUtility.Margin.NONE);
- addToNavbar(true, toggle, viewTitle);
+ Button logoutButton = new Button("Cerrar sesion", event -> {
+ securityConfig.logout();
+ });
+ logoutButton.getStyle().set("margin-right", "50px");
+ logoutButton.addThemeVariants(ButtonVariant.LUMO_ERROR);
+
+ HorizontalLayout headerContent = new HorizontalLayout();
+ headerContent.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.CENTER);
+ headerContent.setWidthFull();
+ headerContent.add(viewTitle);
+ headerContent.setFlexGrow(1, viewTitle);
+ headerContent.add(logoutButton);
+
+
+ addToNavbar(true, toggle, headerContent);
}
private void addDrawerContent() {
@@ -51,8 +72,9 @@ public class MainLayout extends AppLayout {
private SideNav createNavigation() {
SideNav nav = new SideNav();
- nav.addItem(new SideNavItem("Hello World", HelloWorldView.class, LineAwesomeIcon.GLOBE_SOLID.create()));
- nav.addItem(new SideNavItem("About", AboutView.class, LineAwesomeIcon.FILE.create()));
+ nav.addItem(new SideNavItem("Crear nuevo ticket", CrearnuevoTicketView.class, LineAwesomeIcon.EDIT.create()));
+ nav.addItem(new SideNavItem("Mis tickets", MisTicketsView.class, LineAwesomeIcon.TICKET_ALT_SOLID.create()));
+
return nav;
}
diff --git a/src/main/java/com/example/application/views/about/AboutView.java b/src/main/java/com/example/application/views/about/AboutView.java
deleted file mode 100644
index 71c869e..0000000
--- a/src/main/java/com/example/application/views/about/AboutView.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.example.application.views.about;
-
-import com.example.application.views.MainLayout;
-import com.vaadin.flow.component.html.H2;
-import com.vaadin.flow.component.html.Image;
-import com.vaadin.flow.component.html.Paragraph;
-import com.vaadin.flow.component.orderedlayout.VerticalLayout;
-import com.vaadin.flow.router.PageTitle;
-import com.vaadin.flow.router.Route;
-import com.vaadin.flow.theme.lumo.LumoUtility.Margin;
-
-@PageTitle("About")
-@Route(value = "about", layout = MainLayout.class)
-public class AboutView extends VerticalLayout {
-
- public AboutView() {
- setSpacing(false);
-
- Image img = new Image("images/empty-plant.png", "placeholder plant");
- img.setWidth("200px");
- add(img);
-
- H2 header = new H2("This place intentionally left empty");
- header.addClassNames(Margin.Top.XLARGE, Margin.Bottom.MEDIUM);
- add(header);
- add(new Paragraph("It’s a place where you can grow your own UI 🤗"));
-
- setSizeFull();
- setJustifyContentMode(JustifyContentMode.CENTER);
- setDefaultHorizontalComponentAlignment(Alignment.CENTER);
- getStyle().set("text-align", "center");
- }
-
-}
diff --git a/src/main/java/com/example/application/views/crearnuevoticket/CrearnuevoTicketView.java b/src/main/java/com/example/application/views/crearnuevoticket/CrearnuevoTicketView.java
new file mode 100644
index 0000000..f826c1e
--- /dev/null
+++ b/src/main/java/com/example/application/views/crearnuevoticket/CrearnuevoTicketView.java
@@ -0,0 +1,103 @@
+package com.example.application.views.crearnuevoticket;
+
+import com.example.application.api.ApiRedmine;
+import com.example.application.views.MainLayout;
+import com.nimbusds.jose.shaded.gson.Gson;
+import com.nimbusds.jose.shaded.gson.JsonObject;
+import com.nimbusds.jose.shaded.gson.JsonParser;
+import com.vaadin.flow.component.button.Button;
+import com.vaadin.flow.component.button.ButtonVariant;
+import com.vaadin.flow.component.combobox.ComboBox;
+import com.vaadin.flow.component.html.H2;
+import com.vaadin.flow.component.notification.Notification;
+import com.vaadin.flow.component.notification.NotificationVariant;
+import com.vaadin.flow.component.orderedlayout.VerticalLayout;
+import com.vaadin.flow.component.textfield.TextArea;
+import com.vaadin.flow.component.textfield.TextField;
+import com.vaadin.flow.router.Route;
+import jakarta.annotation.security.PermitAll;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Route(value="home", layout = MainLayout.class)
+@PermitAll
+public class CrearnuevoTicketView extends VerticalLayout {
+
+ public CrearnuevoTicketView() {
+ TextField projectId = new TextField("Project ID");
+ projectId.setReadOnly(true);
+
+
+ //Combo de los tipos de tickets
+ ComboBox tipoTickets = new ComboBox<>("Tipo de ticket");
+ ApiRedmine api = new ApiRedmine();
+ List types = api.getTicketTypes();
+ tipoTickets.setItems(types);
+
+
+ //Campo de texto para el asunto
+ TextField asunto = new TextField("Asunto");
+ asunto.setWidth("700px");
+
+
+ //Campo de texto para la descripcion
+ TextArea descripcion = new TextArea("Descripcion");
+ descripcion.setWidth("1000px");
+ descripcion.setHeight("300px");
+
+
+ //Respuestas Json para verificar posibles errores al enviar los nuevos tickets no visibles en la interfaz
+ TextArea jsonOutput = new TextArea("JSON Output");
+ jsonOutput.setReadOnly(true);
+ TextArea responseField = new TextArea("Response");
+ responseField.setReadOnly(true);
+
+
+
+ Button createButton = new Button("Enviar ticket", event -> {
+ Map issueDetails = new HashMap<>();
+ issueDetails.put("project_id", "proyecto-de-prueba");
+ issueDetails.put("subject", asunto.getValue());
+ issueDetails.put("description", descripcion.getValue());
+
+ String response = ApiRedmine.createIssue(issueDetails);
+ if (response.startsWith("{\"issue\":")) {
+ //Aqui extraigo el numero del ticket de la respuesta json
+ JsonObject jsonResponse = JsonParser.parseString(response).getAsJsonObject();
+ int issueNumber = jsonResponse.getAsJsonObject("issue").get("id").getAsInt();
+
+ String notificationMessage = "¡Su ticket se ha enviado corrrectamente! Numero de ticket: #" + issueNumber;
+ Notification issueNtf = new Notification(notificationMessage, 5000, Notification.Position.MIDDLE);
+ issueNtf.addThemeVariants(NotificationVariant.LUMO_SUCCESS);
+ issueNtf.open();
+
+ //Limpiando los campos
+ asunto.clear();
+ descripcion.clear();
+ tipoTickets.clear();
+ } else {
+ Notification errNtf = Notification.show("Ha ocurrido un error al enviar el ticket: "+response, 5000, Notification.Position.MIDDLE);
+ errNtf.addThemeVariants(NotificationVariant.LUMO_ERROR);
+ }
+ });
+ createButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
+
+ VerticalLayout fieldsLayout = new VerticalLayout(asunto, descripcion);
+ fieldsLayout.setAlignItems(Alignment.CENTER);
+
+ VerticalLayout buttonLayout = new VerticalLayout(createButton);
+ buttonLayout.setAlignItems(Alignment.END);
+ buttonLayout.setMargin(true);
+
+ add(new H2("Crear nuevo ticket"),projectId, tipoTickets, fieldsLayout,buttonLayout/*,jsonOutput,responseField*/);
+ }
+
+
+ //Metodo para convertir un Map a json usando la libreria GSON
+ private static String mapToJson(Map map) {
+ Gson gson = new Gson();
+ return gson.toJson(map);
+ }
+}
diff --git a/src/main/java/com/example/application/views/helloworld/HelloWorldView.java b/src/main/java/com/example/application/views/helloworld/HelloWorldView.java
deleted file mode 100644
index dd80e91..0000000
--- a/src/main/java/com/example/application/views/helloworld/HelloWorldView.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.example.application.views.helloworld;
-
-import com.example.application.views.MainLayout;
-import com.vaadin.flow.component.Key;
-import com.vaadin.flow.component.button.Button;
-import com.vaadin.flow.component.notification.Notification;
-import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
-import com.vaadin.flow.component.textfield.TextField;
-import com.vaadin.flow.router.PageTitle;
-import com.vaadin.flow.router.Route;
-import com.vaadin.flow.router.RouteAlias;
-
-@PageTitle("Hello World")
-@Route(value = "hello", layout = MainLayout.class)
-@RouteAlias(value = "", layout = MainLayout.class)
-public class HelloWorldView extends HorizontalLayout {
-
- private TextField name;
- private Button sayHello;
-
- public HelloWorldView() {
- name = new TextField("Your name");
- sayHello = new Button("Say hello");
- sayHello.addClickListener(e -> {
- Notification.show("Hello " + name.getValue());
- });
- sayHello.addClickShortcut(Key.ENTER);
-
- setMargin(true);
- setVerticalComponentAlignment(Alignment.END, name, sayHello);
-
- add(name, sayHello);
- }
-
-}
diff --git a/src/main/java/com/example/application/views/login/LoginView.java b/src/main/java/com/example/application/views/login/LoginView.java
new file mode 100644
index 0000000..4a9394b
--- /dev/null
+++ b/src/main/java/com/example/application/views/login/LoginView.java
@@ -0,0 +1,29 @@
+package com.example.application.views.login;
+
+import com.vaadin.flow.component.html.H1;
+import com.vaadin.flow.component.login.LoginForm;
+import com.vaadin.flow.component.orderedlayout.VerticalLayout;
+import com.vaadin.flow.router.Route;
+import com.vaadin.flow.server.auth.AnonymousAllowed;
+
+@Route("login")
+@AnonymousAllowed
+public class LoginView extends VerticalLayout {
+
+
+ public LoginView() {
+ setSizeFull();
+ setAlignItems(Alignment.CENTER);
+ setJustifyContentMode(JustifyContentMode.CENTER);
+
+ //Formulario de login
+ var login = new LoginForm();
+ login.setAction("login");
+
+ add(
+ new H1("Soporte tecnico T.I"),
+ login
+ );
+ }
+}
+
diff --git a/src/main/java/com/example/application/views/tickets/MisTicketsView.java b/src/main/java/com/example/application/views/tickets/MisTicketsView.java
new file mode 100644
index 0000000..aed1817
--- /dev/null
+++ b/src/main/java/com/example/application/views/tickets/MisTicketsView.java
@@ -0,0 +1,26 @@
+package com.example.application.views.tickets;
+
+import com.example.application.views.MainLayout;
+import com.vaadin.flow.component.html.Div;
+import com.vaadin.flow.component.html.H2;
+import com.vaadin.flow.component.orderedlayout.VerticalLayout;
+import com.vaadin.flow.router.Route;
+import com.vaadin.flow.server.auth.AnonymousAllowed;
+
+@Route(value="mytickets", layout = MainLayout.class)
+@AnonymousAllowed
+public class MisTicketsView extends VerticalLayout {
+
+ public MisTicketsView() {
+ setSizeFull();
+ Div banner = new Div();
+ banner.setText("Aqui es donde el usuario vera sus tickets creados");
+ banner.getStyle()
+ .set("display", "flex")
+ .set("justify-content", "center")
+ .set("align-items", "center")
+ .set("height", "100%");
+
+ add(new H2("Mis tickets"), banner);
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 8509a29..7a68262 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -8,3 +8,11 @@ vaadin.launch-browser=true
# For more information https://vaadin.com/docs/latest/integrations/spring/configuration#special-configuration-parameters
vaadin.allowed-packages = com.vaadin,org.vaadin,dev.hilla,com.example.application
spring.jpa.defer-datasource-initialization = true
+
+# Configuracion de LDAP
+spring:
+ldap:
+urls: ldap://172.16.0.1:389
+base: DC=JUMAPACELAYA, DC=GOB, DC=MX
+username: administrator
+password: Dr3na%134$4guA