Compare commits
50
Commits
particles
...
ace969ba3b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ace969ba3b | ||
|
|
2fc6ba53f6 | ||
|
|
4c38b070ea | ||
|
|
db394beda6 | ||
|
|
76cb3cd89c | ||
|
|
5d8ab2deef | ||
|
|
aef32a8982 | ||
|
|
42f0961f13 | ||
|
|
04310e1cce | ||
|
|
54e747118c | ||
|
|
43430cfbef | ||
|
|
cce83a08de | ||
|
|
f0faa63ca7 | ||
|
|
dfea91d8ca | ||
|
|
73916f0aae | ||
|
|
ebe66c87c0 | ||
|
|
c42fc38b2c | ||
|
|
213f9987d9 | ||
|
|
48cac607de | ||
|
|
6ed2e15017 | ||
|
|
7fc25f46f3 | ||
|
|
c72703ea32 | ||
|
|
e837a9216d | ||
|
|
d4363b3a8a | ||
|
|
1e5862bae6 | ||
|
|
daf88ea437 | ||
|
|
9abd570b87 | ||
|
|
5284d498f3 | ||
|
|
c3a7be82e9 | ||
|
|
fdb57289f8 | ||
|
|
60c1329163 | ||
|
|
0e71c0f581 | ||
|
|
eb67a33331 | ||
|
|
39f20796ce | ||
|
|
e00165c56f | ||
|
|
02c6497700 | ||
|
|
0efd476676 | ||
|
|
237518638c | ||
|
|
fea1a98cea | ||
|
|
ecd9b3d824 | ||
|
|
9808b5d63d | ||
|
|
c13b7077a7 | ||
|
|
023ae809ef | ||
|
|
3a6f137c9a | ||
|
|
cb8447a096 | ||
|
|
3e98e1a498 | ||
|
|
4c31a91bb4 | ||
|
|
d6faaba01c | ||
|
|
56175e62d6 | ||
|
|
174ed834ca |
@@ -0,0 +1,36 @@
|
|||||||
|
package com.alttd.altitudeweb.config;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
|
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class SecurityAuthFailureHandler implements AccessDeniedHandler, AuthenticationEntryPoint {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
AccessDeniedException accessDeniedException) throws IOException {
|
||||||
|
log.warn("Access denied: User '{}' attempted to access '{}' without proper permissions",
|
||||||
|
request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : "unknown",
|
||||||
|
request.getRequestURI());
|
||||||
|
|
||||||
|
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access Denied");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commence(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
AuthenticationException authException) throws IOException {
|
||||||
|
log.warn("Authentication failure: Unauthenticated user attempted to access secured endpoint '{}'",
|
||||||
|
request.getRequestURI());
|
||||||
|
|
||||||
|
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Required");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,20 +31,34 @@ import java.security.interfaces.RSAPublicKey;
|
|||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
|
|
||||||
private final KeyPairService keyPairService;
|
private final KeyPairService keyPairService;
|
||||||
|
private final SecurityAuthFailureHandler securityAuthFailureHandler;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
return http
|
return http
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(
|
||||||
.requestMatchers("/login/userLogin/**", "/login/requestNewUserLogin/**").permitAll()
|
auth -> auth
|
||||||
.requestMatchers("/team/**", "/history/**").permitAll()
|
.requestMatchers("/form/**").hasAuthority(PermissionClaimDto.USER.getValue())
|
||||||
.requestMatchers("/form/**").hasAuthority(PermissionClaimDto.USER.getValue())
|
.requestMatchers("/head_mod/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
||||||
.requestMatchers("/head_mod/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
.requestMatchers("/particles/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
||||||
.anyRequest().authenticated()
|
.requestMatchers("/files/save/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
||||||
)
|
.anyRequest().permitAll()
|
||||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
|
)
|
||||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.oauth2ResourceServer(
|
||||||
.build();
|
oauth2 -> oauth2
|
||||||
|
.jwt(Customizer.withDefaults())
|
||||||
|
.authenticationEntryPoint(securityAuthFailureHandler)
|
||||||
|
.accessDeniedHandler(securityAuthFailureHandler)
|
||||||
|
)
|
||||||
|
.exceptionHandling(
|
||||||
|
ex -> ex
|
||||||
|
.authenticationEntryPoint(securityAuthFailureHandler)
|
||||||
|
.accessDeniedHandler(securityAuthFailureHandler)
|
||||||
|
)
|
||||||
|
.sessionManagement(
|
||||||
|
session -> session
|
||||||
|
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
+14
-14
@@ -83,21 +83,23 @@ public class LoginController implements LoginApi {
|
|||||||
@RateLimit(limit = 5, timeValue = 1, timeUnit = TimeUnit.MINUTES, key = "login")
|
@RateLimit(limit = 5, timeValue = 1, timeUnit = TimeUnit.MINUTES, key = "login")
|
||||||
@Override
|
@Override
|
||||||
public ResponseEntity<String> login(String code) {
|
public ResponseEntity<String> login(String code) {
|
||||||
CacheEntry cacheEntry1 = new CacheEntry(UUID.fromString("55e46bc3-2a29-4c53-850f-dbd944dc5c5f"), Instant.now().plusSeconds(TimeUnit.DAYS.toSeconds(1)));
|
|
||||||
cache.put("23232323", cacheEntry1);
|
|
||||||
if (code == null) {
|
if (code == null) {
|
||||||
|
log.warn("Received null login code");
|
||||||
return ResponseEntity.badRequest().build();
|
return ResponseEntity.badRequest().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
CacheEntry cacheEntry = cache.get(code);
|
CacheEntry cacheEntry = cache.get(code);
|
||||||
if (cacheEntry == null || cacheEntry.expiry().isBefore(Instant.now())) {
|
if (cacheEntry == null || cacheEntry.expiry().isBefore(Instant.now())) {
|
||||||
|
log.warn("Received invalid login code {}", code);
|
||||||
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
String token = generateToken(cacheEntry.uuid);
|
String token = generateToken(cacheEntry.uuid);
|
||||||
|
log.debug("Generated token for user {} with token {}", cacheEntry.uuid, token);
|
||||||
|
|
||||||
cache.remove(code);
|
cache.remove(code);
|
||||||
|
|
||||||
|
log.debug("Generated token for user {}", cacheEntry.uuid);
|
||||||
return ResponseEntity.ok(token);
|
return ResponseEntity.ok(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,12 +136,12 @@ public class LoginController implements LoginApi {
|
|||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
//TODO make a JWT for renewing and one for storing permissions for a session (expiry 1 hour)
|
//TODO make a JWT for renewing and one for storing permissions for a session (expiry 1 hour)
|
||||||
Instant expiryTime = now.plusSeconds(TimeUnit.DAYS.toSeconds(30));
|
Instant expiryTime = now.plusSeconds(TimeUnit.DAYS.toSeconds(30));
|
||||||
CompletableFuture<PrivilegedUser> privilegedUserCompletableFuture = new CompletableFuture<>();
|
CompletableFuture<Optional<PrivilegedUser>> privilegedUserCompletableFuture = new CompletableFuture<>();
|
||||||
List<PermissionClaimDto> claimList = new ArrayList<>();
|
List<PermissionClaimDto> claimList = new ArrayList<>();
|
||||||
Connection.getConnection(Databases.DEFAULT)
|
Connection.getConnection(Databases.DEFAULT)
|
||||||
.runQuery(sqlSession -> {
|
.runQuery(sqlSession -> {
|
||||||
try {
|
try {
|
||||||
PrivilegedUser privilegedUser = sqlSession.getMapper(PrivilegedUserMapper.class)
|
Optional<PrivilegedUser> privilegedUser = sqlSession.getMapper(PrivilegedUserMapper.class)
|
||||||
.getUserByUuid(uuid.toString());
|
.getUserByUuid(uuid.toString());
|
||||||
|
|
||||||
privilegedUserCompletableFuture.complete(privilegedUser);
|
privilegedUserCompletableFuture.complete(privilegedUser);
|
||||||
@@ -148,17 +150,15 @@ public class LoginController implements LoginApi {
|
|||||||
privilegedUserCompletableFuture.completeExceptionally(e);
|
privilegedUserCompletableFuture.completeExceptionally(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
PrivilegedUser privilegedUser = privilegedUserCompletableFuture.join();
|
Optional<PrivilegedUser> privilegedUser = privilegedUserCompletableFuture.join();
|
||||||
claimList.add(PermissionClaimDto.USER);
|
claimList.add(PermissionClaimDto.USER);
|
||||||
if (privilegedUser != null) {
|
privilegedUser.ifPresent(user -> user.getPermissions().forEach(permission -> {
|
||||||
privilegedUser.getPermissions().forEach(permission -> {
|
try {
|
||||||
try {
|
claimList.add(PermissionClaimDto.valueOf(permission));
|
||||||
claimList.add(PermissionClaimDto.valueOf(permission));
|
} catch (IllegalArgumentException e) {
|
||||||
} catch (IllegalArgumentException e) {
|
log.warn("Received invalid permission claim: {}", permission);
|
||||||
log.warn("Received invalid permission claim: {}", permission);
|
}
|
||||||
}
|
}));
|
||||||
});
|
|
||||||
}
|
|
||||||
JwtClaimsSet claims = JwtClaimsSet.builder()
|
JwtClaimsSet claims = JwtClaimsSet.builder()
|
||||||
.issuer("altitudeweb")
|
.issuer("altitudeweb")
|
||||||
.claim("authorities", claimList.stream().map(PermissionClaimDto::getValue).toList())
|
.claim("authorities", claimList.stream().map(PermissionClaimDto::getValue).toList())
|
||||||
|
|||||||
+176
@@ -0,0 +1,176 @@
|
|||||||
|
package com.alttd.altitudeweb.controllers.particles;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.api.ParticlesApi;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.core.io.ByteArrayResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
public class ParticleController implements ParticlesApi {
|
||||||
|
|
||||||
|
@Value("${login.secret:#{null}}")
|
||||||
|
private String loginSecret;
|
||||||
|
|
||||||
|
@Value("${particles.file_path}")
|
||||||
|
private String particlesFilePath;
|
||||||
|
|
||||||
|
@Value("${notification.server.url:http://localhost:8080}")
|
||||||
|
private String notificationServerUrl;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<Resource> downloadFile(String authorization, String filename) throws Exception {
|
||||||
|
if (authorization == null || !authorization.equals(loginSecret)) {
|
||||||
|
return ResponseEntity.status(401).build();
|
||||||
|
}
|
||||||
|
File file = new File(particlesFilePath);
|
||||||
|
if (!file.exists() || !file.isDirectory()) {
|
||||||
|
log.error("Particles file path {} is not a directory, not downloading particles file", particlesFilePath);
|
||||||
|
return ResponseEntity.status(404).build();
|
||||||
|
}
|
||||||
|
File targetFile = new File(file, filename);
|
||||||
|
return getFileForDownload(targetFile, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<Resource> downloadFileForUser(String authorization, String uuid, String filename) throws Exception {
|
||||||
|
if (authorization == null || !authorization.equals(loginSecret)) {
|
||||||
|
return ResponseEntity.status(401).build();
|
||||||
|
}
|
||||||
|
File file = new File(particlesFilePath);
|
||||||
|
if (!file.exists() || !file.isDirectory()) {
|
||||||
|
log.error("Particles file path {} is not a directory, not downloading particles user file", particlesFilePath);
|
||||||
|
return ResponseEntity.status(404).build();
|
||||||
|
}
|
||||||
|
File targetDir = new File(file, uuid);
|
||||||
|
if (targetDir.exists()) {
|
||||||
|
return getFileForDownload(targetDir, filename);
|
||||||
|
} else {
|
||||||
|
log.warn("User {} does not have a directory for particles files", uuid);
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResponseEntity<Resource> getFileForDownload(File file, String filename) {
|
||||||
|
File targetFile = new File(file, filename);
|
||||||
|
if (!targetFile.exists()) {
|
||||||
|
log.warn("Particles file {} does not exist", targetFile.getAbsolutePath());
|
||||||
|
return ResponseEntity.notFound().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetFile.isFile()) {
|
||||||
|
log.warn("Particles file {} is not a file", targetFile.getAbsolutePath());
|
||||||
|
return ResponseEntity.status(404).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Path path = targetFile.toPath();
|
||||||
|
ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));
|
||||||
|
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentLength(targetFile.length())
|
||||||
|
.contentType(MediaType.APPLICATION_JSON)
|
||||||
|
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
|
||||||
|
.body(resource);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Failed to read particles file {}: {}", targetFile.getAbsolutePath(), e.getMessage(), e);
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<Void> saveFile(String filename, MultipartFile content) throws Exception {
|
||||||
|
File file = new File(particlesFilePath);
|
||||||
|
if (!file.exists() || !file.isDirectory()) {
|
||||||
|
log.error("Particles file path {} is not a directory, not saving particles file", particlesFilePath);
|
||||||
|
return ResponseEntity.status(404).build();
|
||||||
|
}
|
||||||
|
ResponseEntity<Void> voidResponseEntity = writeContentToFile(file, filename, content);
|
||||||
|
notifyServerOfFileUpload(filename);
|
||||||
|
return voidResponseEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<Void> saveFileForUser(String uuid, String filename, MultipartFile content) throws Exception {
|
||||||
|
File file = new File(particlesFilePath);
|
||||||
|
if (!file.exists() || !file.isDirectory()) {
|
||||||
|
log.error("Particles file path {} is not a directory, not saving particles user file", particlesFilePath);
|
||||||
|
return ResponseEntity.status(404).build();
|
||||||
|
}
|
||||||
|
File targetDir = new File(file, uuid);
|
||||||
|
if (!file.exists()) {
|
||||||
|
log.debug("Creating particles directory {}", targetDir.getAbsolutePath());
|
||||||
|
if (targetDir.mkdirs()) {
|
||||||
|
log.info("Created particles user directory {}", targetDir.getAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseEntity<Void> voidResponseEntity = writeContentToFile(file, filename, content);
|
||||||
|
notifyServerOfFileUpload(uuid, filename);
|
||||||
|
return voidResponseEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyServerOfFileUpload(String filename) {
|
||||||
|
String notificationUrl = String.format("%s/notify/%s.json", notificationServerUrl, filename);
|
||||||
|
sendNotification(notificationUrl, String.format("file upload: %s", filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyServerOfFileUpload(String uuid, String filename) {
|
||||||
|
String notificationUrl = String.format("%s/notify/%s/%s.json", notificationServerUrl, uuid, filename);
|
||||||
|
sendNotification(notificationUrl, String.format("file upload for user %s: %s", uuid, filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendNotification(String notificationUrl, String logDescription) {
|
||||||
|
try {
|
||||||
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
|
ResponseEntity<String> response = restTemplate.getForEntity(notificationUrl, String.class);
|
||||||
|
|
||||||
|
if (response.getStatusCode().is2xxSuccessful()) {
|
||||||
|
log.info("Successfully notified server of {}", logDescription);
|
||||||
|
} else {
|
||||||
|
log.warn("Failed to notify server of {}, status: {}",
|
||||||
|
logDescription, response.getStatusCode());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error notifying server of {}", logDescription, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ResponseEntity<Void> writeContentToFile(File dir, String filename, MultipartFile content) {
|
||||||
|
File targetFile = new File(dir, filename);
|
||||||
|
if (!Files.isWritable(targetFile.toPath())) {
|
||||||
|
log.error("Particles file {} is not writable", targetFile.getAbsolutePath());
|
||||||
|
return ResponseEntity.status(403).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetFile.exists()) {
|
||||||
|
log.warn("Overwriting existing particles file {}", targetFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
content.transferTo(targetFile);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to write particles file {}", targetFile.getAbsolutePath(), e);
|
||||||
|
return ResponseEntity.status(500).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,4 +5,4 @@ database.host=${DB_HOST:localhost}
|
|||||||
database.user=${DB_USER:root}
|
database.user=${DB_USER:root}
|
||||||
database.password=${DB_PASSWORD:root}
|
database.password=${DB_PASSWORD:root}
|
||||||
cors.allowed-origins=${CORS:https://beta.alttd.com}
|
cors.allowed-origins=${CORS:https://beta.alttd.com}
|
||||||
logging.level.com.alttd.altitudeweb=INFO
|
logging.level.com.alttd.altitudeweb=DEBUG
|
||||||
|
|||||||
@@ -6,4 +6,6 @@ database.user=${DB_USER:root}
|
|||||||
database.password=${DB_PASSWORD:root}
|
database.password=${DB_PASSWORD:root}
|
||||||
cors.allowed-origins=${CORS:https://alttd.com}
|
cors.allowed-origins=${CORS:https://alttd.com}
|
||||||
login.secret=${LOGIN_SECRET:SET_TOKEN}
|
login.secret=${LOGIN_SECRET:SET_TOKEN}
|
||||||
|
particles.file_path=${user.home}/.altitudeweb/particles
|
||||||
|
notification.server.url=${SERVER_IP:10.0.0.107}:${SERVER_PORT:8080}
|
||||||
logging.level.com.alttd.altitudeweb=INFO
|
logging.level.com.alttd.altitudeweb=INFO
|
||||||
|
|||||||
+3
-1
@@ -1,8 +1,10 @@
|
|||||||
package com.alttd.altitudeweb.database.web_db;
|
package com.alttd.altitudeweb.database.web_db;
|
||||||
|
|
||||||
import org.apache.ibatis.annotations.*;
|
import org.apache.ibatis.annotations.*;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public interface PrivilegedUserMapper {
|
public interface PrivilegedUserMapper {
|
||||||
|
|
||||||
@@ -23,7 +25,7 @@ public interface PrivilegedUserMapper {
|
|||||||
@Result(property = "permissions", column = "id", javaType = List.class,
|
@Result(property = "permissions", column = "id", javaType = List.class,
|
||||||
many = @Many(select = "getPermissionsForUser"))
|
many = @Many(select = "getPermissionsForUser"))
|
||||||
})
|
})
|
||||||
PrivilegedUser getUserByUuid(@Param("uuid") String uuid);
|
Optional<PrivilegedUser> getUserByUuid(@Param("uuid") String uuid);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves all privileged users with their permissions
|
* Retrieves all privileged users with their permissions
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.alttd.altitudeweb.setup;
|
|||||||
|
|
||||||
import com.alttd.altitudeweb.database.Databases;
|
import com.alttd.altitudeweb.database.Databases;
|
||||||
import com.alttd.altitudeweb.database.web_db.KeyPairMapper;
|
import com.alttd.altitudeweb.database.web_db.KeyPairMapper;
|
||||||
|
import com.alttd.altitudeweb.database.web_db.PrivilegedUserMapper;
|
||||||
import com.alttd.altitudeweb.database.web_db.SettingsMapper;
|
import com.alttd.altitudeweb.database.web_db.SettingsMapper;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.ibatis.session.SqlSession;
|
import org.apache.ibatis.session.SqlSession;
|
||||||
@@ -18,6 +19,7 @@ public class InitializeWebDb {
|
|||||||
Connection.getConnection(Databases.DEFAULT, (configuration) -> {
|
Connection.getConnection(Databases.DEFAULT, (configuration) -> {
|
||||||
configuration.addMapper(SettingsMapper.class);
|
configuration.addMapper(SettingsMapper.class);
|
||||||
configuration.addMapper(KeyPairMapper.class);
|
configuration.addMapper(KeyPairMapper.class);
|
||||||
|
configuration.addMapper(PrivilegedUserMapper.class);
|
||||||
}).join()
|
}).join()
|
||||||
.runQuery(SqlSession -> {
|
.runQuery(SqlSession -> {
|
||||||
createSettingsTable(SqlSession);
|
createSettingsTable(SqlSession);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"@angular/platform-browser": "^19.2.0",
|
"@angular/platform-browser": "^19.2.0",
|
||||||
"@angular/platform-browser-dynamic": "^19.2.0",
|
"@angular/platform-browser-dynamic": "^19.2.0",
|
||||||
"@angular/router": "^19.2.0",
|
"@angular/router": "^19.2.0",
|
||||||
|
"@auth0/angular-jwt": "^5.2.0",
|
||||||
"@types/three": "^0.177.0",
|
"@types/three": "^0.177.0",
|
||||||
"ngx-cookie-service": "^19.1.2",
|
"ngx-cookie-service": "^19.1.2",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
@@ -1,8 +1,8 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {Meta, Title} from '@angular/platform-browser';
|
import {Meta, Title} from '@angular/platform-browser';
|
||||||
import {ALTITUDE_VERSION} from './constant';
|
import {ALTITUDE_VERSION} from '@custom-types/constant';
|
||||||
import {Router, RouterOutlet} from '@angular/router';
|
import {Router, RouterOutlet} from '@angular/router';
|
||||||
import {FooterComponent} from './footer/footer.component';
|
import {FooterComponent} from '@pages/footer/footer/footer.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -10,8 +10,8 @@ import {FooterComponent} from './footer/footer.component';
|
|||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
styleUrl: './app.component.scss',
|
styleUrl: './app.component.scss',
|
||||||
imports: [
|
imports: [
|
||||||
FooterComponent,
|
RouterOutlet,
|
||||||
RouterOutlet
|
FooterComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit {
|
export class AppComponent implements OnInit {
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import {ApplicationConfig, provideZoneChangeDetection} from '@angular/core';
|
import {ApplicationConfig, provideZoneChangeDetection} from '@angular/core';
|
||||||
import {provideRouter} from '@angular/router';
|
import {provideRouter} from '@angular/router';
|
||||||
|
import {CookieService} from 'ngx-cookie-service';
|
||||||
|
|
||||||
import {routes} from './app.routes';
|
import {routes} from './app.routes';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [provideZoneChangeDetection({eventCoalescing: true}), provideRouter(routes)]
|
providers: [
|
||||||
|
provideZoneChangeDetection({eventCoalescing: true}),
|
||||||
|
provideRouter(routes),
|
||||||
|
CookieService,
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,114 +3,110 @@ import {Routes} from '@angular/router';
|
|||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
loadComponent: () => import('./home/home.component').then(m => m.HomeComponent)
|
loadComponent: () => import('./pages/home/home.component').then(m => m.HomeComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'particles',
|
path: 'particles',
|
||||||
loadComponent: () => import('./particles/particles.component').then(m => m.ParticlesComponent)
|
loadComponent: () => import('./pages/particles/particles.component').then(m => m.ParticlesComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'map',
|
path: 'map',
|
||||||
loadComponent: () => import('./map/map.component').then(m => m.MapComponent)
|
loadComponent: () => import('./pages/features/map/map.component').then(m => m.MapComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'rules',
|
path: 'rules',
|
||||||
loadComponent: () => import('./rules/rules.component').then(m => m.RulesComponent)
|
loadComponent: () => import('./pages/reference/rules/rules.component').then(m => m.RulesComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'vote',
|
path: 'vote',
|
||||||
loadComponent: () => import('./vote/vote.component').then(m => m.VoteComponent)
|
loadComponent: () => import('./pages/vote/vote.component').then(m => m.VoteComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'about',
|
path: 'about',
|
||||||
loadComponent: () => import('./about/about.component').then(m => m.AboutComponent)
|
loadComponent: () => import('./pages/altitude/about/about.component').then(m => m.AboutComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'socials',
|
path: 'socials',
|
||||||
loadComponent: () => import('./socials/socials.component').then(m => m.SocialsComponent)
|
loadComponent: () => import('./pages/altitude/socials/socials.component').then(m => m.SocialsComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'team',
|
path: 'team',
|
||||||
loadComponent: () => import('./team/team.component').then(m => m.TeamComponent)
|
loadComponent: () => import('./pages/altitude/team/team.component').then(m => m.TeamComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'birthdays',
|
path: 'birthdays',
|
||||||
loadComponent: () => import('./birthdays/birthdays.component').then(m => m.BirthdaysComponent)
|
loadComponent: () => import('./pages/altitude/birthdays/birthdays.component').then(m => m.BirthdaysComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'terms',
|
path: 'terms',
|
||||||
loadComponent: () => import('./terms/terms.component').then(m => m.TermsComponent)
|
loadComponent: () => import('./pages/footer/terms/terms.component').then(m => m.TermsComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'privacy',
|
path: 'privacy',
|
||||||
loadComponent: () => import('./privacy/privacy.component').then(m => m.PrivacyComponent)
|
loadComponent: () => import('./pages/footer/privacy/privacy.component').then(m => m.PrivacyComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'bans',
|
path: 'bans',
|
||||||
loadComponent: () => import('./bans/bans.component').then(m => m.BansComponent)
|
loadComponent: () => import('./pages/reference/bans/bans.component').then(m => m.BansComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'bans/:type/:id',
|
path: 'bans/:type/:id',
|
||||||
loadComponent: () => import('./bans/details/details.component').then(m => m.DetailsComponent)
|
loadComponent: () => import('./pages/reference/bans/details/details.component').then(m => m.DetailsComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'economy',
|
path: 'economy',
|
||||||
loadComponent: () => import('./economy/economy.component').then(m => m.EconomyComponent)
|
loadComponent: () => import('./pages/features/economy/economy.component').then(m => m.EconomyComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'claiming',
|
path: 'claiming',
|
||||||
loadComponent: () => import('./claiming/claiming.component').then(m => m.ClaimingComponent)
|
loadComponent: () => import('./pages/features/claiming/claiming.component').then(m => m.ClaimingComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'mypet',
|
path: 'mypet',
|
||||||
loadComponent: () => import('./mypet/mypet.component').then(m => m.MypetComponent)
|
loadComponent: () => import('./pages/features/mypet/mypet.component').then(m => m.MypetComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'warps',
|
path: 'warps',
|
||||||
loadComponent: () => import('./warps/warps.component').then(m => m.WarpsComponent)
|
loadComponent: () => import('./pages/features/warps/warps.component').then(m => m.WarpsComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'skyblock',
|
path: 'skyblock',
|
||||||
loadComponent: () => import('./skyblock/skyblock.component').then(m => m.SkyblockComponent)
|
loadComponent: () => import('./pages/features/skyblock/skyblock.component').then(m => m.SkyblockComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'customfeatures',
|
path: 'customfeatures',
|
||||||
loadComponent: () => import('./customfeatures/customfeatures.component').then(m => m.CustomfeaturesComponent)
|
loadComponent: () => import('./pages/features/customfeatures/customfeatures.component').then(m => m.CustomfeaturesComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'guide',
|
path: 'guide',
|
||||||
loadComponent: () => import('./guide/guide.component').then(m => m.GuideComponent)
|
loadComponent: () => import('./pages/reference/guide/guide.component').then(m => m.GuideComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'ranks',
|
path: 'ranks',
|
||||||
loadComponent: () => import('./ranks/ranks.component').then(m => m.RanksComponent)
|
loadComponent: () => import('./pages/reference/ranks/ranks.component').then(m => m.RanksComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'commandlist',
|
path: 'commandlist',
|
||||||
loadComponent: () => import('./commandlist/commandlist.component').then(m => m.CommandlistComponent)
|
loadComponent: () => import('./pages/reference/commandlist/commandlist.component').then(m => m.CommandlistComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'mapart',
|
path: 'mapart',
|
||||||
loadComponent: () => import('./mapart/mapart.component').then(m => m.MapartComponent)
|
loadComponent: () => import('./pages/reference/mapart/mapart.component').then(m => m.MapartComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'lag',
|
path: 'lag',
|
||||||
loadComponent: () => import('./lag/lag.component').then(m => m.LagComponent)
|
loadComponent: () => import('./pages/reference/lag/lag.component').then(m => m.LagComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'staffpowers',
|
path: 'staffpowers',
|
||||||
loadComponent: () => import('./staffpowers/staffpowers.component').then(m => m.StaffpowersComponent)
|
loadComponent: () => import('./pages/reference/staffpowers/staffpowers.component').then(m => m.StaffpowersComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'forms/:form',
|
path: 'forms/:form',
|
||||||
loadComponent: () => import('./forms/forms.component').then(m => m.FormsComponent)
|
loadComponent: () => import('./pages/forms/forms.component').then(m => m.FormsComponent)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'forms',
|
path: 'forms',
|
||||||
loadComponent: () => import('./forms/forms.component').then(m => m.FormsComponent)
|
loadComponent: () => import('./pages/forms/forms.component').then(m => m.FormsComponent)
|
||||||
},
|
}
|
||||||
{
|
|
||||||
path: 'particles',
|
|
||||||
loadComponent: () => import('./particles/particles.component').then(m => m.ParticlesComponent)
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'community'" height="460px" background_image="/public/img/backgrounds/community.jpg"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>Community</h1>
|
||||||
|
<h2>Talented people who help Altitude in more than one way.</h2>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="customContainer">
|
||||||
|
<h2>Current Nitro Boosters</h2>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section id="social" class="darkmodeSectionThree">
|
||||||
|
<div class="container" style="padding: 50px 0 0 0; justify-content: center;">
|
||||||
|
<h2 class="sectionTitle">Social Media</h2>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; justify-content: center; padding-bottom: 30px;">
|
||||||
|
<p style="text-align: center;">We're currently not looking for more people to help manage our socials.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section id="crateTeam" class="darkmodeSection">
|
||||||
|
<div class="container" style="padding: 50px 0 0 0; justify-content: center;">
|
||||||
|
<h2 class="sectionTitle">Crate Team</h2>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container" style="padding: 50px 0 0 0; justify-content: center;">
|
||||||
|
<h2 class="sectionTitle">Event Leaders</h2>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; justify-content: center; padding-bottom: 30px;">
|
||||||
|
<p style="text-align: center;">We're currently not looking for more Event Leaders.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container" style="padding: 50px 0 0 0; justify-content: center;">
|
||||||
|
<h2 class="sectionTitle">Event Team</h2>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; justify-content: center; padding-bottom: 30px;">
|
||||||
|
<div style="flex-direction: column;">
|
||||||
|
<p style="text-align: center;">We occasionally open applications for the event team.</p>
|
||||||
|
<p style="text-align: center;">If you're interested in joining you simply need to keep an eye on the Discord
|
||||||
|
announcements.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container" style="padding: 50px 0 0 0; justify-content: center;">
|
||||||
|
<h2 class="sectionTitle">YouTubers & Streamers</h2>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; justify-content: center; padding-bottom: 30px;">
|
||||||
|
<div style="flex-direction: column;">
|
||||||
|
<p style="text-align: center;"><a style="cursor: pointer;" id="reqButton">Show Requirements...</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="req" class="hide" style="display: flex; justify-content: center; padding-bottom: 30px;">
|
||||||
|
<div style="flex-direction: column; justify-content: center; max-width: 800px;">
|
||||||
|
<p style="text-align: center;"><span style="font-family: 'opensans-bold', sans-serif;">Requirements:</span>
|
||||||
|
</p>
|
||||||
|
<p style="text-align: center;">You need to have at least one recent stream/video on Altitude which we can use
|
||||||
|
to gauge if your audience enjoys your content on Altitude.</p>
|
||||||
|
<br>
|
||||||
|
<p style="text-align: center;">Twitch: You need to be affiliate and get at least 5 viewers on average while
|
||||||
|
streaming on Altitude.</p>
|
||||||
|
<p style="text-align: center;">YouTube videos: You need at least 500 subs and have at least 200 views per
|
||||||
|
video within a week on average for Altitude content.</p>
|
||||||
|
<p style="text-align: center;">YouTube streamers: You need at least 500 subs and have at least 5 viewers on
|
||||||
|
average while streaming on Altitude.</p>
|
||||||
|
<br>
|
||||||
|
<p style="text-align: center;"><span style="font-family: 'opensans-bold', sans-serif;">Note:</span> Before
|
||||||
|
accepting or denying you we will watch your latest video/stream on Altitude (so keep your broadcasts public
|
||||||
|
on twitch).</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
.customContainer {
|
||||||
|
width: 80%;
|
||||||
|
max-width: 1020px;
|
||||||
|
margin: auto;
|
||||||
|
padding: 80px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CommunityComponent } from './community.component';
|
||||||
|
|
||||||
|
describe('CommunityComponent', () => {
|
||||||
|
let component: CommunityComponent;
|
||||||
|
let fixture: ComponentFixture<CommunityComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [CommunityComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CommunityComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from "../header/header.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-community',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent
|
||||||
|
],
|
||||||
|
templateUrl: './community.component.html',
|
||||||
|
styleUrl: './community.component.scss'
|
||||||
|
})
|
||||||
|
export class CommunityComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<h2 mat-dialog-title>Login</h2>
|
|
||||||
<div mat-dialog-content>
|
|
||||||
<form [formGroup]="loginForm">
|
|
||||||
<mat-form-field appearance="fill" style="width: 100%">
|
|
||||||
<mat-label>Enter your code</mat-label>
|
|
||||||
<input matInput formControlName="code" type="text">
|
|
||||||
<mat-error *ngIf="formHasError()">
|
|
||||||
Code is required
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div mat-dialog-actions align="end">
|
|
||||||
<button mat-button (click)="onCancel()">Cancel</button>
|
|
||||||
<button mat-flat-button color="primary" (click)="onSubmit()" [disabled]="!loginForm.valid">
|
|
||||||
Submit
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'nickgenerator'" height="460px" background_image="/public/img/backgrounds/trees.jpg"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>Nickname Generator</h1>
|
||||||
|
<h2>Customize your in-game nickname</h2>
|
||||||
|
<h3 style="font-family: 'minecraft-text', sans-serif; font-size: 0.8rem; margin-top: 10px;">Made by TheParm</h3>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<!-- <section class="darkmodeSection">
|
||||||
|
<div class="container containerNick">
|
||||||
|
<div style="padding: 0 5% 0 5%;">
|
||||||
|
<div id="parts" class="previewNickDiv">
|
||||||
|
</div>
|
||||||
|
<div class="previewNickDiv">
|
||||||
|
<input type="button" class="button" value="Add Part" onclick="addPart()"/>
|
||||||
|
<input type="button" class="button" value="Remove Part" onclick="deletePart()"/>
|
||||||
|
</div>
|
||||||
|
<br><br><br><br>
|
||||||
|
<div id="commandTry" class="previewNickDiv">
|
||||||
|
<div id="try" class="command darkBg"></div>
|
||||||
|
<input type="button" class="button copy" value="Copy" onclick="copy(this)"/>
|
||||||
|
</div>
|
||||||
|
<div id="commandRequest" class="previewNickDiv">
|
||||||
|
<div id="request" class="command darkBg"></div>
|
||||||
|
<input type="button" class="button copy" value="Copy" onclick="copy(this)"/>
|
||||||
|
</div>
|
||||||
|
<div id="preview" class="preview darkBg previewNickDiv">
|
||||||
|
</div>
|
||||||
|
<div id="template" class='part' style="display: none">
|
||||||
|
<p style="font-family: 'minecraft-text', sans-serif">
|
||||||
|
Text: <input type="text" id="text" class="textPart" size=18 oninput="inputChanged()"/>
|
||||||
|
Gradient: <input type="checkbox" id="grad" class="gradPart" oninput="onGradient(this)"/>
|
||||||
|
<input id="colorA" type="text" class="coloris colorAPart color" value="#ffffff" oninput="inputChanged()"/>
|
||||||
|
<input id="colorB" type="text" class="coloris colorBPart color" value="#ffffff" oninput="inputChanged()"/>
|
||||||
|
Continuation: <input type="checkbox" id="cont" class="contPart" disabled oninput="onContinuation(this)"/>
|
||||||
|
<span id="invalid" class="invalidPart" style="display: none">(min 1 - max 16 chars)</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 20px; text-align: center;">
|
||||||
|
<p style="font-family: 'minecraft-text', sans-serif">
|
||||||
|
Usage: Add as many parts as you wish, then apply the color and/or gradient, and copy/paste the command
|
||||||
|
into the minecraft chat. The total length of the nickname should be between 3 and 16 characters. Use the
|
||||||
|
continuation checkbox to continue the gradient from the last gradient color.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section> -->
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NickgeneratorComponent } from './nickgenerator.component';
|
||||||
|
|
||||||
|
describe('NickgeneratorComponent', () => {
|
||||||
|
let component: NickgeneratorComponent;
|
||||||
|
let fixture: ComponentFixture<NickgeneratorComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [NickgeneratorComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(NickgeneratorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from "../header/header.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-nickgenerator',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent
|
||||||
|
],
|
||||||
|
templateUrl: './nickgenerator.component.html',
|
||||||
|
styleUrl: './nickgenerator.component.scss'
|
||||||
|
})
|
||||||
|
export class NickgeneratorComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'nicknames'" height="460px" background_image="/public/img/backgrounds/trees.jpg"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>How To Get A Nickname</h1>
|
||||||
|
<h2>Personalize your writing and nickname in-game by choosing some of the millions of custom colors that we have
|
||||||
|
to offer!</h2>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Creating a Nickname</h2>
|
||||||
|
<p>Donors that have the Duke or Archduke rank have the ability to create custom nicknames for themselves. A
|
||||||
|
nickname should be similar enough to the player’s username for that player to be identifiable, and it
|
||||||
|
<span style="font-family: 'opensans-bold', sans-serif;">must not exceed 16 characters in length</span>. In
|
||||||
|
general, a nickname should include the main part of a player’s actual username to avoid confusion.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Make it Your Own</h2>
|
||||||
|
<p>Altitude now supports the full range of RGB colors, which includes more than 16 million different hues.
|
||||||
|
In addition, the previous basic color codes still exist and are able to be used in combination with the
|
||||||
|
RGB colors. You can still use <span style="font-family: 'opensans-bold', sans-serif;">/colors</span> to
|
||||||
|
see the basic color codes that are available for use. You can also make use of <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/colorsextra</span> which shows many different RGB
|
||||||
|
color options. These colors will be in the form of <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">#XXXXXX</span>; write down the colors that you like so
|
||||||
|
that you can easily use them later.</p>
|
||||||
|
<img ngSrc="/public/img/random/colors.png" alt="RGB colors" style="width: 100%; padding:0;" height="130"
|
||||||
|
width="480">
|
||||||
|
<p>Output of /colorsextra</p>
|
||||||
|
<p>Furthermore, you can also use the <a [routerLink]="['/nickgenerator']">nickname generation tool</a> to
|
||||||
|
make and preview potential nicknames - as well as copying the commands to use in-game.</p>
|
||||||
|
<p>Players are encouraged to play around and personalize their nicknames, as long as they follow the rules
|
||||||
|
and are legible! There are endless possibilities beyond simple gradients as well; players can create any
|
||||||
|
combination of standard colors and gradients that they choose.</p>
|
||||||
|
<p>Even if you aren’t a Duke or Archduke, you can try out colors like this on signs in-game.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Request Your Nickname</h2>
|
||||||
|
<p>Players can only submit a new nickname request using <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/nick request <name></span> once per day. When a
|
||||||
|
nickname is requested, it will notify staff for near-instant approval. Creating custom nicknames using RGB
|
||||||
|
can be difficult, so we have implemented <span style="font-family: 'opensans-bold', sans-serif;">/nick try <name></span>
|
||||||
|
so that you can experiment with your nicknames as many times as you want. It is highly encouraged to use
|
||||||
|
this command before submitting a formal request to staff. You can select an RGB color by putting the <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">#XXXXXX</span> code into curly braces before your
|
||||||
|
text.</p>
|
||||||
|
<p>We also support gradients in nicknames. You can specify the start and finish color and your nickname will
|
||||||
|
automatically find a gradient that goes between the two endpoints. <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">The symbol > is used to start a gradient, and the symbol < is used to close a gradient.</span>
|
||||||
|
You can have as many gradients as you want in your nickname, as long as it is readable.</p>
|
||||||
|
<p><span style="font-family: 'opensans-bold', sans-serif;">/nick try {#003380}Player</span>
|
||||||
|
would result in a blue name that read as “Player”.</p>
|
||||||
|
<p><span style="font-family: 'opensans-bold', sans-serif;">/nick try {#003380>}Player{#0000FF<}</span>
|
||||||
|
would result in the nickname reading as “Player” with a blue gradient fading across the letters in the
|
||||||
|
name.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Useful Commands</h2>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/nick help</span> - Shows a list of all useful
|
||||||
|
nickname commands and how to use them.
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/nick try name</span> - Allows players to see
|
||||||
|
how a nickname would look as many times as you like.
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/nick request name</span> - Allows players to
|
||||||
|
submit a new nickname to staff for approval. This function can only be used once per day.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NicknamesComponent } from './nicknames.component';
|
||||||
|
|
||||||
|
describe('NicknamesComponent', () => {
|
||||||
|
let component: NicknamesComponent;
|
||||||
|
let fixture: ComponentFixture<NicknamesComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [NicknamesComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(NicknamesComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from "../header/header.component";
|
||||||
|
import {NgOptimizedImage} from '@angular/common';
|
||||||
|
import {RouterLink} from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-nicknames',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent,
|
||||||
|
NgOptimizedImage,
|
||||||
|
RouterLink
|
||||||
|
],
|
||||||
|
templateUrl: './nicknames.component.html',
|
||||||
|
styleUrl: './nicknames.component.scss'
|
||||||
|
})
|
||||||
|
export class NicknamesComponent {
|
||||||
|
|
||||||
|
}
|
||||||
+3
-5
@@ -1,16 +1,14 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {ScrollService} from '../scroll/scroll.service';
|
import {ScrollService} from '@services/scroll.service';
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '@header/header.component';
|
||||||
import {RemoveTrailingPeriodPipe} from "../util/RemoveTrailingPeriodPipe";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-about',
|
selector: 'app-about',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
HeaderComponent,
|
HeaderComponent
|
||||||
RemoveTrailingPeriodPipe
|
|
||||||
],
|
],
|
||||||
templateUrl: './about.component.html',
|
templateUrl: './about.component.html',
|
||||||
styleUrl: './about.component.scss'
|
styleUrl: './about.component.scss'
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {ScrollService} from '../scroll/scroll.service';
|
import {ScrollService} from '@services/scroll.service';
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '@header/header.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-birthdays',
|
selector: 'app-birthdays',
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {ScrollService} from '../scroll/scroll.service';
|
import {ScrollService} from '@services/scroll.service';
|
||||||
import {CommonModule, NgOptimizedImage} from '@angular/common';
|
import {CommonModule, NgOptimizedImage} from '@angular/common';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '@header/header.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-socials',
|
selector: 'app-socials',
|
||||||
+4
-4
@@ -1,11 +1,11 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {ScrollService} from '../scroll/scroll.service';
|
import {ScrollService} from '@services/scroll.service';
|
||||||
import {BASE_PATH, Player, TeamService} from '../../api';
|
import {BASE_PATH, Player, TeamService} from '@api';
|
||||||
import {CommonModule, NgOptimizedImage} from '@angular/common';
|
import {CommonModule, NgOptimizedImage} from '@angular/common';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '@header/header.component';
|
||||||
import {CookieService} from 'ngx-cookie-service';
|
import {CookieService} from 'ngx-cookie-service';
|
||||||
import {map, Observable, shareReplay} from 'rxjs';
|
import {map, Observable, shareReplay} from 'rxjs';
|
||||||
import {environment} from '../../environments/environment';
|
import {environment} from '@environment';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-team',
|
selector: 'app-team',
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '@header/header.component';
|
||||||
import {NgOptimizedImage} from '@angular/common';
|
import {NgOptimizedImage} from '@angular/common';
|
||||||
import {RouterLink} from '@angular/router';
|
import {RouterLink} from '@angular/router';
|
||||||
|
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {HeaderComponent} from "../header/header.component";
|
import {HeaderComponent} from "@header/header.component";
|
||||||
import {RouterLink} from '@angular/router';
|
import {RouterLink} from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '@header/header.component';
|
||||||
import {NgOptimizedImage} from '@angular/common';
|
import {NgOptimizedImage} from '@angular/common';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '@header/header.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '@header/header.component';
|
||||||
import {NgOptimizedImage} from '@angular/common';
|
import {NgOptimizedImage} from '@angular/common';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '@header/header.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-skyblock',
|
selector: 'app-skyblock',
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '@header/header.component';
|
||||||
import {NgOptimizedImage} from '@angular/common';
|
import {NgOptimizedImage} from '@angular/common';
|
||||||
import {RouterLink} from '@angular/router';
|
import {RouterLink} from '@angular/router';
|
||||||
|
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {ALTITUDE_VERSION} from '../constant';
|
import {ALTITUDE_VERSION} from '@custom-types/constant';
|
||||||
import {CommonModule, NgOptimizedImage} from '@angular/common';
|
import {CommonModule, NgOptimizedImage} from '@angular/common';
|
||||||
import {RouterLink} from '@angular/router';
|
import {RouterLink} from '@angular/router';
|
||||||
|
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {ScrollService} from '../scroll/scroll.service';
|
import {ScrollService} from '@services/scroll.service';
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '@header/header.component';
|
||||||
import {RouterLink} from '@angular/router';
|
import {RouterLink} from '@angular/router';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import {Component} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {ScrollService} from '../scroll/scroll.service';
|
import {ScrollService} from '@services/scroll.service';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '@header/header.component';
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {RouterLink} from '@angular/router';
|
import {RouterLink} from '@angular/router';
|
||||||
|
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {FormsComponent} from '../forms.component';
|
import {FormsComponent} from '../forms.component';
|
||||||
import {FormControl, FormGroup, Validators} from '@angular/forms';
|
import {FormControl, FormGroup, Validators} from '@angular/forms';
|
||||||
import {AppealsService, MinecraftAppeal} from '../../../api';
|
import {AppealsService, MinecraftAppeal} from '@api';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-appeal',
|
selector: 'app-appeal',
|
||||||
+3
-3
@@ -1,12 +1,12 @@
|
|||||||
import {Component, Input, OnInit} from '@angular/core';
|
import {Component, Input, OnInit} from '@angular/core';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '@header/header.component';
|
||||||
import {MatDialog} from '@angular/material/dialog';
|
import {MatDialog} from '@angular/material/dialog';
|
||||||
import {ActivatedRoute} from '@angular/router';
|
import {ActivatedRoute} from '@angular/router';
|
||||||
import {LoginDialogComponent} from '../login/login.component';
|
import {LoginDialogComponent} from '@shared-components/login/login.component';
|
||||||
import {KeyValuePipe, NgForOf, NgIf} from '@angular/common';
|
import {KeyValuePipe, NgForOf, NgIf} from '@angular/common';
|
||||||
import {FormType} from './form_type';
|
import {FormType} from './form_type';
|
||||||
import {MatButton} from '@angular/material/button';
|
import {MatButton} from '@angular/material/button';
|
||||||
import {AuthService} from '../services/auth.service';
|
import {AuthService} from '@services/auth.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-forms',
|
selector: 'app-forms',
|
||||||
+12
@@ -130,7 +130,19 @@
|
|||||||
<li class="nav_li"><a class="nav_link2" target="_blank" href="https://alttd.com/blog/">Blog</a></li>
|
<li class="nav_li"><a class="nav_link2" target="_blank" href="https://alttd.com/blog/">Blog</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav_li" *ngIf="isAuthenticated">
|
||||||
|
<a [id]="getCurrentPageId(['particles'])"
|
||||||
|
class="nav_link fake_link" [ngClass]="active">Special</a>
|
||||||
|
<ul class="dropdown" *ngIf="hasAccess(['HEAD_MOD'])">
|
||||||
|
<li class="nav_li"><a class="nav_link2" [routerLink]="['/particles']">Particles</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<ng-container *ngIf="!isAuthenticated">
|
||||||
|
<button mat-button (click)="openLoginDialog()" style="color: white">
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
<app-theme></app-theme>
|
<app-theme></app-theme>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
+41
-4
@@ -1,7 +1,12 @@
|
|||||||
import {Component, HostListener, Input} from '@angular/core';
|
import {Component, HostListener, Input, OnDestroy, OnInit} from '@angular/core';
|
||||||
import {CommonModule, NgOptimizedImage} from '@angular/common';
|
import {CommonModule, NgOptimizedImage} from '@angular/common';
|
||||||
import {ThemeComponent} from '../theme/theme.component';
|
import {ThemeComponent} from '@shared-components/theme/theme.component';
|
||||||
import {RouterLink} from '@angular/router';
|
import {RouterLink} from '@angular/router';
|
||||||
|
import {AuthService} from '@services/auth.service';
|
||||||
|
import {Subscription} from 'rxjs';
|
||||||
|
import {LoginDialogComponent} from '@shared-components/login/login.component';
|
||||||
|
import {MatButton} from '@angular/material/button';
|
||||||
|
import {MatDialog} from '@angular/material/dialog';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -9,13 +14,14 @@ import {RouterLink} from '@angular/router';
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
ThemeComponent,
|
ThemeComponent,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
NgOptimizedImage
|
NgOptimizedImage,
|
||||||
|
MatButton
|
||||||
],
|
],
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
templateUrl: './header.component.html',
|
templateUrl: './header.component.html',
|
||||||
styleUrls: ['./header.component.scss']
|
styleUrls: ['./header.component.scss']
|
||||||
})
|
})
|
||||||
export class HeaderComponent {
|
export class HeaderComponent implements OnInit, OnDestroy {
|
||||||
@Input() current_page: string = '';
|
@Input() current_page: string = '';
|
||||||
@Input() background_image: string = '';
|
@Input() background_image: string = '';
|
||||||
@Input() height: string = '';
|
@Input() height: string = '';
|
||||||
@@ -23,6 +29,24 @@ export class HeaderComponent {
|
|||||||
|
|
||||||
public active: string = '';
|
public active: string = '';
|
||||||
public inverseYPos: number = 0;
|
public inverseYPos: number = 0;
|
||||||
|
private subscription: Subscription | undefined;
|
||||||
|
public isAuthenticated: boolean = false;
|
||||||
|
|
||||||
|
constructor(protected authService: AuthService,
|
||||||
|
private dialog: MatDialog) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.subscription = this.authService.isAuthenticated$.subscribe(isAuthenticated => {
|
||||||
|
this.isAuthenticated = isAuthenticated;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subscription?.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
@HostListener('window:scroll', [])
|
@HostListener('window:scroll', [])
|
||||||
onWindowScroll(): void {
|
onWindowScroll(): void {
|
||||||
@@ -59,4 +83,17 @@ export class HeaderComponent {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public openLoginDialog() {
|
||||||
|
const dialogRef = this.dialog.open(LoginDialogComponent, {
|
||||||
|
width: '400px',
|
||||||
|
})
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
console.log(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasAccess(claims: string[]): boolean {
|
||||||
|
return this.authService.hasAccess(claims)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+8
-4
@@ -1,11 +1,12 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {Title} from '@angular/platform-browser';
|
import {Title} from '@angular/platform-browser';
|
||||||
import {ALTITUDE_VERSION} from '../constant';
|
import {ALTITUDE_VERSION} from '@custom-types/constant';
|
||||||
import {ScrollService} from '../scroll/scroll.service';
|
import {ScrollService} from '@services/scroll.service';
|
||||||
import {CommonModule, NgOptimizedImage} from '@angular/common';
|
import {CommonModule, NgOptimizedImage} from '@angular/common';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '@header/header.component';
|
||||||
import {CopyIpComponent} from '../copy-ip/copy-ip.component';
|
import {CopyIpComponent} from '@shared-components/copy-ip/copy-ip.component';
|
||||||
import {RouterLink} from '@angular/router';
|
import {RouterLink} from '@angular/router';
|
||||||
|
import {JwtHelperService} from '@auth0/angular-jwt';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -16,6 +17,9 @@ import {RouterLink} from '@angular/router';
|
|||||||
RouterLink,
|
RouterLink,
|
||||||
NgOptimizedImage
|
NgOptimizedImage
|
||||||
],
|
],
|
||||||
|
providers: [
|
||||||
|
{provide: JwtHelperService, useFactory: () => new JwtHelperService()}
|
||||||
|
],
|
||||||
selector: 'app-home',
|
selector: 'app-home',
|
||||||
templateUrl: './home.component.html',
|
templateUrl: './home.component.html',
|
||||||
styleUrl: './home.component.scss'
|
styleUrl: './home.component.scss'
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<div class="card-div">
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>Frames</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="frames-container">
|
||||||
|
<mat-tab-group [selectedIndex]="frames.indexOf(currentFrame)"
|
||||||
|
(selectedIndexChange)="switchFrame(frames[$event])">
|
||||||
|
<mat-tab *ngFor="let frameId of frames" [label]="frameId">
|
||||||
|
<div class="frame-content">
|
||||||
|
<h3>Particles in {{ frameId }}</h3>
|
||||||
|
<div class="particles-list">
|
||||||
|
<div *ngFor="let particle of particleData.frames[frameId]; let i = index" class="particle-item">
|
||||||
|
<span class="particle-item-text">
|
||||||
|
Particle {{ i + 1 }}: ({{ particle.x.toFixed(2) }}, {{ particle.y.toFixed(2) }}
|
||||||
|
, {{ particle.z.toFixed(2) }})
|
||||||
|
</span>
|
||||||
|
<button mat-icon-button (click)="removeParticle(frameId, i)">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-icon-button (click)="highlightParticle(frameId, i)">
|
||||||
|
<mat-icon>lightbulb</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="!particleData.frames[frameId] || particleData.frames[frameId].length === 0"
|
||||||
|
class="no-particles">
|
||||||
|
No particles in this frame. Click on the plane to add particles.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="frame-actions">
|
||||||
|
<button mat-raised-button color="warn" (click)="removeFrame(frameId)"
|
||||||
|
[disabled]="frames.length <= 1">
|
||||||
|
Remove Frame
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
<div class="add-frame">
|
||||||
|
<button mat-raised-button color="primary" (click)="addFrame()">
|
||||||
|
Add New Frame
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user