Browse Source

Sistema de captura de tickets para soporte primera version

pull/1/head
parent
commit
ea8699eef7
12 changed files with 362 additions and 76 deletions
  1. +23
    -0
      frontend/index.html
  2. +0
    -0
      mvnw
  3. +14
    -1
      pom.xml
  4. +75
    -0
      src/main/java/com/example/application/api/ApiRedmine.java
  5. +56
    -0
      src/main/java/com/example/application/api/SecurityConfig.java
  6. +28
    -6
      src/main/java/com/example/application/views/MainLayout.java
  7. +0
    -34
      src/main/java/com/example/application/views/about/AboutView.java
  8. +103
    -0
      src/main/java/com/example/application/views/crearnuevoticket/CrearnuevoTicketView.java
  9. +0
    -35
      src/main/java/com/example/application/views/helloworld/HelloWorldView.java
  10. +29
    -0
      src/main/java/com/example/application/views/login/LoginView.java
  11. +26
    -0
      src/main/java/com/example/application/views/tickets/MisTicketsView.java
  12. +8
    -0
      src/main/resources/application.properties

+ 23
- 0
frontend/index.html View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<!--
This file is auto-generated by Vaadin.
-->
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body, #outlet {
height: 100vh;
width: 100%;
margin: 0;
}
</style>
<!-- index.ts is included here automatically (either by the dev server or during the build) -->
</head>
<body>
<!-- This outlet div is where the views are rendered -->
<div id="outlet"></div>
</body>
</html>

+ 0
- 0
mvnw View File


+ 14
- 1
pom.xml View File

@ -72,11 +72,24 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-testbench-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>
</dependencies>
<build>
@ -166,7 +179,7 @@
<!-- Runs the integration tests (*IT) after the server is started -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<!--<artifactId>maven-failsafe-plugin</artifactId>-->
<executions>
<execution>
<goals>


+ 75
- 0
src/main/java/com/example/application/api/ApiRedmine.java View File

@ -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<String, String> issueDetails) {
Map<String, Object> 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<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
} catch (Exception e) {
e.printStackTrace();
return "Error: "+e.getMessage();
}
}
public List<String> getTicketTypes() {
List<String> 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<String> 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<String> parseTicketTypes(String json) {
List<String> 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;
}
}

+ 56
- 0
src/main/java/com/example/application/api/SecurityConfig.java View File

@ -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();
}
*/

+ 28
- 6
src/main/java/com/example/application/views/MainLayout.java View File

@ -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;
}


+ 0
- 34
src/main/java/com/example/application/views/about/AboutView.java View File

@ -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");
}
}

+ 103
- 0
src/main/java/com/example/application/views/crearnuevoticket/CrearnuevoTicketView.java View File

@ -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<String> tipoTickets = new ComboBox<>("Tipo de ticket");
ApiRedmine api = new ApiRedmine();
List<String> 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<String, String> 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<String, String> map) {
Gson gson = new Gson();
return gson.toJson(map);
}
}

+ 0
- 35
src/main/java/com/example/application/views/helloworld/HelloWorldView.java View File

@ -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);
}
}

+ 29
- 0
src/main/java/com/example/application/views/login/LoginView.java View File

@ -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
);
}
}

+ 26
- 0
src/main/java/com/example/application/views/tickets/MisTicketsView.java View File

@ -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);
}
}

+ 8
- 0
src/main/resources/application.properties View File

@ -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

Loading…
Cancel
Save