mirror of
https://github.com/tiennm99/qr-attendance-demo.git
synced 2026-10-11 03:13:47 +00:00
(Add) complete prototype
This commit is contained in:
1 parent
24ff5f8ebd
commit
df014229c8
36 files changed
+775
-16930
No files matched your search
@@ -1 +0,0 @@
|
||||
gradlew text eol=lf
|
||||
@@ -1,10 +1,7 @@
|
||||
name: Build and Release Vaadin Project
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
build-and-release:
|
||||
@@ -29,6 +26,7 @@ jobs:
|
||||
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: build/libs/*
|
||||
env:
|
||||
|
||||
+1
-21
@@ -1,25 +1,5 @@
|
||||
/target/
|
||||
.idea/
|
||||
.settings
|
||||
.project
|
||||
.classpath
|
||||
submissions.xlsx
|
||||
|
||||
*.iml
|
||||
.DS_Store
|
||||
|
||||
# The following files are generated/updated by vaadin-maven-plugin
|
||||
node_modules/
|
||||
src/main/frontend/generated/
|
||||
pnpmfile.js
|
||||
vite.generated.ts
|
||||
|
||||
# Browser drivers for local integration tests
|
||||
drivers/
|
||||
# Error screenshots generated by TestBench for failed integration tests
|
||||
error-screenshots/
|
||||
webpack.generated.js
|
||||
|
||||
src/main/dev-bundle
|
||||
|
||||
|
||||
HELP.md
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
# java-qr-attendance
|
||||
|
||||
Just a QR code attendance demo app for my sister's representation, but written in java
|
||||
|
||||
## How to run
|
||||
|
||||
1. Download java 21 from [here](https://www.oracle.com/java/technologies/downloads/#jdk21-windows)
|
||||
2. Download the .jar file from the releases page
|
||||
3. Run the .jar file with `java -jar <filename>.jar`
|
||||
|
||||
## Note
|
||||
|
||||
**These code for educational purposes only. It is not secure and not optimized for production use.**
|
||||
|
||||
_Good luck, sis!_
|
||||
+10
-24
@@ -1,15 +1,12 @@
|
||||
plugins {
|
||||
java
|
||||
idea
|
||||
`maven-publish`
|
||||
id("com.google.osdetector") version "1.7.3"
|
||||
id("io.spring.dependency-management") version "1.1.6"
|
||||
id("org.springframework.boot") version "3.3.2"
|
||||
id("com.vaadin") version "24.4.8"
|
||||
}
|
||||
|
||||
val ngrokVersion = "1.1.0"
|
||||
val vaadinVersion by extra("24.4.8")
|
||||
val springCloudVersion by extra("2023.0.3")
|
||||
|
||||
group = "io.github.tiennm99"
|
||||
@@ -39,7 +36,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation("io.projectreactor:reactor-test")
|
||||
annotationProcessor("org.projectlombok:lombok")
|
||||
compileOnly("org.projectlombok:lombok")
|
||||
|
||||
@@ -47,9 +43,10 @@ dependencies {
|
||||
implementation("com.google.zxing:javase:3.5.3")
|
||||
implementation("com.ngrok:ngrok-java:${ngrokVersion}")
|
||||
implementation("com.ngrok:ngrok-java-native:${ngrokVersion}:${osdetector.classifier}")
|
||||
implementation("com.vaadin:vaadin-spring-boot-starter")
|
||||
implementation("org.apache.poi:poi-ooxml:5.3.0")
|
||||
implementation("org.springframework.boot:spring-boot-starter")
|
||||
implementation("org.springframework.boot:spring-boot-starter-thymeleaf")
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
@@ -57,7 +54,6 @@ dependencies {
|
||||
|
||||
dependencyManagement {
|
||||
imports {
|
||||
mavenBom("com.vaadin:vaadin-bom:$vaadinVersion")
|
||||
mavenBom("org.springframework.cloud:spring-cloud-dependencies:$springCloudVersion")
|
||||
}
|
||||
}
|
||||
@@ -66,25 +62,15 @@ tasks.withType<Test> {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/${System.getenv("GITHUB_REPOSITORY")}")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
publications {
|
||||
create<MavenPublication>("gpr") {
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.withType<JavaCompile> {
|
||||
sourceCompatibility = JavaVersion.VERSION_21.toString()
|
||||
targetCompatibility = JavaVersion.VERSION_21.toString()
|
||||
}
|
||||
|
||||
tasks.jar {
|
||||
manifest {
|
||||
attributes["Main-Class"] = "io.github.tiennm99.qrattendance.QrAttendanceApplication"
|
||||
}
|
||||
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
Generated
-16654
File diff suppressed because it is too large.
Load diff
-105
@@ -1,105 +0,0 @@
|
||||
{
|
||||
"name": "no-name",
|
||||
"license": "UNLICENSED",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@vaadin/bundles": "24.4.5",
|
||||
"@vaadin/common-frontend": "0.0.19",
|
||||
"@vaadin/polymer-legacy-adapter": "24.4.5",
|
||||
"@vaadin/react-components": "24.4.5",
|
||||
"@vaadin/router": "1.7.5",
|
||||
"@vaadin/vaadin-development-mode-detector": "2.0.7",
|
||||
"@vaadin/vaadin-lumo-styles": "24.4.5",
|
||||
"@vaadin/vaadin-material-styles": "24.4.5",
|
||||
"@vaadin/vaadin-themable-mixin": "24.4.5",
|
||||
"@vaadin/vaadin-usage-statistics": "2.1.2",
|
||||
"construct-style-sheets-polyfill": "3.1.0",
|
||||
"date-fns": "2.29.3",
|
||||
"lit": "3.1.4",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-router-dom": "6.23.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-react": "7.24.7",
|
||||
"@rollup/plugin-replace": "5.0.7",
|
||||
"@rollup/pluginutils": "5.1.0",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"@vitejs/plugin-react": "4.3.1",
|
||||
"async": "3.2.5",
|
||||
"glob": "10.4.1",
|
||||
"rollup-plugin-brotli": "3.1.0",
|
||||
"rollup-plugin-visualizer": "5.12.0",
|
||||
"strip-css-comments": "5.0.0",
|
||||
"transform-ast": "2.4.4",
|
||||
"typescript": "5.4.5",
|
||||
"vite": "5.3.3",
|
||||
"vite-plugin-checker": "0.6.4",
|
||||
"workbox-build": "7.1.1",
|
||||
"workbox-core": "7.1.0",
|
||||
"workbox-precaching": "7.1.0"
|
||||
},
|
||||
"vaadin": {
|
||||
"dependencies": {
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@vaadin/bundles": "24.4.5",
|
||||
"@vaadin/common-frontend": "0.0.19",
|
||||
"@vaadin/polymer-legacy-adapter": "24.4.5",
|
||||
"@vaadin/react-components": "24.4.5",
|
||||
"@vaadin/router": "1.7.5",
|
||||
"@vaadin/vaadin-development-mode-detector": "2.0.7",
|
||||
"@vaadin/vaadin-lumo-styles": "24.4.5",
|
||||
"@vaadin/vaadin-material-styles": "24.4.5",
|
||||
"@vaadin/vaadin-themable-mixin": "24.4.5",
|
||||
"@vaadin/vaadin-usage-statistics": "2.1.2",
|
||||
"construct-style-sheets-polyfill": "3.1.0",
|
||||
"date-fns": "2.29.3",
|
||||
"lit": "3.1.4",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"react-router-dom": "6.23.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-react": "7.24.7",
|
||||
"@rollup/plugin-replace": "5.0.7",
|
||||
"@rollup/pluginutils": "5.1.0",
|
||||
"@types/react": "18.3.3",
|
||||
"@types/react-dom": "18.3.0",
|
||||
"@vitejs/plugin-react": "4.3.1",
|
||||
"async": "3.2.5",
|
||||
"glob": "10.4.1",
|
||||
"rollup-plugin-brotli": "3.1.0",
|
||||
"rollup-plugin-visualizer": "5.12.0",
|
||||
"strip-css-comments": "5.0.0",
|
||||
"transform-ast": "2.4.4",
|
||||
"typescript": "5.4.5",
|
||||
"vite": "5.3.3",
|
||||
"vite-plugin-checker": "0.6.4",
|
||||
"workbox-build": "7.1.1",
|
||||
"workbox-core": "7.1.0",
|
||||
"workbox-precaching": "7.1.0"
|
||||
},
|
||||
"hash": "2a4c77083ab51f28e5e318219da09835683db988de08b49ff99c8f3532628271"
|
||||
},
|
||||
"overrides": {
|
||||
"@vaadin/bundles": "$@vaadin/bundles",
|
||||
"@vaadin/polymer-legacy-adapter": "$@vaadin/polymer-legacy-adapter",
|
||||
"@vaadin/vaadin-development-mode-detector": "$@vaadin/vaadin-development-mode-detector",
|
||||
"@vaadin/router": "$@vaadin/router",
|
||||
"@vaadin/vaadin-usage-statistics": "$@vaadin/vaadin-usage-statistics",
|
||||
"@vaadin/react-components": "$@vaadin/react-components",
|
||||
"@vaadin/common-frontend": "$@vaadin/common-frontend",
|
||||
"react-dom": "$react-dom",
|
||||
"construct-style-sheets-polyfill": "$construct-style-sheets-polyfill",
|
||||
"react-router-dom": "$react-router-dom",
|
||||
"lit": "$lit",
|
||||
"@polymer/polymer": "$@polymer/polymer",
|
||||
"react": "$react",
|
||||
"date-fns": "$date-fns",
|
||||
"@vaadin/vaadin-themable-mixin": "$@vaadin/vaadin-themable-mixin",
|
||||
"@vaadin/vaadin-lumo-styles": "$@vaadin/vaadin-lumo-styles",
|
||||
"@vaadin/vaadin-material-styles": "$@vaadin/vaadin-material-styles"
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<!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>
|
||||
@@ -1,14 +1,12 @@
|
||||
package io.github.tiennm99.qrattendance;
|
||||
|
||||
import com.vaadin.flow.spring.annotation.EnableVaadin;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import com.vaadin.flow.theme.Theme;
|
||||
import com.vaadin.flow.component.page.AppShellConfigurator;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableVaadin
|
||||
public class QRAttendanceApplication implements AppShellConfigurator {
|
||||
@Slf4j
|
||||
public class QRAttendanceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(QRAttendanceApplication.class, args);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.github.tiennm99.qrattendance.config;
|
||||
package io.github.tiennm99.qrattendance.configuration;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.github.tiennm99.qrattendance.configuration;
|
||||
|
||||
import io.github.tiennm99.qrattendance.ngrok.NgrokInterceptor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
private final NgrokInterceptor ngrokInterceptor;
|
||||
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry
|
||||
.addInterceptor(ngrokInterceptor)
|
||||
.addPathPatterns(
|
||||
"/",
|
||||
"/ask-server-location",
|
||||
"/set-server-location",
|
||||
"/manage",
|
||||
"/submissions",
|
||||
"/download-submissions");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.github.tiennm99.qrattendance.controller.student;
|
||||
|
||||
import io.github.tiennm99.qrattendance.data.ApplicationData;
|
||||
import io.github.tiennm99.qrattendance.util.DistanceUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
public class GateController {
|
||||
|
||||
private final ApplicationData data;
|
||||
|
||||
@GetMapping("/gate")
|
||||
public String gate() {
|
||||
return "gate";
|
||||
}
|
||||
|
||||
@PostMapping("/check-location")
|
||||
@ResponseBody
|
||||
public String checkLocation(@RequestParam double latitude, @RequestParam double longitude) {
|
||||
double serverLatitude = data.getServerLocation().getLeft();
|
||||
double serverLongitude = data.getServerLocation().getRight();
|
||||
double distance =
|
||||
DistanceUtil.calculateDistance(latitude, longitude, serverLatitude, serverLongitude);
|
||||
|
||||
if (distance > 1.0) {
|
||||
return "rejected";
|
||||
} else {
|
||||
return "accepted";
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package io.github.tiennm99.qrattendance.controller.student;
|
||||
|
||||
import io.github.tiennm99.qrattendance.service.SubmissionService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
public class SubmitController {
|
||||
|
||||
private final SubmissionService submissionService;
|
||||
|
||||
@PostMapping("/submit")
|
||||
@ResponseBody
|
||||
public String submit(
|
||||
@RequestParam String studentId,
|
||||
@RequestParam double latitude,
|
||||
@RequestParam double longitude,
|
||||
@RequestHeader("X-Forwarded-For") String ip,
|
||||
HttpServletRequest request) {
|
||||
String userAgent = request.getHeader("User-Agent");
|
||||
submissionService.addSubmission(studentId, latitude, longitude, ip, userAgent);
|
||||
return "Submission received";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.github.tiennm99.qrattendance.controller.student;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
public class ViewController {
|
||||
|
||||
@GetMapping("/view")
|
||||
public String view() {
|
||||
return "view";
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package io.github.tiennm99.qrattendance.controller.teacher;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
public class AccessDeniedController {
|
||||
|
||||
@GetMapping("/access-denied")
|
||||
public String accessDenied() {
|
||||
return "access-denied";
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package io.github.tiennm99.qrattendance.controller.teacher;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
public class AskServerLocationController {
|
||||
@GetMapping("/ask-server-location")
|
||||
public String askServerLocation() {
|
||||
return "ask-server-location";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package io.github.tiennm99.qrattendance.controller.teacher;
|
||||
|
||||
import io.github.tiennm99.qrattendance.data.ApplicationData;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
public class HomeController {
|
||||
|
||||
private final ApplicationData data;
|
||||
|
||||
@GetMapping("/")
|
||||
public String home() {
|
||||
if (data.getServerLocation() == null) {
|
||||
return "redirect:/ask-server-location";
|
||||
} else {
|
||||
return "redirect:/manage";
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package io.github.tiennm99.qrattendance.controller.teacher;
|
||||
|
||||
import io.github.tiennm99.qrattendance.data.ApplicationData;
|
||||
import io.github.tiennm99.qrattendance.data.Submission;
|
||||
import io.github.tiennm99.qrattendance.service.QRCodeService;
|
||||
import io.github.tiennm99.qrattendance.service.SubmissionService;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
public class ManageController {
|
||||
private final ApplicationData data;
|
||||
private final QRCodeService qrCodeService;
|
||||
private final SubmissionService submissionService;
|
||||
|
||||
@GetMapping("/manage")
|
||||
public String manage(Model model) {
|
||||
var qrCode = qrCodeService.generateQRCode(data.getNgrokUrl() + "/gate");
|
||||
var submissions = data.getSubmissions();
|
||||
model.addAttribute("qrCode", qrCode);
|
||||
model.addAttribute("submissions", submissions);
|
||||
return "manage";
|
||||
}
|
||||
|
||||
@GetMapping("/submissions")
|
||||
@ResponseBody
|
||||
public List<Submission> getSubmissions() {
|
||||
return data.getSubmissions();
|
||||
}
|
||||
|
||||
@PostMapping("/export-submissions")
|
||||
@ResponseBody
|
||||
public String exportSubmissions() {
|
||||
submissionService.exportSubmissions();
|
||||
return "Submissions exported";
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package io.github.tiennm99.qrattendance.controller.teacher;
|
||||
|
||||
import io.github.tiennm99.qrattendance.data.ApplicationData;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
@Controller
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
@Slf4j
|
||||
public class SetServerLocationController {
|
||||
|
||||
private final ApplicationData data;
|
||||
|
||||
@PostMapping("/set-server-location")
|
||||
public String confirmLocation(@RequestParam("lat") double lat, @RequestParam("lon") double lon) {
|
||||
log.info("Location confirmed: {}, {}", lat, lon);
|
||||
data.setServerLocation(new ImmutablePair<>(lat, lon));
|
||||
return "redirect:/manage";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.github.tiennm99.qrattendance.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.annotation.ApplicationScope;
|
||||
|
||||
@ApplicationScope
|
||||
@Component
|
||||
@Data
|
||||
public class ApplicationData {
|
||||
String ngrokUrl;
|
||||
Pair<Double, Double> serverLocation;
|
||||
List<Submission> submissions = new ArrayList<>();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package io.github.tiennm99.qrattendance.data;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public final class Submission {
|
||||
private final String studentId;
|
||||
private final double latitude;
|
||||
private final double longitude;
|
||||
private final String ip;
|
||||
private final String userAgent;
|
||||
private final String duplicatedWith;
|
||||
private final boolean tooFarFromServer;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.github.tiennm99.qrattendance.ngrok;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
public class NgrokInterceptor implements HandlerInterceptor {
|
||||
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
|
||||
throws Exception {
|
||||
var requestUrl = request.getRequestURL().toString();
|
||||
if (requestUrl.contains("ngrok")) {
|
||||
response.sendRedirect("/access-denied");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+13
-6
@@ -1,13 +1,15 @@
|
||||
package io.github.tiennm99.qrattendance.ngrok;
|
||||
|
||||
import com.ngrok.Session;
|
||||
import io.github.tiennm99.qrattendance.config.NgrokConfiguration;
|
||||
import io.github.tiennm99.qrattendance.configuration.NgrokConfiguration;
|
||||
import io.github.tiennm99.qrattendance.data.ApplicationData;
|
||||
import java.net.URI;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.web.context.WebServerInitializedEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -19,7 +21,10 @@ import org.springframework.stereotype.Component;
|
||||
public class NgrokWebServerEventListener {
|
||||
|
||||
private final NgrokConfiguration ngrokConfiguration;
|
||||
private String url;
|
||||
private final ApplicationData data;
|
||||
|
||||
@Value("${server.port}")
|
||||
int port;
|
||||
|
||||
@SneakyThrows
|
||||
@EventListener
|
||||
@@ -27,10 +32,12 @@ public class NgrokWebServerEventListener {
|
||||
if (ngrokConfiguration.isEnabled()) {
|
||||
var sessionBuilder = Session.withAuthtoken(ngrokConfiguration.getAuthToken());
|
||||
try (var session = sessionBuilder.connect()) {
|
||||
var endpoint =
|
||||
session.forwardHttp(session.httpEndpoint(), new URI("http://localhost:8080").toURL());
|
||||
url = endpoint.getUrl();
|
||||
log.info("ngrok url: {}", url);
|
||||
var url = "http://localhost:" + port;
|
||||
var endpoint = session.forwardHttp(session.httpEndpoint(), new URI(url).toURL());
|
||||
var ngrokUrl = endpoint.getUrl();
|
||||
log.info("url: {}", url);
|
||||
log.info("ngrok url: {}", ngrokUrl);
|
||||
data.setNgrokUrl(ngrokUrl);
|
||||
endpoint.join();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package io.github.tiennm99.qrattendance.service;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||
import com.google.zxing.qrcode.QRCodeWriter;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Base64;
|
||||
import javax.imageio.ImageIO;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class QRCodeService {
|
||||
|
||||
public String generateQRCode(String text) {
|
||||
try {
|
||||
QRCodeWriter qrCodeWriter = new QRCodeWriter();
|
||||
var bitMatrix = qrCodeWriter.encode(text, BarcodeFormat.QR_CODE, 256, 256);
|
||||
BufferedImage qrImage = MatrixToImageWriter.toBufferedImage(bitMatrix);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ImageIO.write(qrImage, "png", baos);
|
||||
byte[] imageBytes = baos.toByteArray();
|
||||
return Base64.getEncoder().encodeToString(imageBytes);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to generate QR code", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package io.github.tiennm99.qrattendance.service;
|
||||
|
||||
import io.github.tiennm99.qrattendance.data.ApplicationData;
|
||||
import io.github.tiennm99.qrattendance.data.Submission;
|
||||
import io.github.tiennm99.qrattendance.util.DistanceUtil;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.FillPatternType;
|
||||
import org.apache.poi.ss.usermodel.IndexedColors;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||
@Service
|
||||
@Slf4j
|
||||
public class SubmissionService {
|
||||
private final ApplicationData data;
|
||||
|
||||
public void addSubmission(
|
||||
String studentId, double latitude, double longitude, String ip, String userAgent) {
|
||||
var duplicatedWith = "";
|
||||
for (Submission submission : data.getSubmissions()) {
|
||||
if (submission.getUserAgent().equals(userAgent)) {
|
||||
duplicatedWith = submission.getStudentId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
double serverLatitude = data.getServerLocation().getLeft();
|
||||
double serverLongitude = data.getServerLocation().getRight();
|
||||
double distance =
|
||||
DistanceUtil.calculateDistance(latitude, longitude, serverLatitude, serverLongitude);
|
||||
|
||||
boolean tooFarFromServer = distance > 1.0;
|
||||
|
||||
var newSubmission =
|
||||
new Submission(
|
||||
studentId, latitude, longitude, ip, userAgent, duplicatedWith, tooFarFromServer);
|
||||
|
||||
data.getSubmissions().add(newSubmission);
|
||||
|
||||
log.info(
|
||||
"New submission: {}, {}, {}, {}, {}, {}, {}",
|
||||
studentId,
|
||||
latitude,
|
||||
longitude,
|
||||
ip,
|
||||
userAgent,
|
||||
duplicatedWith,
|
||||
tooFarFromServer);
|
||||
}
|
||||
|
||||
public void exportSubmissions() {
|
||||
List<Submission> submissions = data.getSubmissions();
|
||||
try (Workbook workbook = new XSSFWorkbook()) {
|
||||
Sheet sheet = workbook.createSheet("Submissions");
|
||||
Row headerRow = sheet.createRow(0);
|
||||
String[] headers = {
|
||||
"Student ID",
|
||||
"Latitude",
|
||||
"Longitude",
|
||||
"IP",
|
||||
"User Agent",
|
||||
"Duplicated With",
|
||||
"Too Far From Server"
|
||||
};
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
Cell cell = headerRow.createCell(i);
|
||||
cell.setCellValue(headers[i]);
|
||||
}
|
||||
|
||||
CellStyle redStyle = workbook.createCellStyle();
|
||||
redStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
|
||||
redStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
||||
|
||||
int rowNum = 1;
|
||||
for (Submission submission : submissions) {
|
||||
Row row = sheet.createRow(rowNum++);
|
||||
row.createCell(0).setCellValue(submission.getStudentId());
|
||||
row.createCell(1).setCellValue(submission.getLatitude());
|
||||
row.createCell(2).setCellValue(submission.getLongitude());
|
||||
row.createCell(3).setCellValue(submission.getIp());
|
||||
row.createCell(4).setCellValue(submission.getUserAgent());
|
||||
row.createCell(5).setCellValue(submission.getDuplicatedWith());
|
||||
row.createCell(6).setCellValue(submission.isTooFarFromServer());
|
||||
|
||||
if (!submission.getDuplicatedWith().isEmpty() || submission.isTooFarFromServer()) {
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
row.getCell(i).setCellStyle(redStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File exportFile = new File("submissions.xlsx");
|
||||
try (FileOutputStream fileOut = new FileOutputStream(exportFile)) {
|
||||
workbook.write(fileOut);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error exporting submissions", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.github.tiennm99.qrattendance.util;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.commons.math3.util.FastMath;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class DistanceUtil {
|
||||
public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
|
||||
final int R = 6371; // Radius of the earth in km
|
||||
double latDistance = FastMath.toRadians(lat2 - lat1);
|
||||
double lonDistance = FastMath.toRadians(lon2 - lon1);
|
||||
double a =
|
||||
FastMath.sin(latDistance / 2) * FastMath.sin(latDistance / 2)
|
||||
+ FastMath.cos(FastMath.toRadians(lat1))
|
||||
* FastMath.cos(FastMath.toRadians(lat2))
|
||||
* FastMath.sin(lonDistance / 2)
|
||||
* FastMath.sin(lonDistance / 2);
|
||||
double c = 2 * FastMath.atan2(FastMath.sqrt(a), FastMath.sqrt(1 - a));
|
||||
return R * c; // Distance in km
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package io.github.tiennm99.qrattendance.views;
|
||||
|
||||
import com.vaadin.flow.component.html.H1;
|
||||
import com.vaadin.flow.component.html.Paragraph;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
import com.vaadin.flow.router.Route;
|
||||
|
||||
@Route("")
|
||||
public class HomeView extends VerticalLayout {
|
||||
|
||||
public HomeView() {
|
||||
|
||||
add(new H1("Welcome to your new application"));
|
||||
add(new Paragraph("This is the home view"));
|
||||
|
||||
add(new Paragraph("You can edit this view in src\\main\\java\\io\\github\\tiennm99\\qrattendance\\views\\HomeView.java"));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ ngrok:
|
||||
enabled: true
|
||||
authToken: CHANGE_ME
|
||||
server:
|
||||
port: 8080
|
||||
port: 31009
|
||||
spring:
|
||||
application:
|
||||
name: java-qr-attendance
|
||||
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Access Denied</title>
|
||||
<link rel="stylesheet"
|
||||
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5 text-center">
|
||||
<h1>Access Denied</h1>
|
||||
<p>You do not have permission to access this page.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Map</title>
|
||||
<link rel="stylesheet"
|
||||
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"/>
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"/>
|
||||
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
|
||||
<style>
|
||||
#map {
|
||||
height: 400px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<div id="map" class="mb-3"></div>
|
||||
<form id="locationForm" th:action="@{/set-server-location}" method="post" class="text-center">
|
||||
<input type="hidden" id="lat" name="lat"/>
|
||||
<input type="hidden" id="lon" name="lon"/>
|
||||
<button type="submit" id="confirmBtn" class="btn btn-primary" disabled>Confirm Location</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script th:inline="javascript">
|
||||
var map = L.map('map').setView([0, 0], 2);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
var marker;
|
||||
|
||||
function onLocationFound(e) {
|
||||
var radius = e.accuracy / 2;
|
||||
if (marker) {
|
||||
map.removeLayer(marker);
|
||||
}
|
||||
marker = L.marker(e.latlng).addTo(map);
|
||||
map.setView(e.latlng, 13);
|
||||
document.getElementById('confirmBtn').disabled = false;
|
||||
updateForm(e.latlng);
|
||||
}
|
||||
|
||||
function onLocationError(e) {
|
||||
alert(e.message);
|
||||
}
|
||||
|
||||
function updateForm(latlng) {
|
||||
document.getElementById('lat').value = latlng.lat;
|
||||
document.getElementById('lon').value = latlng.lng;
|
||||
}
|
||||
|
||||
map.on('locationfound', onLocationFound);
|
||||
map.on('locationerror', onLocationError);
|
||||
map.on('click', function (e) {
|
||||
onLocationFound(e);
|
||||
});
|
||||
|
||||
map.locate({setView: true, maxZoom: 16});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Gate</title>
|
||||
<link rel="stylesheet"
|
||||
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"/>
|
||||
<script>
|
||||
function getLocation() {
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(sendLocation);
|
||||
} else {
|
||||
alert("Geolocation is not supported by this browser.");
|
||||
}
|
||||
}
|
||||
|
||||
function sendLocation(position) {
|
||||
const latitude = position.coords.latitude;
|
||||
const longitude = position.coords.longitude;
|
||||
|
||||
fetch('/check-location', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: `latitude=${latitude}&longitude=${longitude}`
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(result => {
|
||||
if (result === 'rejected') {
|
||||
alert('You are rejected due to being more than 1km away from the server location.');
|
||||
} else {
|
||||
window.location.href = '/view';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body onload="getLocation()">
|
||||
<div class="container text-center mt-5">
|
||||
<h1>Gate</h1>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,112 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>Manage</title>
|
||||
<link rel="stylesheet"
|
||||
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"/>
|
||||
<style>
|
||||
#submissions {
|
||||
max-height: 50vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
width: 256px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.highlight-red {
|
||||
color: red !important;
|
||||
}
|
||||
|
||||
.table-striped tbody tr:nth-of-type(odd) {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.table-striped tbody tr:nth-of-type(even) {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function displaySubmission(submission) {
|
||||
var submissionsTable = document.getElementById('submissionsTable');
|
||||
var newRow = submissionsTable.insertRow();
|
||||
var highlightClass = (submission.duplicatedWith != '' || submission.tooFarFromServer)
|
||||
? 'highlight-red' : '';
|
||||
newRow.innerHTML = `
|
||||
<td class="${highlightClass}">${submission.studentId}</td>
|
||||
<td class="${highlightClass}">${submission.latitude}</td>
|
||||
<td class="${highlightClass}">${submission.longitude}</td>
|
||||
<td class="${highlightClass}">${submission.ip}</td>
|
||||
<td class="${highlightClass}">${submission.userAgent}</td>
|
||||
<td class="${highlightClass}">${submission.duplicatedWith}</td>
|
||||
<td class="${highlightClass}">${submission.tooFarFromServer}</td>
|
||||
`;
|
||||
}
|
||||
|
||||
function refreshSubmissions() {
|
||||
fetch('/submissions')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
var submissionsTable = document.getElementById('submissionsTable');
|
||||
submissionsTable.innerHTML = '';
|
||||
data.forEach(submission => displaySubmission(submission));
|
||||
var submissionsDiv = document.getElementById('submissions');
|
||||
submissionsDiv.scrollTop = submissionsDiv.scrollHeight;
|
||||
});
|
||||
}
|
||||
|
||||
setInterval(refreshSubmissions, 5000);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<h1 class="text-center">Manage</h1>
|
||||
<div class="text-center mb-3">
|
||||
<img th:src="'data:image/png;base64,' + ${qrCode}" alt="QR Code" class="qr-code"/>
|
||||
</div>
|
||||
<div id="submissions" class="mb-3">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Student ID</th>
|
||||
<th>Latitude</th>
|
||||
<th>Longitude</th>
|
||||
<th>IP</th>
|
||||
<th>User Agent</th>
|
||||
<th>Duplicated With</th>
|
||||
<th>Too Far From Server</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="submissionsTable">
|
||||
<tr th:each="submission : ${submissions}"
|
||||
th:class="${(submission.duplicatedWith != '' or submission.tooFarFromServer) ? 'highlight-red' : ''}">
|
||||
<td th:text="${submission.studentId}"></td>
|
||||
<td th:text="${submission.latitude}"></td>
|
||||
<td th:text="${submission.longitude}"></td>
|
||||
<td th:text="${submission.ip}"></td>
|
||||
<td th:text="${submission.userAgent}"></td>
|
||||
<td th:text="${submission.duplicatedWith}"></td>
|
||||
<td th:text="${submission.tooFarFromServer}"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<button class="btn btn-primary" onclick="exportSubmissions()">Export Submissions</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function exportSubmissions() {
|
||||
fetch('/export-submissions', {
|
||||
method: 'POST'
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(message => alert(message))
|
||||
.catch(error => console.error('Error:', error));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title>View</title>
|
||||
<link rel="stylesheet"
|
||||
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"/>
|
||||
<script>
|
||||
function getLocationAndSubmit() {
|
||||
if (navigator.geolocation) {
|
||||
navigator.geolocation.getCurrentPosition(sendLocation);
|
||||
} else {
|
||||
alert("Geolocation is not supported by this browser.");
|
||||
}
|
||||
}
|
||||
|
||||
function sendLocation(position) {
|
||||
const latitude = position.coords.latitude;
|
||||
const longitude = position.coords.longitude;
|
||||
const studentId = document.getElementById("studentId").value;
|
||||
const userAgent = navigator.userAgent;
|
||||
|
||||
fetch('/submit', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: `studentId=${studentId}&latitude=${latitude}&longitude=${longitude}`
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(result => {
|
||||
alert(result);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<h1 class="text-center">Submit Your Information</h1>
|
||||
<form onsubmit="event.preventDefault(); getLocationAndSubmit();" class="text-center">
|
||||
<div class="form-group">
|
||||
<label for="studentId">Student ID:</label>
|
||||
<input type="text" id="studentId" name="studentId" class="form-control" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,39 +0,0 @@
|
||||
// This TypeScript configuration file is generated by vaadin-maven-plugin.
|
||||
// This is needed for TypeScript compiler to compile your TypeScript code in the project.
|
||||
// It is recommended to commit this file to the VCS.
|
||||
// You might want to change the configurations to fit your preferences
|
||||
// For more information about the configurations, please refer to http://www.typescriptlang.org/docs/handbook/tsconfig-json.html
|
||||
{
|
||||
"_version": "9.1",
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"jsx": "react-jsx",
|
||||
"inlineSources": true,
|
||||
"module": "esNext",
|
||||
"target": "es2020",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"baseUrl": "src/main/frontend",
|
||||
"paths": {
|
||||
"@vaadin/flow-frontend": ["generated/jar-resources"],
|
||||
"@vaadin/flow-frontend/*": ["generated/jar-resources/*"],
|
||||
"Frontend/*": ["*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/main/frontend/**/*",
|
||||
"types.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/main/frontend/generated/jar-resources/**"
|
||||
]
|
||||
}
|
||||
Vendored
-17
@@ -1,17 +0,0 @@
|
||||
// This TypeScript modules definition file is generated by vaadin-maven-plugin.
|
||||
// You can not directly import your different static files into TypeScript,
|
||||
// This is needed for TypeScript compiler to declare and export as a TypeScript module.
|
||||
// It is recommended to commit this file to the VCS.
|
||||
// You might want to change the configurations to fit your preferences
|
||||
declare module '*.css?inline' {
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
const content: CSSResultGroup;
|
||||
export default content;
|
||||
}
|
||||
|
||||
// Allow any CSS Custom Properties
|
||||
declare module 'csstype' {
|
||||
interface Properties {
|
||||
[index: `--${string}`]: any;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { UserConfigFn } from 'vite';
|
||||
import { overrideVaadinConfig } from './vite.generated';
|
||||
|
||||
const customConfig: UserConfigFn = (env) => ({
|
||||
// Here you can add custom Vite parameters
|
||||
// https://vitejs.dev/config/
|
||||
});
|
||||
|
||||
export default overrideVaadinConfig(customConfig);
|
||||
Reference in new issue
Block a user