program finalizat
@@ -4,6 +4,8 @@ node_modules/
|
|||||||
# Ignore build artifacts
|
# Ignore build artifacts
|
||||||
dist/
|
dist/
|
||||||
.idea
|
.idea
|
||||||
|
out/
|
||||||
|
json_files/
|
||||||
|
|
||||||
# Ignore environment-specific settings
|
# Ignore environment-specific settings
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
@@ -4,25 +4,68 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Aplicatie P2P pentru stocarea resurselor digitale",
|
"description": "Aplicatie P2P pentru stocarea resurselor digitale",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "del-cli dist",
|
"clean": "del-cli dist && del-cli out",
|
||||||
"start": "tsc && copyfiles -u 1 'src/**/*' dist && electron dist/main/main.js"
|
"build-dist": "tsc && copyfiles -u 1 'src/**/*' dist",
|
||||||
|
"start-dev": "tsc && copyfiles -u 1 'src/**/*' dist && electron dist/main/main.js",
|
||||||
|
"start": "npm run clean && npm run build-dist && electron-forge start",
|
||||||
|
"package": "npm run clean && npm run build-dist && electron-forge package",
|
||||||
|
"make": "npm run clean && npm run build-dist && electron-forge make"
|
||||||
},
|
},
|
||||||
"main": "dist/main/main.js",
|
"main": "dist/main/main.js",
|
||||||
"author": "Cerbu Andrei - Mihnea",
|
"author": "Cerbu Andrei - Mihnea",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"electron": "^32.0.1",
|
|
||||||
"ping": "^0.4.4",
|
"ping": "^0.4.4",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/electron": "^1.6.10",
|
"@electron-forge/cli": "^6.0.0",
|
||||||
"@types/node": "^22.6.1",
|
"@electron-forge/maker-deb": "^6.0.0",
|
||||||
|
"@electron-forge/maker-rpm": "^6.0.0",
|
||||||
|
"@electron-forge/maker-squirrel": "^6.0.0",
|
||||||
|
"@electron-forge/maker-zip": "^6.0.0",
|
||||||
"@types/ping": "^0.4.4",
|
"@types/ping": "^0.4.4",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
"del-cli": "^5.0.0",
|
"del-cli": "^5.0.0",
|
||||||
|
"electron": "^33.0.2",
|
||||||
"typescript": "^5.6.2"
|
"typescript": "^5.6.2"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"forge": {
|
||||||
|
"packagerConfig": {
|
||||||
|
"name": "CeoApp",
|
||||||
|
"icon": "../app_icons/icon.ico",
|
||||||
|
"ignore": [
|
||||||
|
"src",
|
||||||
|
".idea",
|
||||||
|
"tsconfig.json"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"makers": [
|
||||||
|
{
|
||||||
|
"name": "@electron-forge/maker-squirrel",
|
||||||
|
"config": {
|
||||||
|
"name": "CEOApp",
|
||||||
|
"setupIcon": "../app_icons/icon.ico"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@electron-forge/maker-zip",
|
||||||
|
"platforms": ["darwin"],
|
||||||
|
"config": {
|
||||||
|
"icon": "../app_icons/icon.icns"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@electron-forge/maker-deb",
|
||||||
|
"platforms": ["linux"],
|
||||||
|
"config": {
|
||||||
|
"icon": "../app_icons/icon.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 298 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
@@ -0,0 +1,133 @@
|
|||||||
|
body, html {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: "Pridi", sans-serif;
|
||||||
|
background-color: #4A628A;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4, h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
height: 90%;
|
||||||
|
width: 35%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subheading {
|
||||||
|
padding-top: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
height: 230px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
font-size: 1em;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
resize: none;
|
||||||
|
background-color: #3A4A6C; /* Darker background for the textarea */
|
||||||
|
color: #f0f0f0; /* Light text color */
|
||||||
|
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea::placeholder {
|
||||||
|
opacity: 0.7;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Customize the textarea scrollbar */
|
||||||
|
textarea::-webkit-scrollbar {
|
||||||
|
width: 10px; /* Width of the scrollbar */
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.1); /* Track background color */
|
||||||
|
border-radius: 10px; /* Rounded corners for the track */
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(74, 98, 138, 0.8); /* Scrollbar handle color */
|
||||||
|
border-radius: 10px; /* Rounded corners for the scrollbar handle */
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(74, 98, 138, 1); /* Darker color on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #4A628A;
|
||||||
|
box-shadow: 0 0 5px rgba(74, 98, 138, 0.5);
|
||||||
|
background-color: #334466; /* Slightly brighter background on focus */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.announcement-form-footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 20px;
|
||||||
|
gap: 10px; /* Space between buttons */
|
||||||
|
}
|
||||||
|
|
||||||
|
button#submitAnnouncement {
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
background-color: #4A628A;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button#backButton {
|
||||||
|
background-color: transparent; /* No background */
|
||||||
|
border: none; /* Remove default button border */
|
||||||
|
color: #4A628A; /* Set text color */
|
||||||
|
font-size: 1em; /* Set font size */
|
||||||
|
cursor: pointer; /* Add pointer cursor for interactivity */
|
||||||
|
text-decoration: underline; /* Underline text */
|
||||||
|
padding: 0; /* Remove default padding */
|
||||||
|
}
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
/* Global styles for body and background */
|
|
||||||
body, html {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: "Roboto", sans-serif;
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
background-image: url('../assets/background.png');
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Container starts hidden */
|
|
||||||
.container {
|
|
||||||
opacity: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Container styling to match your previous design */
|
|
||||||
.left_block, .right_block {
|
|
||||||
background-color: rgba(83, 92, 145, 0.71);
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block {
|
|
||||||
width: 45%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right_block {
|
|
||||||
width: 45%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
font-size: 2rem;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Enable scrolling in the departments container */
|
|
||||||
#departments-container {
|
|
||||||
max-height: 50vh; /* Limit the height of the container */
|
|
||||||
overflow-y: auto; /* Enable vertical scrolling */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Scrollbar styling for departments container */
|
|
||||||
#departments-container::-webkit-scrollbar {
|
|
||||||
width: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#departments-container::-webkit-scrollbar-track {
|
|
||||||
background: #1B1A55;
|
|
||||||
}
|
|
||||||
|
|
||||||
#departments-container::-webkit-scrollbar-thumb {
|
|
||||||
background: #535C91;
|
|
||||||
border: 2px solid #1B1A55;
|
|
||||||
}
|
|
||||||
|
|
||||||
#departments-container::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form styling */
|
|
||||||
form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: 1rem;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"] {
|
|
||||||
padding: 0.8rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
button[type="submit"], #createDepartmentButton {
|
|
||||||
background-color: #23BDEE;
|
|
||||||
color: white;
|
|
||||||
padding: 0.8rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
button[type="submit"]:hover, #createDepartmentButton:hover {
|
|
||||||
background-color: #1da1cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Department card styling */
|
|
||||||
.department-card {
|
|
||||||
background-color: #ffffff;
|
|
||||||
color: #333;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 1rem 0;
|
|
||||||
border-radius: 5px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.department-card button {
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
margin-left: 10px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modify-button {
|
|
||||||
background-color: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-button {
|
|
||||||
background-color: #f44336;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Modal styles */
|
|
||||||
.modal {
|
|
||||||
display: none;
|
|
||||||
position: fixed;
|
|
||||||
z-index: 1;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: auto;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
background-color: white;
|
|
||||||
margin: 15% auto;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #888;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close {
|
|
||||||
color: #aaa;
|
|
||||||
float: right;
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close:hover, .close:focus {
|
|
||||||
color: black;
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Footer style */
|
|
||||||
.footer {
|
|
||||||
margin-top: 20px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
body, html {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
|
background-color: #4A628A;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4, h2, h1{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
img{
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-section{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
border: 3px solid #f3f3f3;
|
||||||
|
border-top: 3px solid #2196F3;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
@@ -1,110 +1,143 @@
|
|||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
font-weight: 500;
|
background-color: #4A628A;
|
||||||
font-style: normal;
|
}
|
||||||
|
|
||||||
background-image: url('../assets/background.png');
|
h4, h2, h1{
|
||||||
background-size: cover;
|
margin: 0;
|
||||||
background-repeat: no-repeat;
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
color: #1B1A55;
|
|
||||||
font-size: 4vh;
|
|
||||||
text-transform: uppercase;
|
|
||||||
line-height: 2.5rem;
|
|
||||||
margin-bottom: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form {
|
|
||||||
background-color: #535C91;
|
|
||||||
opacity: 71;
|
|
||||||
padding: 9vh 3vh 5vh;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form-title {
|
|
||||||
margin: 0 0 5vh 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form-title h2 {
|
|
||||||
color: #FFFFFF;
|
|
||||||
font-size: 5vh;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form hr {
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form-content {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
width: 70%;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 0.7rem;
|
|
||||||
background-color: #1B1A55;
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
button {
|
display: flex;
|
||||||
font-weight: bold;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
height: 90%;
|
||||||
width: 35%;
|
width: 35%;
|
||||||
font-size: 1rem;
|
|
||||||
padding: 10px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-top: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
.header{
|
||||||
filter: brightness(85%);
|
text-align: center;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subheading{
|
||||||
|
padding-top: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
color: rgba(255,255,255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-content input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: 1px solid #ccc; /* Light gray border */
|
||||||
|
border-radius: 8px; /* Rounded corners */
|
||||||
|
font-size: 1em;
|
||||||
|
font-family: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #f9f9f9; /* Light background */
|
||||||
|
color: #333; /* Dark text color */
|
||||||
|
transition: border-color 0.3s ease, box-shadow 0.3s ease; /* Smooth transition for interaction */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Placeholder Text Styling */
|
||||||
|
.login-form-content input::placeholder {
|
||||||
|
color: #aaa; /* Lighter color for placeholders */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus State Styling */
|
||||||
|
.login-form-content input:focus {
|
||||||
|
outline: none; /* Remove default focus outline */
|
||||||
|
border-color: #4A628A; /* Blue border on focus */
|
||||||
|
box-shadow: 0 0 5px rgba(74, 98, 138, 0.5); /* Subtle shadow for focus */
|
||||||
|
background-color: #fff; /* Slightly brighter background on focus */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Field Hover Effect */
|
||||||
|
.login-form-content input:hover {
|
||||||
|
border-color: #888; /* Darker gray border on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disabled Input Styling */
|
||||||
|
.login-form-content input:disabled {
|
||||||
|
background-color: #e0e0e0; /* Light gray background for disabled input */
|
||||||
|
cursor: not-allowed; /* Show "not allowed" cursor */
|
||||||
|
opacity: 0.7; /* Slight transparency */
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-form-footer {
|
.login-form-footer {
|
||||||
margin-top: 3vh;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
width: 100%;
|
||||||
align-content: center;
|
flex-direction: column;
|
||||||
|
justify-content: space-between; /* Align buttons next to each other */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-evenly;
|
gap: 10px; /* Space between buttons */
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling for buttons */
|
||||||
|
.login-form-footer button {
|
||||||
|
flex: 1; /* Ensure buttons are equal width */
|
||||||
|
padding: 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[name="underline"] {
|
||||||
|
background-color: transparent; /* No background */
|
||||||
|
border: none; /* Remove default button border */
|
||||||
|
color: #4A628A; /* Set text color */
|
||||||
|
font-size: 1em; /* Set font size */
|
||||||
|
cursor: pointer; /* Add pointer cursor for interactivity */
|
||||||
|
text-decoration: underline; /* Underline text */
|
||||||
|
padding: 0; /* Remove default padding */
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name="submit"] {
|
button[name="submit"] {
|
||||||
background-color: #F44336;
|
width: 100%;
|
||||||
color: white;
|
background-color: #4A628A; /* Blue background */
|
||||||
}
|
color: white; /* White text color */
|
||||||
|
|
||||||
button[name="signup"] {
|
|
||||||
background-color: #2196F3;
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
@@ -1,245 +1,208 @@
|
|||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
|
background-color: #4A628A;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4, h2, h1{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
font-size: 32px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
letter-spacing: -1px;
|
||||||
|
|
||||||
background-image: url('../assets/background.png');
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.overlay {
|
|
||||||
display: none;
|
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.85);
|
|
||||||
z-index: 2;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ceo-validation-form {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: 300px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #1B1A55;
|
|
||||||
border-radius: 10px;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ceo-validation-form h2 {
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ceo-validation-form p {
|
.user{
|
||||||
color: white;
|
display: inline;
|
||||||
text-align: center;
|
|
||||||
font-size: 1rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-actions {
|
|
||||||
text-align: center;
|
|
||||||
padding-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-actions button {
|
|
||||||
padding: 10px 20px;
|
|
||||||
margin: 0 10px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
width: 80%;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 0.7rem;
|
|
||||||
background-color: #1B1A55;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#back_button {
|
|
||||||
background-color: #f44336;
|
|
||||||
width: 35%;
|
|
||||||
height: auto;
|
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
.navbar{
|
||||||
#submit_button {
|
padding-top: 8px;
|
||||||
background-color: #4CAF50;
|
padding-right: 32px;
|
||||||
width: 35%;
|
padding-left: 32px;
|
||||||
height: auto;
|
height: 10%;
|
||||||
color: white;
|
display:flex;
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
opacity: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background-color: #535C91;
|
|
||||||
opacity: 71;
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_top {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
text-align: left;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.welcome{
|
||||||
.left_block_top h1 {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_top img {
|
|
||||||
margin: 0 3vw 0 5vw;
|
|
||||||
width: 8vw;
|
|
||||||
height: 8vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_content {
|
|
||||||
margin: 2rem 0 2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_buttons {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
img{
|
||||||
|
padding-right: 8px;
|
||||||
|
width:25px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container{
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
.left_block{
|
||||||
margin: 0 1rem 0 1rem;
|
background: linear-gradient(to bottom, #131A24, #4A628A);
|
||||||
width: 15vw;
|
width: 25%;
|
||||||
height: 10vh;
|
display: flex;
|
||||||
border: none;
|
flex-direction: column;
|
||||||
border-radius: 10px;
|
}
|
||||||
color: white;
|
|
||||||
font-size: 1rem;
|
.notification-title{
|
||||||
font-weight: bold;
|
padding-top: 16px;
|
||||||
text-transform: uppercase;
|
padding-bottom: 16px;
|
||||||
|
text-align: center;
|
||||||
|
height: 10%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications{
|
||||||
|
overflow-y: auto;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-grow: 1; /* Allow it to take available space */
|
||||||
|
max-height: calc(90% - 80px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right_block_content button:last-child {
|
||||||
|
background-color: #FF6347;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right_block_content button:nth-child(8) {
|
||||||
|
background-color: #FF6347;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right_block_content button:last-child:hover {
|
||||||
|
background-color: #fd4c29;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert, .logout-button{
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
border-radius: 15px;
|
||||||
outline: none;
|
width: 80%;
|
||||||
transition: background-color 0.3s ease;
|
min-height: 64px;
|
||||||
|
height: auto;
|
||||||
|
padding:12px;
|
||||||
|
background: #FF6347;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.notification{
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 15px;
|
||||||
|
width: 80%;
|
||||||
|
min-height: 64px;
|
||||||
|
height: auto;
|
||||||
|
padding:12px;
|
||||||
|
background: rgba(186, 229, 255);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
.notification:hover{
|
||||||
filter: brightness(85%);
|
background: #d9efff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left_block_footer {
|
.alert:hover, .logout-button:hover{
|
||||||
|
background: rgb(244, 44, 44);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notif-title{
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 300;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-content{
|
||||||
|
height: 90%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left_block_footer{
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: end;
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right_block {
|
.right_block_content{
|
||||||
display: flex;
|
height: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right_block{
|
||||||
|
width: 75%;
|
||||||
|
display:flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: #535C91;
|
justify-content: center;
|
||||||
opacity: 71;
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.notifications {
|
.right_block_content {
|
||||||
display: flex;
|
display: grid;
|
||||||
margin: 1rem 2rem 2rem 3rem;
|
grid-template-columns: repeat(3, 1fr); /* 3 equal-width columns */
|
||||||
flex-direction: column;
|
gap: 8px; /* Spacing between buttons */
|
||||||
align-items: start;
|
padding: 32px; /* Optional padding */
|
||||||
height: 40vh; /* Fixed height */
|
|
||||||
width: 80%; /* Full width */
|
|
||||||
overflow: auto; /* Enable scrolling */
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications::-webkit-scrollbar {
|
|
||||||
width: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications::-webkit-scrollbar-track {
|
|
||||||
background: #1B1A55;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications::-webkit-scrollbar-thumb {
|
|
||||||
background: #535C91;
|
|
||||||
border: 2px solid #1B1A55;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
button[name="logout"] {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 10vw;
|
|
||||||
height: 5vh;
|
|
||||||
background-color: #F44336;
|
|
||||||
}
|
|
||||||
|
|
||||||
button[name="alert"] {
|
|
||||||
margin: 0.7rem;
|
|
||||||
background-color: #F44336;
|
|
||||||
}
|
|
||||||
|
|
||||||
button[name="notification"] {
|
|
||||||
margin: 0.7rem;
|
|
||||||
background-color: #23BDEE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name="menu_button"] {
|
button[name="menu_button"] {
|
||||||
margin: 0.7rem;
|
padding: 10px 20px;
|
||||||
background-color: #1B1A55;
|
font-size: 16px;
|
||||||
|
background-color: rgba(186,229,244,0.4); /* Button background */
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Disclaimer Modal Styles */
|
button[name="menu_button"]:hover {
|
||||||
#disclaimer-modal {
|
background-color: #3B5173; /* Darker background on hover */
|
||||||
display: none; /* Initially hidden */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-button {
|
.notifications::-webkit-scrollbar {
|
||||||
background-color: #f44336; /* Red color for cancel */
|
width: 4px; /* Width of the scrollbar */
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit-button {
|
.notifications::-webkit-scrollbar-track {
|
||||||
background-color: #4CAF50; /* Green color foQQr submit */
|
background: rgba(255, 255, 255, 0.1); /* Track background color */
|
||||||
|
border-radius: 10px; /* Rounded corners for the track */
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(74, 98, 138, 0.8); /* Scrollbar handle color */
|
||||||
|
border-radius: 10px; /* Rounded corners for the scrollbar handle */
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(74, 98, 138, 1); /* Darker color on hover */
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
html, body, div, span, applet, object, iframe,
|
||||||
|
h1, h2, h3, h4, h5, h6, p, blockquote, pre, hr
|
||||||
|
a, abbr, acronym, address, big, cite, code,
|
||||||
|
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||||
|
small, strike, strong, sub, sup, tt, var,
|
||||||
|
b, u, i, center,
|
||||||
|
dl, dt, dd, ol, ul, li,
|
||||||
|
fieldset, form, label, legend,
|
||||||
|
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||||
|
article, aside, canvas, details, embed,
|
||||||
|
figure, figcaption, footer, header, hgroup,
|
||||||
|
menu, nav, output, ruby, section, summary,
|
||||||
|
time, mark, audio, video {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font: inherit;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
/* HTML5 display-role reset for older browsers */
|
||||||
|
article, aside, details, figcaption, figure,
|
||||||
|
footer, header, hgroup, menu, nav, section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
ol, ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
blockquote, q {
|
||||||
|
quotes: none;
|
||||||
|
}
|
||||||
|
blockquote:before, blockquote:after,
|
||||||
|
q:before, q:after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
body, html {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
|
background-color: #4A628A;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4, h2, h1{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
height: 90%;
|
||||||
|
width: 35%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header{
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
padding-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.departments-container{
|
||||||
|
overflow-y: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: 370px;
|
||||||
|
max-height: 370px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.department-name{
|
||||||
|
width:70%;
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
color: rgba(255,255,255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-management-container{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between; /* Align buttons next to each other */
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px; /* Space between buttons */
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling for buttons */
|
||||||
|
.user-management-container button, .modal button {
|
||||||
|
flex: 1; /* Ensure buttons are equal width */
|
||||||
|
padding: 8px 16px 8px 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[name="underline"] {
|
||||||
|
background-color: transparent; /* No background */
|
||||||
|
border: none; /* Remove default button border */
|
||||||
|
color: #4A628A; /* Set text color */
|
||||||
|
font-size: 1em; /* Set font size */
|
||||||
|
cursor: pointer; /* Add pointer cursor for interactivity */
|
||||||
|
text-decoration: underline; /* Underline text */
|
||||||
|
padding: 0; /* Remove default padding */
|
||||||
|
}
|
||||||
|
|
||||||
|
.department-card{
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-button {
|
||||||
|
width: auto;
|
||||||
|
margin-right: 8px;
|
||||||
|
background-color: #ED4B4B; /* Blue background */
|
||||||
|
color: white; /* White text color */
|
||||||
|
}
|
||||||
|
|
||||||
|
button[name="submit"] {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #4A628A; /* Blue background */
|
||||||
|
color: white; /* White text color */
|
||||||
|
}
|
||||||
|
|
||||||
|
.departments-container::-webkit-scrollbar {
|
||||||
|
width: 4px; /* Width of the scrollbar */
|
||||||
|
}
|
||||||
|
|
||||||
|
.departments-container::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.1); /* Track background color */
|
||||||
|
border-radius: 10px; /* Rounded corners for the track */
|
||||||
|
}
|
||||||
|
|
||||||
|
.departments-container::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(74, 98, 138, 0.8); /* Scrollbar handle color */
|
||||||
|
border-radius: 10px; /* Rounded corners for the scrollbar handle */
|
||||||
|
}
|
||||||
|
|
||||||
|
.departments-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(74, 98, 138, 1); /* Darker color on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
.departments-container:empty {
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name{
|
||||||
|
padding-top: 8px;
|
||||||
|
font-size: 20px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
color: rgba(255,255,255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-department{
|
||||||
|
font-weight: lighter;
|
||||||
|
padding-top: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
color: rgba(255,255,255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
display: none; /* Hidden by default */
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
backdrop-filter: blur(5px);/* Black with opacity */
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: #fefefe;
|
||||||
|
margin: 15% auto;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid #888;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
float: right;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button:hover,
|
||||||
|
.close-button:focus {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
input{
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border: 1px solid #ccc; /* Light gray border */
|
||||||
|
border-radius: 8px; /* Rounded corners */
|
||||||
|
font-size: 12px;
|
||||||
|
font-family: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #f9f9f9; /* Light background */
|
||||||
|
color: #333; /* Dark text color */
|
||||||
|
transition: border-color 0.3s ease, box-shadow 0.3s ease; /* Smooth transition for interaction */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
body, html {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
|
background-color: #4A628A;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4, h2, h1{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
height: 90%;
|
||||||
|
width: 35%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header{
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
padding-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-container{
|
||||||
|
overflow-y: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: 370px;
|
||||||
|
max-height: 370px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-management-container{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between; /* Align buttons next to each other */
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px; /* Space between buttons */
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling for buttons */
|
||||||
|
.user-management-container button {
|
||||||
|
flex: 1; /* Ensure buttons are equal width */
|
||||||
|
padding: 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[name="underline"] {
|
||||||
|
background-color: transparent; /* No background */
|
||||||
|
border: none; /* Remove default button border */
|
||||||
|
color: #4A628A; /* Set text color */
|
||||||
|
font-size: 1em; /* Set font size */
|
||||||
|
cursor: pointer; /* Add pointer cursor for interactivity */
|
||||||
|
text-decoration: underline; /* Underline text */
|
||||||
|
padding: 0; /* Remove default padding */
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-info{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-card{
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-button {
|
||||||
|
width: auto;
|
||||||
|
margin-right: 8px;
|
||||||
|
background-color: #ED4B4B; /* Blue background */
|
||||||
|
color: white; /* White text color */
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-container::-webkit-scrollbar {
|
||||||
|
width: 4px; /* Width of the scrollbar */
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-container::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.1); /* Track background color */
|
||||||
|
border-radius: 10px; /* Rounded corners for the track */
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-container::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(74, 98, 138, 0.8); /* Scrollbar handle color */
|
||||||
|
border-radius: 10px; /* Rounded corners for the scrollbar handle */
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(74, 98, 138, 1); /* Darker color on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
.users-container:empty {
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name{
|
||||||
|
padding-top: 8px;
|
||||||
|
font-size: 20px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
color: rgba(255,255,255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-department{
|
||||||
|
font-weight: lighter;
|
||||||
|
padding-top: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
color: rgba(255,255,255, 0.8);
|
||||||
|
}
|
||||||
@@ -1,110 +1,136 @@
|
|||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
font-weight: 500;
|
background-color: #4A628A;
|
||||||
font-style: normal;
|
}
|
||||||
|
|
||||||
background-image: url('../assets/background.png');
|
h4, h2, h1{
|
||||||
background-size: cover;
|
margin: 0;
|
||||||
background-repeat: no-repeat;
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
opacity: 0;
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 100vh;
|
justify-content: space-evenly;
|
||||||
text-align: center; /* Center the text for all child elements */
|
height: 90%;
|
||||||
|
width: 35%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header{
|
||||||
color: #1B1A55;
|
|
||||||
font-size: 4vh;
|
|
||||||
text-transform: uppercase;
|
|
||||||
line-height: 2.5rem;
|
|
||||||
margin-bottom: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-form {
|
|
||||||
background-color: #535C91;
|
|
||||||
opacity: 71;
|
|
||||||
padding: 13vh 3vh 5vh;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-form-title {
|
|
||||||
margin-bottom: 5vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-form hr {
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-form-title h2 {
|
|
||||||
color: #FFFFFF;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 5vh;
|
padding-bottom: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-form-content {
|
.title{
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subheading{
|
||||||
|
padding-top: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
color: rgba(255,255,255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-form{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-direction: column;
|
||||||
justify-content: center;
|
align-items: center;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-form-content input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: 1px solid #ccc; /* Light gray border */
|
||||||
|
border-radius: 8px; /* Rounded corners */
|
||||||
|
font-size: 1em;
|
||||||
|
font-family: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #f9f9f9; /* Light background */
|
||||||
|
color: #333; /* Dark text color */
|
||||||
|
transition: border-color 0.3s ease, box-shadow 0.3s ease; /* Smooth transition for interaction */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Placeholder Text Styling */
|
||||||
|
.profile-form-content input::placeholder {
|
||||||
|
color: #aaa; /* Lighter color for placeholders */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus State Styling */
|
||||||
|
.profile-form-content input:focus {
|
||||||
|
outline: none; /* Remove default focus outline */
|
||||||
|
border-color: #4A628A; /* Blue border on focus */
|
||||||
|
box-shadow: 0 0 5px rgba(74, 98, 138, 0.5); /* Subtle shadow for focus */
|
||||||
|
background-color: #fff; /* Slightly brighter background on focus */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Field Hover Effect */
|
||||||
|
.profile-form-content input:hover {
|
||||||
|
border-color: #888; /* Darker gray border on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disabled Input Styling */
|
||||||
|
.profile-form-content input:disabled {
|
||||||
|
background-color: #e0e0e0; /* Light gray background for disabled input */
|
||||||
|
cursor: not-allowed; /* Show "not allowed" cursor */
|
||||||
|
opacity: 0.7; /* Slight transparency */
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-form-footer {
|
.profile-form-footer {
|
||||||
margin-top: 7vh;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
width: 100%;
|
||||||
justify-content: center;
|
flex-direction: column;
|
||||||
|
justify-content: space-between; /* Align buttons next to each other */
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px; /* Space between buttons */
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
/* Styling for buttons */
|
||||||
text-align: center;
|
.profile-form button {
|
||||||
color: white;
|
flex: 1; /* Ensure buttons are equal width */
|
||||||
font-weight: bold;
|
padding: 16px;
|
||||||
font-size: 1.2rem;
|
font-weight: 300;
|
||||||
width: 70%;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 0.7rem;
|
|
||||||
background-color: #1B1A55;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
font-weight: bold;
|
|
||||||
width: 35%;
|
|
||||||
font-size: 1rem;
|
|
||||||
padding: 10px;
|
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 8px;
|
||||||
margin-top: 1rem;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button[name="underline"] {
|
||||||
filter: brightness(85%);
|
background-color: transparent; /* No background */
|
||||||
|
border: none; /* Remove default button border */
|
||||||
|
color: #4A628A; /* Set text color */
|
||||||
|
font-size: 1em; /* Set font size */
|
||||||
|
cursor: pointer; /* Add pointer cursor for interactivity */
|
||||||
|
text-decoration: underline; /* Underline text */
|
||||||
|
padding: 0; /* Remove default padding */
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name="submit"] {
|
button[name="submit"] {
|
||||||
background-color: #F44336;
|
width: 100%;
|
||||||
color: white;
|
background-color: #4A628A; /* Blue background */
|
||||||
}
|
color: white; /* White text color */
|
||||||
|
|
||||||
button[name="login"] {
|
|
||||||
background-color: #2196F3;
|
|
||||||
color: white;
|
|
||||||
margin-right: 5rem;
|
|
||||||
}
|
}
|
||||||
@@ -1,110 +1,151 @@
|
|||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
font-weight: 500;
|
background-color: #4A628A;
|
||||||
font-style: normal;
|
}
|
||||||
|
|
||||||
background-image: url('../assets/background.png');
|
h4, h2, h1{
|
||||||
background-size: cover;
|
margin: 0;
|
||||||
background-repeat: no-repeat;
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
color: #1B1A55;
|
|
||||||
font-size: 4vh;
|
|
||||||
text-transform: uppercase;
|
|
||||||
line-height: 2.5rem;
|
|
||||||
margin-bottom: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form {
|
|
||||||
background-color: #535C91;
|
|
||||||
opacity: 71;
|
|
||||||
padding: 9vh 3vh 5vh;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form-title {
|
|
||||||
margin: 0 0 5vh 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form-title h2 {
|
|
||||||
color: #FFFFFF;
|
|
||||||
font-size: 5vh;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form hr {
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form-content {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
width: 70%;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 0.7rem;
|
|
||||||
background-color: #1B1A55;
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
button {
|
display: flex;
|
||||||
font-weight: bold;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 90%;
|
||||||
width: 35%;
|
width: 35%;
|
||||||
font-size: 1rem;
|
|
||||||
padding: 10px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-top: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
.header{
|
||||||
filter: brightness(85%);
|
display: flex;
|
||||||
|
width: 80%;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 20%;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subheading{
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
padding-top: 8px;
|
||||||
|
color: rgba(255,255,255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form{
|
||||||
|
height: 80%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-content input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: 1px solid #ccc; /* Light gray border */
|
||||||
|
border-radius: 8px; /* Rounded corners */
|
||||||
|
font-size: 1em;
|
||||||
|
font-family: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #f9f9f9; /* Light background */
|
||||||
|
color: #333; /* Dark text color */
|
||||||
|
transition: border-color 0.3s ease, box-shadow 0.3s ease; /* Smooth transition for interaction */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Placeholder Text Styling */
|
||||||
|
.login-form-content input::placeholder {
|
||||||
|
color: #aaa; /* Lighter color for placeholders */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus State Styling */
|
||||||
|
.login-form-content input:focus {
|
||||||
|
outline: none; /* Remove default focus outline */
|
||||||
|
border-color: #4A628A; /* Blue border on focus */
|
||||||
|
box-shadow: 0 0 5px rgba(74, 98, 138, 0.5); /* Subtle shadow for focus */
|
||||||
|
background-color: #fff; /* Slightly brighter background on focus */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Field Hover Effect */
|
||||||
|
.login-form-content input:hover {
|
||||||
|
border-color: #888; /* Darker gray border on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disabled Input Styling */
|
||||||
|
.login-form-content input:disabled {
|
||||||
|
background-color: #e0e0e0; /* Light gray background for disabled input */
|
||||||
|
cursor: not-allowed; /* Show "not allowed" cursor */
|
||||||
|
opacity: 0.7; /* Slight transparency */
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-form-footer {
|
.login-form-footer {
|
||||||
margin-top: 3vh;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
width: 100%;
|
||||||
align-content: center;
|
flex-direction: column;
|
||||||
|
justify-content: space-between; /* Align buttons next to each other */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-evenly;
|
gap: 10px; /* Space between buttons */
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling for buttons */
|
||||||
|
.login-form-footer button {
|
||||||
|
flex: 1; /* Ensure buttons are equal width */
|
||||||
|
padding: 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[name="underline"] {
|
||||||
|
background-color: transparent; /* No background */
|
||||||
|
border: none; /* Remove default button border */
|
||||||
|
color: #4A628A; /* Set text color */
|
||||||
|
font-size: 1em; /* Set font size */
|
||||||
|
cursor: pointer; /* Add pointer cursor for interactivity */
|
||||||
|
text-decoration: underline; /* Underline text */
|
||||||
|
padding: 0; /* Remove default padding */
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name="submit"] {
|
button[name="submit"] {
|
||||||
background-color: #F44336;
|
width: 100%;
|
||||||
color: white;
|
background-color: #4A628A; /* Blue background */
|
||||||
}
|
color: white; /* White text color */
|
||||||
|
|
||||||
button[name="signup"] {
|
|
||||||
background-color: #2196F3;
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
@@ -1,191 +1,162 @@
|
|||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
font-weight: 500;
|
background-color: #4A628A;
|
||||||
font-style: normal;
|
}
|
||||||
|
|
||||||
background-image: url('../assets/background.png');
|
h4, h2, h1{
|
||||||
background-size: cover;
|
margin: 0;
|
||||||
background-repeat: no-repeat;
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2 {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background-color: #535C91;
|
|
||||||
opacity: 71;
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_top {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_top_left {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: start;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_top_left hr {
|
|
||||||
margin: 0 0 1rem 0;
|
|
||||||
width: 45%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_top_right {
|
|
||||||
width: 10vw;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-content: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
padding: 0 2vh 0 2vh;
|
|
||||||
background-color: #1B1A55;
|
|
||||||
margin: 0 0 0 5vw;
|
|
||||||
border-radius: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_content {
|
|
||||||
display: flex;
|
|
||||||
justify-content: start;
|
|
||||||
align-items: center;
|
|
||||||
margin: 2rem 0 2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-content: center;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin: 0 1rem 0 1rem;
|
|
||||||
width: 10vw;
|
|
||||||
height: 5vh;
|
|
||||||
|
|
||||||
border: none;
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||||
color: white;
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
text-transform: uppercase;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
||||||
outline: none;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
filter: brightness(85%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_footer {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: end;
|
height: 90%;
|
||||||
|
width: 35%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right_block {
|
.header{
|
||||||
display: flex;
|
text-align: center;
|
||||||
flex-direction: column;
|
padding-bottom: 16px;
|
||||||
background-color: #535C91;
|
padding-top: 32px;
|
||||||
opacity: 71;
|
}
|
||||||
padding: 2rem 4rem 2rem 2rem;
|
|
||||||
border-radius: 1rem;
|
.title{
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
font-size: 48px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: -1px;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.choose_user_form_title {
|
.subheading{
|
||||||
display: flex;
|
padding-top: 8px;
|
||||||
flex-wrap: wrap;
|
font-size: 16px;
|
||||||
flex-direction: column;
|
letter-spacing: -0.5px;
|
||||||
justify-content: left;
|
color: rgba(255,255,255, 0.8);
|
||||||
align-content: start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.choose_user_form_title hr {
|
.form{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between; /* Align buttons next to each other */
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px; /* Space between buttons */
|
||||||
|
}
|
||||||
|
|
||||||
|
.filename-title{
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
color: white;
|
||||||
|
padding: 48px 24px 48px 24px;
|
||||||
|
background-color: rgba(0,0,0,0.25);
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-top: 16px;
|
||||||
|
box-shadow: 2px 2px 2px 1px rgb(0 0 0 / 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.users_block{
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label{
|
||||||
|
margin-top: 8px;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.choose_user_form_content {
|
.choose_user_form_content {
|
||||||
|
overflow-y: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 180px;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 1rem 7rem 2rem 0.5rem;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: start;
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
height: 20vh; /* Fixed height */
|
h5{
|
||||||
width: 100%; /* Full width */
|
font-size: 20px;
|
||||||
overflow: auto; /* Enable scrolling */
|
font-weight: 300;
|
||||||
|
text-align: center;
|
||||||
font-size: 1.2rem;
|
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.choose_user_form_content::-webkit-scrollbar {
|
/* Styling for buttons */
|
||||||
width: 10px; /* Reduced width of the scrollbar by 2px */
|
.form button {
|
||||||
|
flex: 1; /* Ensure buttons are equal width */
|
||||||
|
padding: 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.choose_user_form_content::-webkit-scrollbar-track {
|
button[name="underline"] {
|
||||||
background: #1B1A55; /* Updated track color */
|
background-color: transparent; /* No background */
|
||||||
}
|
border: none; /* Remove default button border */
|
||||||
|
color: #4A628A; /* Set text color */
|
||||||
.choose_user_form_content::-webkit-scrollbar-thumb {
|
font-size: 1em; /* Set font size */
|
||||||
background: #535C91; /* Updated handle color */
|
cursor: pointer; /* Add pointer cursor for interactivity */
|
||||||
border: 2px solid #1B1A55; /* Adding a border to effectively reduce the thumb size */
|
text-decoration: underline; /* Underline text */
|
||||||
}
|
padding: 0; /* Remove default padding */
|
||||||
|
|
||||||
.choose_user_form_content::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #555; /* Updated handle color on hover */
|
|
||||||
}
|
|
||||||
|
|
||||||
button[name="back"] {
|
|
||||||
background-color: #23BDEE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name="submit"] {
|
button[name="submit"] {
|
||||||
background-color: #F44336;
|
width: 100%;
|
||||||
|
background-color: #4A628A; /* Blue background */
|
||||||
|
color: white; /* White text color */
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name="select_file"] {
|
button[name="submit"]:hover {
|
||||||
margin: 0;
|
background-color: #3B5173; /* Darker background on hover */
|
||||||
width: 8vw;
|
}
|
||||||
background-color: #1B1A55;
|
|
||||||
|
.choose_user_form_content::-webkit-scrollbar {
|
||||||
|
width: 4px; /* Width of the scrollbar */
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose_user_form_content::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.1); /* Track background color */
|
||||||
|
border-radius: 10px; /* Rounded corners for the track */
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose_user_form_content::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(74, 98, 138, 0.8); /* Scrollbar handle color */
|
||||||
|
border-radius: 10px; /* Rounded corners for the scrollbar handle */
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose_user_form_content::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(74, 98, 138, 1); /* Darker color on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose_user_form_content:empty {
|
||||||
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
body, html {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: "Roboto", sans-serif;
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
background-image: url('../assets/background.png');
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
opacity: 0; /* Make steps invisible by default */
|
|
||||||
visibility: hidden; /* Hide steps by default */
|
|
||||||
transition: opacity 0.5s ease, visibility 0.5s ease; /* Smooth transition */
|
|
||||||
}
|
|
||||||
|
|
||||||
.step.active {
|
|
||||||
opacity: 1; /* Show the active step */
|
|
||||||
visibility: visible; /* Make it visible */
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
color: #1B1A55;
|
|
||||||
font-size: 4vh;
|
|
||||||
text-transform: uppercase;
|
|
||||||
line-height: 2.5rem;
|
|
||||||
margin-bottom: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-form {
|
|
||||||
background-color: #535C91;
|
|
||||||
opacity: 71;
|
|
||||||
padding: 9vh 3vh 5vh;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-form-title {
|
|
||||||
margin: 0 0 5vh 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-form-title h2 {
|
|
||||||
color: #FFFFFF;
|
|
||||||
font-size: 5vh;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-form hr {
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-form-content {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
width: 70%;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 0.7rem;
|
|
||||||
background-color: #1B1A55;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
font-weight: bold;
|
|
||||||
width: 35%;
|
|
||||||
font-size: 1rem;
|
|
||||||
padding: 10px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-top: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
filter: brightness(85%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-form-footer {
|
|
||||||
margin-top: 3vh;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-content: center;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
}
|
|
||||||
|
|
||||||
button[name="back"] {
|
|
||||||
background-color: #F44336;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
button[name="continue"] {
|
|
||||||
background-color: #2196F3;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
body, html {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
font-family: "Roboto", sans-serif;
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
|
|
||||||
background-image: url('../assets/background.png');
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
color: #1B1A55;
|
|
||||||
font-size: 4vh;
|
|
||||||
text-transform: uppercase;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-section {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border: 6px solid #f3f3f3;
|
|
||||||
border-top: 6px solid #2196F3;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-section p {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: white;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
/* Global styles for body and background */
|
|
||||||
body, html {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: "Roboto", sans-serif;
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
background-image: url('../assets/background.png');
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Container starts hidden */
|
|
||||||
.container {
|
|
||||||
opacity: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Container styling to match your previous design */
|
|
||||||
.user-management-container {
|
|
||||||
background-color: rgba(83, 92, 145, 0.71);
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 15px rgba(0, 0, 0, 0.2);
|
|
||||||
color: white;
|
|
||||||
width: 80%;
|
|
||||||
max-width: 1000px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* User card styling */
|
|
||||||
.user-card {
|
|
||||||
background-color: #fff;
|
|
||||||
color: #333;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 15px;
|
|
||||||
margin: 15px 0;
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-card:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* User name */
|
|
||||||
.user-name {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* User department */
|
|
||||||
.user-department {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: #555;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Delete button */
|
|
||||||
.delete-button {
|
|
||||||
padding: 10px 20px;
|
|
||||||
background-color: #f44336;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
transition: background-color 0.3s ease, box-shadow 0.3s ease;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-button:hover {
|
|
||||||
background-color: #d32f2f;
|
|
||||||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Footer buttons */
|
|
||||||
button {
|
|
||||||
margin: 0 1rem 0 1rem;
|
|
||||||
width: 10vw;
|
|
||||||
height: 5vh;
|
|
||||||
|
|
||||||
border: none;
|
|
||||||
border-radius: 10px;
|
|
||||||
|
|
||||||
color: white;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
text-transform: uppercase;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
||||||
outline: none;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
filter: brightness(85%);
|
|
||||||
}
|
|
||||||
|
|
||||||
button[name="back"] {
|
|
||||||
background-color: #23BDEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Footer class */
|
|
||||||
.footer {
|
|
||||||
margin-top: 20px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Scrollable user container */
|
|
||||||
#users-container {
|
|
||||||
max-height: 50vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#users-container::-webkit-scrollbar {
|
|
||||||
width: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#users-container::-webkit-scrollbar-track {
|
|
||||||
background: #1B1A55;
|
|
||||||
}
|
|
||||||
|
|
||||||
#users-container::-webkit-scrollbar-thumb {
|
|
||||||
background: #535C91;
|
|
||||||
border: 2px solid #1B1A55;
|
|
||||||
}
|
|
||||||
|
|
||||||
#users-container::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #555;
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
body, html {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
font-family: "Roboto", sans-serif;
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
|
|
||||||
background-image: url('../assets/background.png');
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
color: #1B1A55;
|
|
||||||
font-size: 4vh;
|
|
||||||
text-transform: uppercase;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-section {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border: 6px solid #f3f3f3;
|
|
||||||
border-top: 6px solid #2196F3;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-section p {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: white;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
|
<link href="../css/announcement.css" rel="stylesheet">
|
||||||
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<title>Send Announcement</title>
|
||||||
|
</head>
|
||||||
|
<body onload="fadeIn()">
|
||||||
|
<div class="page">
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1 class="title">TeamVault</h1>
|
||||||
|
<h4 class="subheading">Send an Important Announcement</h4>
|
||||||
|
</div>
|
||||||
|
<form class="announcement-form" id="announcementForm">
|
||||||
|
<textarea name="message" id="announcementMessage" placeholder="Type your announcement here..." rows="8"></textarea>
|
||||||
|
<div class="announcement-form-footer">
|
||||||
|
<button id="backButton" type="button">Back</button>
|
||||||
|
<button id="submitAnnouncement" type="button">Send Announcement</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../js/announcement.js"></script>
|
||||||
|
<script src="../js/helpers.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -4,10 +4,13 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
<!-- Reuse the same CSS files as UC Not Found -->
|
<!-- Reuse the same CSS files as UC Not Found -->
|
||||||
<link href="../css/welcome.css" rel="stylesheet">
|
<link href="../css/loading.css" rel="stylesheet">
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<script src="../js/helpers.js"></script>
|
<script src="../js/helpers.js"></script>
|
||||||
<title>Welcome</title>
|
<title>Welcome</title>
|
||||||
@@ -15,8 +18,9 @@
|
|||||||
<body onload="fadeIn()">
|
<body onload="fadeIn()">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>BACKUP FETCHER</h1>
|
<h1 class="title">Team Vault</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<img src="../assets/refresh-data.webp" alt="Backup Fetcher" class="backup-img">
|
||||||
<div class="loading-section">
|
<div class="loading-section">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<p>Backup is not searched, please wait...</p>
|
<p>Backup is not searched, please wait...</p>
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
|
||||||
|
|
||||||
<link href="../css/departments_management.css" rel="stylesheet">
|
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<script src="../js/departments_management.js"></script>
|
|
||||||
<script src="../js/helpers.js"></script>
|
|
||||||
|
|
||||||
<title>Manage Departments</title>
|
|
||||||
</head>
|
|
||||||
<body onload="fadeIn()">
|
|
||||||
<div class="container">
|
|
||||||
<div class="left_block">
|
|
||||||
<h1>Departments</h1>
|
|
||||||
<div id="departments-container">
|
|
||||||
<!-- Department divs will be dynamically inserted here -->
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
<button id="backButton" name="back" type="button">Back</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="right_block">
|
|
||||||
<h1>Create New Department</h1>
|
|
||||||
<form id="new-department-form">
|
|
||||||
<label for="departmentName">Department Name:</label>
|
|
||||||
<input type="text" id="departmentName" name="departmentName" placeholder="Enter department name" required>
|
|
||||||
<button type="submit" id="createDepartmentButton">Create Department</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Modal for modifying department -->
|
|
||||||
<div id="modify-department-modal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<span id="close-modal" class="close">×</span>
|
|
||||||
<h2>Modify Department</h2>
|
|
||||||
<label for="new-department-name">New Department Name:</label>
|
|
||||||
<input type="text" id="new-department-name" placeholder="Enter new department name" />
|
|
||||||
<button id="confirm-modify">Modify</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -4,32 +4,38 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
<link href="../css/login.css" rel="stylesheet">
|
<link href="../css/login.css" rel="stylesheet">
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<title>Login</title>
|
<title>Login</title>
|
||||||
</head>
|
</head>
|
||||||
<body onload="fadeIn()">
|
<body onload="fadeIn()">
|
||||||
|
<div class="page">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>DO WE KNOW</h1>
|
<h1 class="title">TeamVault</h1>
|
||||||
<h1>EACH OTHER?</h1>
|
<h4 class="subheading">Do we know each other?</h4>
|
||||||
</div>
|
</div>
|
||||||
<form class="login-form" id="loginForm">
|
<form class="login-form" id="loginForm">
|
||||||
<div class="login-form-title">
|
<div class="login-form-title">
|
||||||
<h2>Login</h2>
|
<h2 class="login-title">Login</h2>
|
||||||
<hr>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="login-form-content">
|
<div class="login-form-content">
|
||||||
<input name="email" placeholder="Email" type="email">
|
<input name="email" placeholder="Email" type="email">
|
||||||
<input name="password" placeholder="Password" type="password">
|
<input name="password" placeholder="Password" type="password">
|
||||||
</div>
|
</div>
|
||||||
<div class="login-form-footer">
|
<div class="login-form-footer">
|
||||||
|
<div>
|
||||||
|
<button id="resetPassword" name="underline" type="button">Reset Password</button>
|
||||||
|
</div>
|
||||||
<button id="submit" name="submit" type="submit">Submit</button>
|
<button id="submit" name="submit" type="submit">Submit</button>
|
||||||
<button id="resetPassword" name="resetPassword" type="button">Reset Password</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="../js/login.js"></script>
|
<script src="../js/login.js"></script>
|
||||||
<script src="../js/helpers.js"></script>
|
<script src="../js/helpers.js"></script>
|
||||||
|
|||||||
@@ -1,74 +1,65 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
|
<link href="../css/main_menu.css" rel="stylesheet">
|
||||||
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<link href="../css/main_menu.css" rel="stylesheet">
|
<script src="../js/main_menu.js"></script>
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
<script src="../js/helpers.js"></script>
|
||||||
|
|
||||||
<script src="../js/main_menu.js"></script>
|
<title>Main Page</title>
|
||||||
<script src="../js/helpers.js"></script>
|
|
||||||
|
|
||||||
<title>Main Page</title>
|
</head>
|
||||||
|
<body onload="initialSetup(); fadeIn()">
|
||||||
</head>
|
<div class="page">
|
||||||
<body onload="fadeIn()">
|
|
||||||
<div id="disclaimer-modal" class="overlay">
|
|
||||||
<div class="ceo-validation-form">
|
|
||||||
<h2>Are you sure?</h2>
|
|
||||||
<p>This operation cannot be reverted. Do you want to proceed with resetting the database?</p>
|
|
||||||
<div class="form-actions">
|
|
||||||
<button id="disclaimer-cancel" class="cancel-button">Cancel</button>
|
|
||||||
<button id="disclaimer-submit" class="submit-button">Yes, Reset</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="left_block">
|
<div class="left_block">
|
||||||
<div class="left_block_top">
|
<div class="notification-title">
|
||||||
<div>
|
<h1 class="notif-title">Notifications</h1>
|
||||||
<h1>WELCOME BACK,</h1>
|
|
||||||
<h1 id="username-field"></h1>
|
|
||||||
<h2>Hope you have a productive day!</h2>
|
|
||||||
</div>
|
|
||||||
<img alt="" src="../assets/user_1144760.png">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="left_block_content">
|
<div class=left-content>
|
||||||
<div class="left_block_buttons">
|
<div class="notifications" id="notifications">
|
||||||
<button id="change-info" name="menu_button">Change your info</button>
|
<!--Notifications are generated here-->
|
||||||
<button id="share-file" name="menu_button">Share a file</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="left_block_buttons">
|
<div class="left_block_footer">
|
||||||
<button id="backup-dir" name="menu_button">Set backup directory</button>
|
<button class="logout-button" id="logout" name="logout">Logout</button>
|
||||||
<button id="department-dir" name="menu_button">Set department directory</button>
|
|
||||||
<button id="share-dir" name="menu_button">Set share directory</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="left_block_buttons">
|
|
||||||
<button id="manage-users" name="menu_button">Manage users</button>
|
|
||||||
<button id="manage-departments" name="menu_button">Manage departments</button>
|
|
||||||
</div>
|
|
||||||
<div class="left_block_buttons">
|
|
||||||
<button id="reset-database" name="menu_button">Reset database</button>
|
|
||||||
</div>
|
|
||||||
<div class="left_block_buttons">
|
|
||||||
<button id="restore-backup" name="menu_button">Restore backup</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="left_block_footer">
|
|
||||||
<button id="logout" name="logout">Logout</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right_block">
|
|
||||||
<div>
|
|
||||||
<h1>NOTIFICATIONS</h1>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<div class="notifications" id="notifications">
|
|
||||||
|
|
||||||
|
<div class="right_block">
|
||||||
|
<div class="navbar">
|
||||||
|
<div class="welcome">
|
||||||
|
<img alt="" src="../assets/user_1144760.png">
|
||||||
|
<h3 class="user">Welcome Back, </h3>
|
||||||
|
<h3 class="user" id="username-field"></h3>
|
||||||
|
<h3 class="user">!</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<h1 class="title">TeamVault</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="right_block_content">
|
||||||
|
<button id="change-info" name="menu_button">Change your info</button>
|
||||||
|
<button id="share-file" name="menu_button">Share a file</button>
|
||||||
|
<button id="send-announcement" name="menu_button">Send an announcement</button>
|
||||||
|
<button id="backup-dir" name="menu_button">Set backup directory</button>
|
||||||
|
<button id="share-dir" name="menu_button">Set share directory</button>
|
||||||
|
<button id="overview-departments" name="menu_button">Overview departments</button>
|
||||||
|
<button id="overview-users" name="menu_button">Overview users</button>
|
||||||
|
<button id="reset-database" name="menu_button">Reset database</button>
|
||||||
|
<button id="restore-backup" name="menu_button">Restore backup</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</div>
|
||||||
</html>
|
</body>
|
||||||
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
|
<link href="../css/overview_departments.css" rel="stylesheet">
|
||||||
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<script src="../js/helpers.js"></script>
|
||||||
|
<script src="../js/overview_departments.js"></script>
|
||||||
|
|
||||||
|
<title>Manage Departments</title>
|
||||||
|
</head>
|
||||||
|
<body onload="fetchDepartments(); fadeIn()">
|
||||||
|
<div class="page">
|
||||||
|
<div class="container">
|
||||||
|
<div class="user-management-container">
|
||||||
|
<h1 class="title">Manage Departments</h1>
|
||||||
|
<div id="departments-container" class="departments-container">
|
||||||
|
<!-- Department divs will be dynamically inserted here -->
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<button id="backButton" name="underline" type="button">Back</button>
|
||||||
|
<button id="createDepartmentButton" name="submit" type="button">Create</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Create Department Modal -->
|
||||||
|
<div id="createDepartmentModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span id="closeCreateModal" class="close-button">×</span>
|
||||||
|
<h2 class="title" style="color: black; margin-bottom: 16px;">Create Department</h2>
|
||||||
|
<input type="text" id="newDepartmentName" placeholder="Department Name" />
|
||||||
|
<button id="confirmCreateButton" name="submit" type="button"
|
||||||
|
style="width: 30%;"
|
||||||
|
>Create</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modify Department Modal -->
|
||||||
|
<div id="modifyDepartmentModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span id="closeModifyModal" class="close-button">×</span>
|
||||||
|
<h2 class="title" style="color: black; margin-bottom: 16px;">Modify Department</h2>
|
||||||
|
<input type="text" id="modifiedDepartmentName" placeholder="New Department Name" />
|
||||||
|
<button id="confirmModifyButton" name="submit" type="button"
|
||||||
|
style="width: 30%;"
|
||||||
|
>Modify</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
|
<link href="../css/overview_users.css" rel="stylesheet">
|
||||||
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<script src="../js/helpers.js"></script>
|
||||||
|
<script src="../js/overview_users.js"></script>
|
||||||
|
|
||||||
|
<title>Manage Users</title>
|
||||||
|
</head>
|
||||||
|
<body onload="fetchData(); fadeIn()">
|
||||||
|
<div class="page">
|
||||||
|
<div class="container">
|
||||||
|
<div class="user-management-container">
|
||||||
|
<h1 class="title">Manage Users</h1>
|
||||||
|
<div id="users-container" class="users-container">
|
||||||
|
<!-- User divs will be dynamically inserted here -->
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<button id="backButton" name="underline" type="button">Back</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -4,9 +4,12 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
<link href="../css/profile.css" rel="stylesheet">
|
<link href="../css/profile.css" rel="stylesheet">
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<script src="../js/profile.js"></script>
|
<script src="../js/profile.js"></script>
|
||||||
<script src="../js/helpers.js"></script>
|
<script src="../js/helpers.js"></script>
|
||||||
@@ -14,22 +17,24 @@
|
|||||||
<title>Profile</title>
|
<title>Profile</title>
|
||||||
</head>
|
</head>
|
||||||
<body onload="fadeIn()">
|
<body onload="fadeIn()">
|
||||||
<div class="container">
|
<div class="page">
|
||||||
<form class="profile-form" id="profileForm">
|
<div class="container">
|
||||||
<div class="profile-form-title">
|
<form class="profile-form" id="profileForm">
|
||||||
<h2>Profile</h2>
|
<div class="header">
|
||||||
<hr>
|
<h1 class="title">Profile</h1>
|
||||||
</div>
|
<h3 class="subheading">Who are you?</h3>
|
||||||
<div class="profile-form-content">
|
</div>
|
||||||
<input name="email" placeholder="Email" type="email">
|
<div class="profile-form-content">
|
||||||
<input name="username" placeholder="Username" type="text">
|
<input name="email" placeholder="Email" type="email">
|
||||||
<input name="password" placeholder="Password" type="password">
|
<input name="username" placeholder="Username" type="text">
|
||||||
</div>
|
<input name="password" placeholder="Password" type="password">
|
||||||
<div class="profile-form-footer">
|
</div>
|
||||||
<button name="login" type="button">Back</button>
|
<div class="profile-form-footer">
|
||||||
<button name="submit" type="submit">Submit</button>
|
<button id="back" name="underline" type="button">Back</button>
|
||||||
</div>
|
<button id="submit" name="submit" type="submit">Submit</button>
|
||||||
</form>
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
|
<link href="../css/loading.css" rel="stylesheet">
|
||||||
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<script src="../js/helpers.js"></script>
|
||||||
|
<script src="../js/reset_database.js"></script>
|
||||||
|
<title>UC Not Found</title>
|
||||||
|
</head>
|
||||||
|
<body onload="fadeIn()">
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1 class="title">TeamVault</h1>
|
||||||
|
</div>
|
||||||
|
<img src="../assets/2581896.png" style="margin-left: 20px" alt="Connecting to server" class="server-img">
|
||||||
|
<div class="loading-section">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>Please wait while we reset the database.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -3,33 +3,37 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
<link href="../css/reset_password.css" rel="stylesheet">
|
<link href="../css/reset_password.css" rel="stylesheet">
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<title>Reset Password</title>
|
<title>Reset Password</title>
|
||||||
</head>
|
</head>
|
||||||
<body onload="fadeIn()">
|
<body onload="fadeIn()">
|
||||||
|
<div class="page">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>RESET</h1>
|
<h1 class="title">TeamVault</h1>
|
||||||
<h1>YOUR PASSWORD</h1>
|
<h3 class="subheading">Forgot your password? No worries, it happens to the best of us!</h3>
|
||||||
</div>
|
</div>
|
||||||
<form class="login-form" id="resetPasswordForm">
|
<form class="login-form" id="resetPasswordForm">
|
||||||
<div class="login-form-title">
|
<div class="login-form-title">
|
||||||
<h2>Reset Password</h2>
|
<h2 class="login-title">Reset Password</h2>
|
||||||
<hr>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="login-form-content">
|
<div class="login-form-content">
|
||||||
<input name="email" placeholder="Email" type="email" required>
|
<input name="email" placeholder="Email" type="email" required>
|
||||||
<input name="newPassword" placeholder="New Password" type="password" required>
|
<input name="newPassword" placeholder="New Password" type="password" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="login-form-footer">
|
<div class="login-form-footer">
|
||||||
<button id="backToLogin" name="backToLogin" type="button">Back to Login</button>
|
<button id="backToLogin" name="underline" type="button">Back to Login</button>
|
||||||
<button id="resetPassword" name="resetPassword" type="submit">Reset Password</button>
|
<button id="resetPassword" name="submit" type="submit">Reset Password</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<script src="../js/reset_password.js"></script>
|
<script src="../js/reset_password.js"></script>
|
||||||
<script src="../js/helpers.js"></script>
|
<script src="../js/helpers.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
|
<link href="../css/loading.css" rel="stylesheet">
|
||||||
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<script src="../js/helpers.js"></script>
|
||||||
|
|
||||||
|
<title>Sending Announcement</title>
|
||||||
|
</head>
|
||||||
|
<body onload="fadeIn()">
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1 class="title">TeamVault</h1>
|
||||||
|
</div>
|
||||||
|
<img src="../assets/mail_2772213.png" alt="Sending announcement" class="announcement-img">
|
||||||
|
<div class="loading-section">
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<p>Sending announcement to all users. Please wait...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -4,46 +4,42 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
<link href="../css/share_file.css" rel="stylesheet">
|
<link href="../css/share_file.css" rel="stylesheet">
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<script src="../js/share_file.js"></script>
|
<script src="../js/share_file.js"></script>
|
||||||
<script src="../js/helpers.js"></script>
|
<script src="../js/helpers.js"></script>
|
||||||
|
|
||||||
<title>Share File</title>
|
<title>Share File</title>
|
||||||
</head>
|
</head>
|
||||||
<body onload="fadeIn()">
|
<body onload="fetchUsersAndCreateCheckboxes(); updateFileName(); fadeIn()">
|
||||||
|
<div class="page">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="left_block">
|
<div class="form">
|
||||||
<div class="left_block_top">
|
<div class="header">
|
||||||
<div class="left_block_top_left">
|
<h1 class="title">Share a File</h1>
|
||||||
<h1>SHARE A FILE</h1>
|
<h3 class="subheading">First select the file you want to share.</h3>
|
||||||
<hr>
|
<button class="filename-title" id="selectFile" name="submit" type="button">Select</button>
|
||||||
<h2>First select the file you want to share</h2>
|
<form class="users_block" id="userDestForm">
|
||||||
<h2>(can be whatever resource from the system)</h2>
|
<h5>Users</h5>
|
||||||
</div>
|
<h3 class="subheading">Select the users you want to share the file with.</h3>
|
||||||
<div class="left_block_top_right">
|
<div id="choose_user_form_content" class="choose_user_form_content">
|
||||||
<h2 id="fileName"></h2>
|
<!-- Insert the users from the database -->
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="left_block_content">
|
|
||||||
<button id="selectFile" name="select_file" type="button">Select</button>
|
|
||||||
</div>
|
<div class="footer">
|
||||||
<div class="left_block_footer">
|
<button id="backButton" name="underline" type="button">Back</button>
|
||||||
<button id="backButton" name="back" type="button">Back</button>
|
|
||||||
<button id="submitButton" name="submit" type="submit">Submit</button>
|
<button id="submitButton" name="submit" type="submit">Submit</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form class="right_block" id="userDestForm">
|
</div>
|
||||||
<div class="choose_user_form_title">
|
|
||||||
<h1>USERS</h1>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<div class="choose_user_form_content">
|
|
||||||
<!-- Insert the users from the database -->
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
|
||||||
<link href="../css/sign_up.css" rel="stylesheet">
|
|
||||||
<title>Signup Process</title>
|
|
||||||
</head>
|
|
||||||
<body onload="fadeIn()">
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<!-- Step 1: Profile Information -->
|
|
||||||
<div class="step active" id="step1">
|
|
||||||
<div class="header">
|
|
||||||
<h1>LET US MEET</h1>
|
|
||||||
<h1>EACH OTHER</h1>
|
|
||||||
</div>
|
|
||||||
<form class="signup-form" id="signupForm">
|
|
||||||
<div class="signup-form-title">
|
|
||||||
<h2>Sign Up</h2>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<div class="signup-form-content">
|
|
||||||
<input name="email" placeholder="Email" type="email" required>
|
|
||||||
<input name="name" placeholder="Username" type="text" required>
|
|
||||||
<input name="password" placeholder="Password" type="password" required>
|
|
||||||
</div>
|
|
||||||
<div class="signup-form-footer">
|
|
||||||
<button id="backToLogin" name="back" type="button">Cancel</button>
|
|
||||||
<button id="nextToStep2" name="continue" type="submit">Next</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Step 2: Department Selection -->
|
|
||||||
<div class="step" id="step2" style="display: none;">
|
|
||||||
<div class="header">
|
|
||||||
<h1>TELL ME MORE</h1>
|
|
||||||
<h1>ABOUT</h1>
|
|
||||||
<h1>YOUR WORK</h1>
|
|
||||||
</div>
|
|
||||||
<form class="signup-form" id="departmentForm">
|
|
||||||
<div class="signup-form-title">
|
|
||||||
<h2>Choose your department</h2>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<div class="signup-form-content" id="departmentList">
|
|
||||||
<!-- List of departments will be dynamically inserted here -->
|
|
||||||
</div>
|
|
||||||
<div class="signup-form-footer">
|
|
||||||
<button id="backToStep1" name="back" type="button">Back</button>
|
|
||||||
<button id="submitSignup" name="continue" type="submit">Submit</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="../js/sign_up.js"></script>
|
|
||||||
<script src="../js/helpers.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -4,9 +4,12 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
<link href="../css/uc_not_found.css" rel="stylesheet">
|
<link href="../css/loading.css" rel="stylesheet">
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<script src="../js/helpers.js"></script>
|
<script src="../js/helpers.js"></script>
|
||||||
|
|
||||||
@@ -15,9 +18,9 @@
|
|||||||
<body onload="fadeIn()">
|
<body onload="fadeIn()">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>SEARCHING FOR THE</h1>
|
<h1 class="title">TeamVault</h1>
|
||||||
<h1>SERVER...</h1>
|
|
||||||
</div>
|
</div>
|
||||||
|
<img src="../assets/cloud-computing.webp" alt="Connecting to server" class="server-img">
|
||||||
<div class="loading-section">
|
<div class="loading-section">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<p>Please wait while we try to connect to the server.</p>
|
<p>Please wait while we try to connect to the server.</p>
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
|
||||||
|
|
||||||
<link href="../css/users_management.css" rel="stylesheet">
|
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<script src="../js/helpers.js"></script>
|
|
||||||
<script src="../js/users_management.js"></script>
|
|
||||||
|
|
||||||
<title>Manage Users</title>
|
|
||||||
</head>
|
|
||||||
<body onload="fadeIn()">
|
|
||||||
<div class="container">
|
|
||||||
<div class="user-management-container">
|
|
||||||
<h1>Manage Users</h1>
|
|
||||||
<div id="users-container">
|
|
||||||
<!-- User divs will be dynamically inserted here -->
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
<button id="backButton" name="back" type="button">Back</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -4,10 +4,13 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
<!-- Reuse the same CSS files as UC Not Found -->
|
<!-- Reuse the same CSS files as UC Not Found -->
|
||||||
<link href="../css/welcome.css" rel="stylesheet">
|
<link href="../css/loading.css" rel="stylesheet">
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<script src="../js/helpers.js"></script>
|
<script src="../js/helpers.js"></script>
|
||||||
<title>Welcome</title>
|
<title>Welcome</title>
|
||||||
@@ -15,7 +18,7 @@
|
|||||||
<body onload="fadeIn()">
|
<body onload="fadeIn()">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>WELCOME TO THE APP</h1>
|
<h1 class="title">Welcome to TeamVault</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="loading-section">
|
<div class="loading-section">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const submitButton = document.getElementById('submitAnnouncement');
|
||||||
|
const buttonBack = document.getElementById('backButton');
|
||||||
|
const announcementTextarea = document.getElementById('announcementMessage');
|
||||||
|
|
||||||
|
buttonBack.addEventListener('click', function () {
|
||||||
|
fadeOut('main_menu');
|
||||||
|
});
|
||||||
|
|
||||||
|
submitButton.addEventListener('click', async function () {
|
||||||
|
const message = announcementTextarea.value.trim();
|
||||||
|
|
||||||
|
if (message === '') {
|
||||||
|
await window.electronAPI.showAlert('Please enter a message before sending.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the announcement message via the electronAPI
|
||||||
|
window.electronAPI.startAnnouncementWorker(message);
|
||||||
|
fadeOut('send_announcement');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
document.addEventListener('DOMContentLoaded', async () => {
|
|
||||||
const backButton = document.getElementById('backButton');
|
|
||||||
const form = document.getElementById('new-department-form');
|
|
||||||
const modifyModal = document.getElementById('modify-department-modal');
|
|
||||||
const closeModal = document.getElementById('close-modal');
|
|
||||||
const confirmModifyButton = document.getElementById('confirm-modify');
|
|
||||||
let currentDepartmentId = null;
|
|
||||||
|
|
||||||
// Retrieve operation codes via IPC
|
|
||||||
const operationCodes = await window.electronAPI.getOperationsCodes();
|
|
||||||
if (!operationCodes) {
|
|
||||||
await window.electronAPI.showAlert('Internal error of the application.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open the TCP socket right after fetching the operation codes
|
|
||||||
if (!await window.electronAPI.openUcSocket()) {
|
|
||||||
await window.electronAPI.showAlert('Failed to open socket. Internal error of the application.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let codeGetDepartments = operationCodes.GET_DEPARTMENTS;
|
|
||||||
let codeCreateDepartment = operationCodes.CREATE_DEPARTMENT;
|
|
||||||
let codeModifyDepartment = operationCodes.MODIFY_DEPARTMENT;
|
|
||||||
let codeDeleteDepartment = operationCodes.DELETE_DEPARTMENT;
|
|
||||||
let codeOk = operationCodes.OK;
|
|
||||||
|
|
||||||
// Set up the back button - close the socket before going back
|
|
||||||
backButton.addEventListener('click', async () => {
|
|
||||||
await window.electronAPI.closeUcSocket(); // Close the socket when going back
|
|
||||||
fadeOut('main_menu'); // Navigate to the main menu
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set up the form for creating new departments
|
|
||||||
form.addEventListener('submit', async function (event) {
|
|
||||||
event.preventDefault(); // Prevent the form from refreshing the page
|
|
||||||
const departmentName = document.getElementById('departmentName').value;
|
|
||||||
|
|
||||||
if (departmentName.trim()) {
|
|
||||||
await createDepartment(departmentName); // Call createDepartment function when the form is submitted
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch the departments on page load
|
|
||||||
await fetchDepartments();
|
|
||||||
|
|
||||||
// Fetch departments and render them in the left block
|
|
||||||
async function fetchDepartments() {
|
|
||||||
const departmentsContainer = document.getElementById('departments-container');
|
|
||||||
|
|
||||||
// Send the request to get the departments
|
|
||||||
if (!await window.electronAPI.sendUcMessage(codeGetDepartments)) {
|
|
||||||
await window.electronAPI.showAlert("Failed to send request.");
|
|
||||||
await redirectToMainMenu();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await waitForResponse();
|
|
||||||
if (!response || response.operationCode !== codeOk) {
|
|
||||||
await window.electronAPI.showAlert("Unable to fetch departments.");
|
|
||||||
await redirectToMainMenu();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
departmentsContainer.innerHTML = ''; // Clear any existing data
|
|
||||||
|
|
||||||
const departments = response.metaInfo.departments;
|
|
||||||
|
|
||||||
// Filter out CEO department
|
|
||||||
const filteredDepartments = departments.filter(department => department.name.toLowerCase() !== 'ceo');
|
|
||||||
|
|
||||||
// Check if there are no departments after filtering
|
|
||||||
if (!filteredDepartments.length) {
|
|
||||||
const noDepartmentsMessage = document.createElement('p');
|
|
||||||
noDepartmentsMessage.textContent = 'No departments exist.';
|
|
||||||
noDepartmentsMessage.classList.add('no-departments-message'); // Add a class for styling
|
|
||||||
departmentsContainer.appendChild(noDepartmentsMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over the remaining departments and render them
|
|
||||||
filteredDepartments.forEach(department => {
|
|
||||||
const departmentDiv = document.createElement('div');
|
|
||||||
departmentDiv.classList.add('department-card');
|
|
||||||
|
|
||||||
// Create department name div
|
|
||||||
const nameDiv = document.createElement('div');
|
|
||||||
nameDiv.classList.add('department-name');
|
|
||||||
nameDiv.textContent = department.name;
|
|
||||||
|
|
||||||
// Create modify button
|
|
||||||
const modifyButton = document.createElement('button');
|
|
||||||
modifyButton.textContent = 'Modify';
|
|
||||||
modifyButton.classList.add('modify-button');
|
|
||||||
modifyButton.dataset.departmentId = department.id;
|
|
||||||
|
|
||||||
// Create delete button
|
|
||||||
const deleteButton = document.createElement('button');
|
|
||||||
deleteButton.textContent = 'Delete';
|
|
||||||
deleteButton.classList.add('delete-button');
|
|
||||||
deleteButton.dataset.departmentId = department.id;
|
|
||||||
|
|
||||||
// Append department info and buttons to the department div
|
|
||||||
departmentDiv.appendChild(nameDiv);
|
|
||||||
departmentDiv.appendChild(modifyButton);
|
|
||||||
departmentDiv.appendChild(deleteButton);
|
|
||||||
|
|
||||||
// Append department div to the container
|
|
||||||
departmentsContainer.appendChild(departmentDiv);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add click event listeners for all dynamically created buttons
|
|
||||||
addEventListenersToButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add event listeners to dynamically created buttons
|
|
||||||
function addEventListenersToButtons() {
|
|
||||||
const modifyButtons = document.querySelectorAll('.modify-button');
|
|
||||||
const deleteButtons = document.querySelectorAll('.delete-button');
|
|
||||||
|
|
||||||
modifyButtons.forEach(button => {
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
currentDepartmentId = button.dataset.departmentId;
|
|
||||||
openModifyModal(); // Open the modal when modify button is clicked
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
deleteButtons.forEach(button => {
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
const departmentId = button.dataset.departmentId;
|
|
||||||
deleteDepartment(departmentId);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open the modify modal
|
|
||||||
function openModifyModal() {
|
|
||||||
modifyModal.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the modify modal
|
|
||||||
function closeModifyModal() {
|
|
||||||
modifyModal.style.display = 'none';
|
|
||||||
currentDepartmentId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle modify modal close event
|
|
||||||
closeModal.addEventListener('click', closeModifyModal);
|
|
||||||
|
|
||||||
// Handle department modification
|
|
||||||
confirmModifyButton.addEventListener('click', async () => {
|
|
||||||
const newDepartmentName = document.getElementById('new-department-name').value;
|
|
||||||
if (newDepartmentName.trim() && currentDepartmentId) {
|
|
||||||
await modifyDepartment(currentDepartmentId, newDepartmentName);
|
|
||||||
closeModifyModal(); // Close the modal after modification
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function modifyDepartment(departmentId, newName) {
|
|
||||||
if (!await window.electronAPI.sendUcMessage(codeModifyDepartment, {
|
|
||||||
departmentId: departmentId,
|
|
||||||
newDepartmentName: newName
|
|
||||||
})) {
|
|
||||||
await window.electronAPI.showAlert('Failed to send modification request.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await waitForResponse();
|
|
||||||
if (response && response.operationCode === codeOk) {
|
|
||||||
await window.electronAPI.showAlert(`Department modified: ${newName}`);
|
|
||||||
await fetchDepartments(); // Refresh the department list after modification
|
|
||||||
} else {
|
|
||||||
await window.electronAPI.showAlert('Failed to modify the department.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteDepartment(departmentId) {
|
|
||||||
const confirmDelete = confirm("Are you sure you want to delete this department?");
|
|
||||||
if (confirmDelete) {
|
|
||||||
if (!await window.electronAPI.sendUcMessage(codeDeleteDepartment, { departmentId })) {
|
|
||||||
await window.electronAPI.showAlert('Failed to send delete request.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await waitForResponse();
|
|
||||||
if (response && response.operationCode === codeOk) {
|
|
||||||
await window.electronAPI.showAlert("Department deleted successfully.");
|
|
||||||
await fetchDepartments(); // Refresh the department list after deletion
|
|
||||||
} else {
|
|
||||||
await window.electronAPI.showAlert("Failed to delete the department.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to create a new department
|
|
||||||
async function createDepartment(departmentName) {
|
|
||||||
if (!await window.electronAPI.sendUcMessage(codeCreateDepartment, { departmentName })) {
|
|
||||||
await window.electronAPI.showAlert('Failed to send create request.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await waitForResponse();
|
|
||||||
if (response && response.operationCode === codeOk) {
|
|
||||||
await window.electronAPI.showAlert(`New department created: ${departmentName}`);
|
|
||||||
await fetchDepartments(); // Refresh the department list
|
|
||||||
} else {
|
|
||||||
await window.electronAPI.showAlert("Failed to create department.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect to main menu
|
|
||||||
async function redirectToMainMenu() {
|
|
||||||
await window.electronAPI.closeUcSocket();
|
|
||||||
fadeOut('main_menu');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -24,12 +24,24 @@ function fadeOut(destination) {
|
|||||||
async function waitForResponse() {
|
async function waitForResponse() {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const idResponseCheck = setInterval(async () => {
|
const idResponseCheck = setInterval(async () => {
|
||||||
const status = await window.electronAPI.hasResponseArrived();
|
if (await window.electronAPI.hasResponseArrived()) {
|
||||||
if (status) {
|
|
||||||
clearInterval(idResponseCheck);
|
clearInterval(idResponseCheck);
|
||||||
const response = await window.electronAPI.getLastUcResult();
|
resolve(await window.electronAPI.getLastUcResult()); // Resolve the response or null if not available
|
||||||
resolve(response || null); // Resolve the response or null if not available
|
|
||||||
}
|
}
|
||||||
}, 100); // Check every 100 milliseconds if the response has arrived
|
}, 100); // Check every 100 milliseconds if the response has arrived
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleScroll(idComponent, scrollHeight = 180) {
|
||||||
|
const element = document.getElementById(idComponent); // Using getElementById
|
||||||
|
if (!element) {
|
||||||
|
console.error(`Element with ID '${idComponent}' not found.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (element.scrollHeight > scrollHeight) {
|
||||||
|
element.style.overflowY = 'auto'; // Enable scroll if content overflows
|
||||||
|
} else {
|
||||||
|
element.style.overflowY = 'hidden'; // Disable scroll if content fits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
let codeLogin = '';
|
||||||
|
let codeFindByEmail = '';
|
||||||
|
let codeFindKeyByUser = '';
|
||||||
|
let codeOk = '';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async function () {
|
document.addEventListener('DOMContentLoaded', async function () {
|
||||||
const submitButton = document.getElementById('submit');
|
const submitButton = document.getElementById('submit');
|
||||||
const resetPasswordButton = document.getElementById('resetPassword');
|
const resetPasswordButton = document.getElementById('resetPassword');
|
||||||
@@ -9,10 +14,10 @@ document.addEventListener('DOMContentLoaded', async function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const codeLogin = operationCodes.LOGIN;
|
codeLogin = operationCodes.LOGIN;
|
||||||
const codeFindByEmail = operationCodes.FIND_BY_EMAIL;
|
codeFindByEmail = operationCodes.FIND_BY_EMAIL;
|
||||||
const codeFindKeyByUser = operationCodes.FIND_KEY_BY_USER_ID;
|
codeFindKeyByUser = operationCodes.FIND_KEY_BY_USER_ID;
|
||||||
const codeOk = operationCodes.OK;
|
codeOk = operationCodes.OK;
|
||||||
|
|
||||||
resetPasswordButton.addEventListener('click', function (e) {
|
resetPasswordButton.addEventListener('click', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -66,79 +71,88 @@ document.addEventListener('DOMContentLoaded', async function () {
|
|||||||
await window.electronAPI.closeUcSocket();
|
await window.electronAPI.closeUcSocket();
|
||||||
await window.electronAPI.changeContent('main_menu');
|
await window.electronAPI.changeContent('main_menu');
|
||||||
});
|
});
|
||||||
|
|
||||||
async function attemptLogin(email, password) {
|
|
||||||
const app_type = await window.electronAPI.readUserConfig('app_type');
|
|
||||||
const messageData = { email, password, app_type };
|
|
||||||
|
|
||||||
// Send the login message to the server
|
|
||||||
if (!await window.electronAPI.sendUcMessage(codeLogin, messageData)) {
|
|
||||||
await window.electronAPI.showAlert('Failed to send login request.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the response using waitForResponse
|
|
||||||
const response = await waitForResponse();
|
|
||||||
if (response && response.operationCode === codeOk) {
|
|
||||||
await window.electronAPI.resetApplicationInfo();
|
|
||||||
await window.electronAPI.resetUserConfig();
|
|
||||||
await window.electronAPI.writeUserConfig('app_type', app_type);
|
|
||||||
await window.electronAPI.writeUserConfig('user_info', { email, password });
|
|
||||||
return true;
|
|
||||||
}else if(response && respone.operationCode !== codeOk){
|
|
||||||
await window.electronAPI.showAlert(response.metaInfo.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await window.electronAPI.showAlert('Invalid login response received.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchAndStoreUserInfo(userEmail) {
|
|
||||||
if (!await window.electronAPI.sendUcMessage(codeFindByEmail, { email: userEmail })) {
|
|
||||||
await window.electronAPI.showAlert('Failed to send request to fetch user info.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the response using waitForResponse
|
|
||||||
const response = await waitForResponse();
|
|
||||||
if (response && response.operationCode === codeOk) {
|
|
||||||
let userInfo = await window.electronAPI.readUserConfig('user_info');
|
|
||||||
if (!userInfo) {
|
|
||||||
userInfo = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
userInfo.id = response.metaInfo.id;
|
|
||||||
userInfo.departmentId = response.metaInfo.departmentId;
|
|
||||||
userInfo.name = response.metaInfo.name || 'User';
|
|
||||||
|
|
||||||
await window.electronAPI.writeUserConfig('user_info', userInfo);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await window.electronAPI.showAlert('Failed to fetch user info from server.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchAndStoreEncryptionKey(userId) {
|
|
||||||
if (!await window.electronAPI.sendUcMessage(codeFindKeyByUser, { userId })) {
|
|
||||||
await window.electronAPI.showAlert('Failed to send request to fetch encryption key.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the response using waitForResponse
|
|
||||||
const response = await waitForResponse();
|
|
||||||
if (response && response.operationCode === codeOk) {
|
|
||||||
const encryptionKey = {
|
|
||||||
key: response.metaInfo.key.key,
|
|
||||||
iv: response.metaInfo.key.iv,
|
|
||||||
};
|
|
||||||
|
|
||||||
await window.electronAPI.writeUserConfig('encryption_key', encryptionKey);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
await window.electronAPI.showAlert('Failed to fetch encryption key from server.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function attemptLogin(email, password) {
|
||||||
|
const app_type = await window.electronAPI.readUserConfig('app_type');
|
||||||
|
const messageData = {email, password, app_type};
|
||||||
|
|
||||||
|
// Send the login message to the server
|
||||||
|
if (!await window.electronAPI.sendUcMessage(codeLogin, messageData)) {
|
||||||
|
await window.electronAPI.showAlert('Failed to send login request.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the response using waitForResponse
|
||||||
|
const response = await waitForResponse();
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
await window.electronAPI.showAlert('No response from server.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.operationCode !== codeOk) {
|
||||||
|
await window.electronAPI.showAlert(response.metaInfo.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user_info = await window.electronAPI.readUserConfig('user_info');
|
||||||
|
if (!user_info || (user_info && user_info.email !== email)) {
|
||||||
|
await window.electronAPI.resetApplicationInfo();
|
||||||
|
await window.electronAPI.resetMemory();
|
||||||
|
await window.electronAPI.resetUserConfig();
|
||||||
|
await window.electronAPI.writeUserConfig('app_type', app_type);
|
||||||
|
await window.electronAPI.writeUserConfig('user_info', {email, password});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAndStoreUserInfo(userEmail) {
|
||||||
|
if (!await window.electronAPI.sendUcMessage(codeFindByEmail, {email: userEmail})) {
|
||||||
|
await window.electronAPI.showAlert('Failed to send request to fetch user info.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the response using waitForResponse
|
||||||
|
const response = await waitForResponse();
|
||||||
|
if (response && response.operationCode === codeOk) {
|
||||||
|
let userInfo = await window.electronAPI.readUserConfig('user_info');
|
||||||
|
if (!userInfo) {
|
||||||
|
userInfo = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo.id = response.metaInfo.id;
|
||||||
|
userInfo.departmentId = response.metaInfo.departmentId;
|
||||||
|
userInfo.name = response.metaInfo.name || 'User';
|
||||||
|
|
||||||
|
await window.electronAPI.writeUserConfig('user_info', userInfo);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await window.electronAPI.showAlert('Failed to fetch user info from server.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAndStoreEncryptionKey(userId) {
|
||||||
|
if (!await window.electronAPI.sendUcMessage(codeFindKeyByUser, {userId})) {
|
||||||
|
await window.electronAPI.showAlert('Failed to send request to fetch encryption key.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the response using waitForResponse
|
||||||
|
const response = await waitForResponse();
|
||||||
|
if (response && response.operationCode === codeOk) {
|
||||||
|
const encryptionKey = {
|
||||||
|
key: response.metaInfo.key.key,
|
||||||
|
iv: response.metaInfo.key.iv,
|
||||||
|
};
|
||||||
|
|
||||||
|
await window.electronAPI.writeUserConfig('encryption_key', encryptionKey);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await window.electronAPI.showAlert('Failed to fetch encryption key from server.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,25 +3,15 @@ document.addEventListener('DOMContentLoaded', async function () {
|
|||||||
|
|
||||||
const backupButton = document.getElementById('backup-dir');
|
const backupButton = document.getElementById('backup-dir');
|
||||||
const shareButton = document.getElementById('share-dir');
|
const shareButton = document.getElementById('share-dir');
|
||||||
const departmentButton = document.getElementById('department-dir');
|
|
||||||
|
|
||||||
const changeInfoButton = document.getElementById('change-info');
|
const changeInfoButton = document.getElementById('change-info');
|
||||||
const shareFileButton = document.getElementById('share-file');
|
const shareFileButton = document.getElementById('share-file');
|
||||||
|
|
||||||
const manageUsersButton = document.getElementById('manage-users');
|
|
||||||
const manageDepartmentsButton = document.getElementById('manage-departments');
|
|
||||||
|
|
||||||
const resetDatabaseButton = document.getElementById('reset-database');
|
|
||||||
const restoreBackupButton = document.getElementById('restore-backup');
|
|
||||||
|
|
||||||
const logoutButton = document.getElementById('logout');
|
const logoutButton = document.getElementById('logout');
|
||||||
|
const restoreBackupButton = document.getElementById('restore-backup');
|
||||||
const disclaimerModal = document.getElementById('disclaimer-modal');
|
const buttonOverviewDepartments = document.getElementById('overview-departments');
|
||||||
const cancelDisclaimerButton = document.getElementById('disclaimer-cancel');
|
const buttonOverviewUsers = document.getElementById('overview-users');
|
||||||
const submitDisclaimerButton = document.getElementById('disclaimer-submit');
|
const buttonResetDatabase = document.getElementById('reset-database');
|
||||||
|
const buttonSendAnnouncement = document.getElementById('send-announcement');
|
||||||
await fetchUserInfo();
|
|
||||||
await checkAndSetAllDirectories();
|
|
||||||
|
|
||||||
backupButton.addEventListener('click', async function () {
|
backupButton.addEventListener('click', async function () {
|
||||||
await setPath('backupDirectory');
|
await setPath('backupDirectory');
|
||||||
@@ -31,8 +21,28 @@ document.addEventListener('DOMContentLoaded', async function () {
|
|||||||
await setPath('shareDirectory');
|
await setPath('shareDirectory');
|
||||||
});
|
});
|
||||||
|
|
||||||
departmentButton.addEventListener('click', async function () {
|
buttonResetDatabase.addEventListener('click', async function () {
|
||||||
await setPath('departmentDirectory');
|
// Show a confirmation dialog
|
||||||
|
const userConfirmed = await showConfirmationDialog("Are you sure you want to reset the database? This action cannot be undone.");
|
||||||
|
|
||||||
|
// If the user confirms, perform the database reset
|
||||||
|
if (userConfirmed) {
|
||||||
|
await resetDatabase();
|
||||||
|
} else {
|
||||||
|
window.electronAPI.showAlert("Database reset canceled.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonSendAnnouncement.addEventListener('click', async function () {
|
||||||
|
fadeOut('announcement');
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonOverviewDepartments.addEventListener('click', async function () {
|
||||||
|
fadeOut('overview_departments');
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonOverviewUsers.addEventListener('click', async function () {
|
||||||
|
fadeOut('overview_users');
|
||||||
});
|
});
|
||||||
|
|
||||||
changeInfoButton.addEventListener('click', function () {
|
changeInfoButton.addEventListener('click', function () {
|
||||||
@@ -43,191 +53,203 @@ document.addEventListener('DOMContentLoaded', async function () {
|
|||||||
fadeOut('share_file');
|
fadeOut('share_file');
|
||||||
});
|
});
|
||||||
|
|
||||||
manageUsersButton.addEventListener('click', function () {
|
|
||||||
fadeOut('users_management');
|
|
||||||
});
|
|
||||||
|
|
||||||
manageDepartmentsButton.addEventListener('click', function () {
|
|
||||||
fadeOut('departments_management' +
|
|
||||||
'');
|
|
||||||
});
|
|
||||||
|
|
||||||
resetDatabaseButton.addEventListener('click', function () {
|
|
||||||
disclaimerModal.style.display = 'block'; // Show disclaimer modal
|
|
||||||
});
|
|
||||||
|
|
||||||
cancelDisclaimerButton.addEventListener('click', function () {
|
|
||||||
disclaimerModal.style.display = 'none'; // Hide modal if cancel is clicked
|
|
||||||
});
|
|
||||||
|
|
||||||
submitDisclaimerButton.addEventListener('click', async function () {
|
|
||||||
disclaimerModal.style.display = 'none'; // Hide modal
|
|
||||||
await resetDatabase(); // Perform database reset
|
|
||||||
fadeOut('login'); // Fade out and go back to login
|
|
||||||
});
|
|
||||||
|
|
||||||
logoutButton.addEventListener('click', async function () {
|
logoutButton.addEventListener('click', async function () {
|
||||||
fadeOut('login');
|
fadeOut('login');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add event listener for the restore backup button
|
||||||
restoreBackupButton.addEventListener('click', async function () {
|
restoreBackupButton.addEventListener('click', async function () {
|
||||||
await restoreBackup();
|
await restoreBackup();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function restoreBackup() {
|
|
||||||
const backupDirectory = await window.electronAPI.readApplicationInfo('backupDirectory');
|
|
||||||
|
|
||||||
if (backupDirectory && backupDirectory.path) {
|
|
||||||
await window.electronAPI.showAlert('You have already set a backup directory. You cannot restore again.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const destinationPath = await window.electronAPI.selectDirectory();
|
|
||||||
if (!destinationPath) return;
|
|
||||||
|
|
||||||
await window.electronAPI.startBackupRetrieval(destinationPath);
|
|
||||||
fadeOut('backup_retrieve');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkAndSetAllDirectories() {
|
|
||||||
await attachNotificationButton('backupDirectory', 'Set your backup directory!', 'backup_alert', 'alert');
|
|
||||||
await attachNotificationButton('shareDirectory', 'Set your share directory!', 'share_alert', 'alert');
|
|
||||||
await attachNotificationButton('departmentDirectory', 'Set your department directory!', 'department_alert', 'alert');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function checkPathExistence(pathKey) {
|
|
||||||
return await window.electronAPI.readApplicationInfo(pathKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setPath(pathKey) {
|
|
||||||
const path = await window.electronAPI.selectDirectory();
|
|
||||||
if (path === undefined) return;
|
|
||||||
|
|
||||||
const id = await window.electronAPI.createMemoryEntry()
|
|
||||||
console.log({id, path})
|
|
||||||
await window.electronAPI.writeApplicationInfo(pathKey, {id, path});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function attachNotificationButton(pathKey, buttonText, buttonId, buttonName) {
|
|
||||||
const path = await checkPathExistence(pathKey);
|
|
||||||
if (!path) {
|
|
||||||
const notificationsDiv = document.getElementById('notifications');
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.id = buttonId;
|
|
||||||
button.name = buttonName;
|
|
||||||
button.textContent = buttonText;
|
|
||||||
button.addEventListener('click', async function () {
|
|
||||||
await setPath(pathKey);
|
|
||||||
button.remove();
|
|
||||||
});
|
|
||||||
notificationsDiv.appendChild(button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchUserInfo() {
|
|
||||||
const usernameField = document.getElementById('username-field');
|
|
||||||
let userInfo = await window.electronAPI.readUserConfig('user_info');
|
|
||||||
if (userInfo && userInfo.name) {
|
|
||||||
usernameField.textContent = userInfo.name;
|
|
||||||
} else {
|
|
||||||
console.error("Username field is not available in the DOM.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadReceivedFiles() {
|
|
||||||
const shareDirectoryData = await window.electronAPI.readApplicationInfo('shareDirectory');
|
|
||||||
if (!shareDirectoryData || !shareDirectoryData.id) {
|
|
||||||
console.log('No received files or directory ID found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const directoryId = shareDirectoryData.id;
|
|
||||||
const directoryStructure = await window.electronAPI.readMemoryEntry(directoryId);
|
|
||||||
if (!directoryStructure || !directoryStructure.structure) {
|
|
||||||
console.log('No received files found in the directory structure.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const notificationsDiv = document.getElementById('notifications');
|
|
||||||
const existingButtons = Array.from(notificationsDiv.children).map(button => button.getAttribute('data-filepath'));
|
|
||||||
|
|
||||||
Object.keys(directoryStructure.structure).forEach(userName => {
|
|
||||||
const userFiles = directoryStructure.structure[userName];
|
|
||||||
Object.keys(userFiles).forEach(fileName => {
|
|
||||||
const filePath = userFiles[fileName];
|
|
||||||
if (!existingButtons.includes(filePath)) {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.setAttribute('data-filepath', filePath);
|
|
||||||
button.name = 'notification';
|
|
||||||
button.textContent = `You received a file "${fileName}" from ${userName}`;
|
|
||||||
button.addEventListener('click', () => handleFileReceivedButtonPressed(filePath, button));
|
|
||||||
notificationsDiv.appendChild(button);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeReceivedFile(filePath) {
|
|
||||||
const shareDirectoryData = await window.electronAPI.readApplicationInfo('shareDirectory');
|
|
||||||
if (!shareDirectoryData || !shareDirectoryData.id) {
|
|
||||||
console.log('No directory ID found to update.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const directoryId = shareDirectoryData.id;
|
|
||||||
let directoryStructure = await window.electronAPI.readMemoryEntry(directoryId);
|
|
||||||
|
|
||||||
if (directoryStructure && directoryStructure.structure) {
|
|
||||||
for (let userName in directoryStructure.structure) {
|
|
||||||
let userFiles = directoryStructure.structure[userName];
|
|
||||||
if (userFiles[filePath]) {
|
|
||||||
delete userFiles[filePath];
|
|
||||||
if (Object.keys(userFiles).length === 0) {
|
|
||||||
delete directoryStructure.structure[userName];
|
|
||||||
}
|
|
||||||
await window.electronAPI.updateMemoryEntry(directoryId, directoryStructure);
|
|
||||||
console.log(`File ${filePath} removed from memory.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('No directory structure found in memory to update.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleFileReceivedButtonPressed(filePath, button) {
|
|
||||||
await window.electronAPI.showFileInExplorer(filePath)
|
|
||||||
.then(() => console.log('File explorer opened for: ' + filePath))
|
|
||||||
.catch(error => console.error('Error opening file explorer:', error));
|
|
||||||
|
|
||||||
button.remove();
|
|
||||||
await removeReceivedFile(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resetDatabase() {
|
|
||||||
const operationCodes = await window.electronAPI.getOperationsCodes();
|
|
||||||
if (!operationCodes) {
|
|
||||||
await window.electronAPI.showAlert('Internal error of the application. Failed to retrieve operation codes.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const codeOk = operationCodes.OK;
|
|
||||||
const codeResetDatabase = operationCodes.RESET_DATABASE;
|
|
||||||
|
|
||||||
if(!await window.electronAPI.openUcSocket()){
|
|
||||||
await window.electronAPI.showAlert('Internal application error.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the request to reset the database
|
|
||||||
if (!await window.electronAPI.sendUcMessage(codeResetDatabase)) {
|
|
||||||
await window.electronAPI.showAlert('Failed to send reset database request.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the response
|
|
||||||
await waitForResponse();
|
|
||||||
await window.electronAPI.changeContent('login');
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function initialSetup(){
|
||||||
|
await checkAndSetAllDirectories();
|
||||||
|
await loadReceivedFiles();
|
||||||
|
await fetchUserInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restoreBackup() {
|
||||||
|
const backupDirectory = await window.electronAPI.readApplicationInfo('backupDirectory');
|
||||||
|
|
||||||
|
if (backupDirectory && backupDirectory.path) {
|
||||||
|
// If backup directory is already set, display an alert
|
||||||
|
await window.electronAPI.showAlert('You have already set a backup directory. You cannot restore again.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prompt the user to choose the destination for the restored backup
|
||||||
|
const destinationPath = await window.electronAPI.selectDirectory();
|
||||||
|
if (!destinationPath) {
|
||||||
|
return; // User canceled the directory selection
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the IPC method to initiate the backup retrieval process
|
||||||
|
window.electronAPI.startBackupRetrieval(destinationPath);
|
||||||
|
|
||||||
|
// Switch the content to the 'backup_retrieve' page
|
||||||
|
fadeOut('backup_retrieve');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkAndSetAllDirectories() {
|
||||||
|
await attachNotificationButton('backupDirectory', 'Set your backup directory!', 'backup_alert', 'alert');
|
||||||
|
await attachNotificationButton('shareDirectory', 'Set your share directory!', 'share_alert', 'alert');
|
||||||
|
await attachNotificationButton('departmentDirectory', 'Set your department directory!', 'department_alert', 'alert');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkPathExistence(pathKey) {
|
||||||
|
return await window.electronAPI.readApplicationInfo(pathKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setPath(pathKey) {
|
||||||
|
const path = await window.electronAPI.selectDirectory();
|
||||||
|
if (path === undefined) return;
|
||||||
|
|
||||||
|
const id = await window.electronAPI.createMemoryEntry()
|
||||||
|
console.log({id, path})
|
||||||
|
await window.electronAPI.writeApplicationInfo(pathKey, {id, path});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function attachNotificationButton(pathKey, buttonText, buttonId, buttonName) {
|
||||||
|
const path = await checkPathExistence(pathKey);
|
||||||
|
if (!path) {
|
||||||
|
const notificationsDiv = document.getElementById('notifications');
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.className='alert';
|
||||||
|
button.id = buttonId;
|
||||||
|
button.name = buttonName;
|
||||||
|
button.textContent = buttonText;
|
||||||
|
button.addEventListener('click', async function () {
|
||||||
|
await setPath(pathKey);
|
||||||
|
button.remove(); // Remove button after setting the path
|
||||||
|
});
|
||||||
|
notificationsDiv.appendChild(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchUserInfo() {
|
||||||
|
const usernameField = document.getElementById('username-field');
|
||||||
|
|
||||||
|
// Read the user credentials from the userConfig
|
||||||
|
let userInfo = await window.electronAPI.readUserConfig('user_info');
|
||||||
|
if (userInfo && userInfo.name) {
|
||||||
|
usernameField.textContent = userInfo.name;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the greeting with the fetched user's name
|
||||||
|
if (usernameField) {
|
||||||
|
usernameField.textContent = userInfo.name; // Update the h1 with the user's name
|
||||||
|
} else {
|
||||||
|
console.error("Username field is not available in the DOM.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadReceivedFiles() {
|
||||||
|
// Read the shareDirectory from applicationInfo
|
||||||
|
const shareDirectoryData = await window.electronAPI.readApplicationInfo('shareDirectory');
|
||||||
|
|
||||||
|
if (!shareDirectoryData || !shareDirectoryData.id) {
|
||||||
|
console.log('No received files or directory ID found.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const directoryId = shareDirectoryData.id;
|
||||||
|
|
||||||
|
// Fetch the directory structure (JSON objects with user names and file paths)
|
||||||
|
const directoryStructure = await window.electronAPI.readMemoryEntry(directoryId);
|
||||||
|
|
||||||
|
if (!directoryStructure || !directoryStructure.structure) {
|
||||||
|
console.log('No received files found in the directory structure.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notificationsDiv = document.getElementById('notifications');
|
||||||
|
const existingButtons = Array.from(notificationsDiv.children).map(button => button.getAttribute('data-filepath'));
|
||||||
|
|
||||||
|
// Iterate over each user and their files in the structure
|
||||||
|
Object.keys(directoryStructure.structure).forEach(userName => {
|
||||||
|
const userFiles = directoryStructure.structure[userName];
|
||||||
|
|
||||||
|
// Iterate over each file of the user
|
||||||
|
Object.keys(userFiles).forEach(fileName => {
|
||||||
|
const filePath = userFiles[fileName];
|
||||||
|
|
||||||
|
// Check if a button for this file path already exists
|
||||||
|
if (!existingButtons.includes(filePath)) {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.setAttribute('data-filepath', filePath); // Set a custom attribute to track the file path
|
||||||
|
button.className = 'notification';
|
||||||
|
button.name = 'notification';
|
||||||
|
button.textContent = `You received a file "${fileName}" from ${userName}`; // Display the userName and file name
|
||||||
|
button.addEventListener('click', () => handleFileReceivedButtonPressed(filePath, button));
|
||||||
|
notificationsDiv.appendChild(button);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeReceivedFile(filePath) {
|
||||||
|
// Read the shareDirectory from applicationInfo
|
||||||
|
const shareDirectoryData = await window.electronAPI.readApplicationInfo('shareDirectory');
|
||||||
|
|
||||||
|
if (!shareDirectoryData || !shareDirectoryData.id) {
|
||||||
|
console.log('No directory ID found to update.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const directoryId = shareDirectoryData.id;
|
||||||
|
|
||||||
|
// Fetch the directory structure
|
||||||
|
let directoryStructure = await window.electronAPI.readMemoryEntry(directoryId);
|
||||||
|
|
||||||
|
if (directoryStructure && directoryStructure.structure) {
|
||||||
|
// Iterate over each user and their files
|
||||||
|
for (let userName in directoryStructure.structure) {
|
||||||
|
let userFiles = directoryStructure.structure[userName];
|
||||||
|
|
||||||
|
// Check if the file exists and remove it
|
||||||
|
if (userFiles[filePath]) {
|
||||||
|
delete userFiles[filePath];
|
||||||
|
|
||||||
|
// Remove the user if no more files are left
|
||||||
|
if (Object.keys(userFiles).length === 0) {
|
||||||
|
delete directoryStructure.structure[userName];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the directory structure in memory
|
||||||
|
await window.electronAPI.updateMemoryEntry(directoryId, directoryStructure);
|
||||||
|
console.log(`File ${filePath} removed from memory.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('No directory structure found in memory to update.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleFileReceivedButtonPressed(filePath, button) {
|
||||||
|
console.log('Notification button clicked!');
|
||||||
|
|
||||||
|
// Open the file in the file explorer
|
||||||
|
await window.electronAPI.showFileInExplorer(filePath)
|
||||||
|
.then(() => console.log('File explorer opened for: ' + filePath))
|
||||||
|
.catch(error => console.error('Error opening file explorer:', error));
|
||||||
|
|
||||||
|
// Remove the button after opening the file
|
||||||
|
button.remove();
|
||||||
|
|
||||||
|
// Remove the file from memory
|
||||||
|
await removeReceivedFile(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showConfirmationDialog(message) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const userResponse = window.confirm(message); // Show native confirmation dialog
|
||||||
|
resolve(userResponse); // Resolve with true (OK) or false (Cancel)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetDatabase(){
|
||||||
|
fadeOut('reset_database');
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
let codeOk = '';
|
||||||
|
let codeGetDepartments = '';
|
||||||
|
let codeCreateDepartment = '';
|
||||||
|
let codeModifyDepartment = '';
|
||||||
|
let codeDeleteDepartment = '';
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
const backButton = document.getElementById('backButton');
|
||||||
|
const createButton = document.getElementById('createDepartmentButton');
|
||||||
|
const closeCreateModalButton = document.getElementById('closeCreateModal');
|
||||||
|
const confirmCreateButton = document.getElementById('confirmCreateButton');
|
||||||
|
|
||||||
|
if (!await window.electronAPI.openUcSocket()) {
|
||||||
|
await window.electronAPI.showAlert('Internal error of the application. Unable to open socket.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
backButton.addEventListener('click', async () => {
|
||||||
|
await window.electronAPI.closeUcSocket();
|
||||||
|
fadeOut('main_menu');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show the Create Department Modal
|
||||||
|
createButton.addEventListener('click', () => {
|
||||||
|
document.getElementById('createDepartmentModal').style.display = 'block';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close Modals
|
||||||
|
closeCreateModalButton.addEventListener('click', () => {
|
||||||
|
document.getElementById('createDepartmentModal').style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Confirm Create Department
|
||||||
|
confirmCreateButton.addEventListener('click', async () => {
|
||||||
|
const departmentName = document.getElementById('newDepartmentName').value.trim();
|
||||||
|
if (!departmentName) {
|
||||||
|
window.electronAPI.showAlert("Please enter a department name.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await createDepartment(departmentName);
|
||||||
|
document.getElementById('createDepartmentModal').style.display = 'none';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Example usage to render these dummy departments
|
||||||
|
async function fetchDepartments() {
|
||||||
|
const departmentsContainer = document.getElementById('departments-container');
|
||||||
|
departmentsContainer.innerHTML = ''; // Clear any existing data
|
||||||
|
|
||||||
|
// Retrieve the operation codes via IPC
|
||||||
|
const operationCodes = await window.electronAPI.getOperationsCodes();
|
||||||
|
if (!operationCodes) {
|
||||||
|
await window.electronAPI.showAlert('Internal error of the application. Failed to retrieve operation codes.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
codeOk = operationCodes.OK;
|
||||||
|
codeGetDepartments = operationCodes.GET_DEPARTMENTS;
|
||||||
|
codeCreateDepartment = operationCodes.CREATE_DEPARTMENT;
|
||||||
|
codeModifyDepartment = operationCodes.MODIFY_DEPARTMENT;
|
||||||
|
codeDeleteDepartment = operationCodes.DELETE_DEPARTMENT;
|
||||||
|
|
||||||
|
// Send the login message to the server
|
||||||
|
if (!await window.electronAPI.sendUcMessage(codeGetDepartments)) {
|
||||||
|
await window.electronAPI.showAlert('Failed to send login request.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the response using waitForResponse
|
||||||
|
const response = await waitForResponse();
|
||||||
|
|
||||||
|
if(!response || response.operationCode !== codeOk){
|
||||||
|
await window.electronAPI.showAlert('Could not fetch departments.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const departments = response.metaInfo.departments;
|
||||||
|
|
||||||
|
const filteredDepartments = departments.filter(department => department.name.toLowerCase() !== 'ceo');
|
||||||
|
|
||||||
|
if (!filteredDepartments.length) {
|
||||||
|
const noDepartmentsMessage = document.createElement('p');
|
||||||
|
noDepartmentsMessage.textContent = 'No departments exist.';
|
||||||
|
noDepartmentsMessage.classList.add('no-departments-message');
|
||||||
|
departmentsContainer.appendChild(noDepartmentsMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredDepartments.forEach(department => {
|
||||||
|
const departmentDiv = document.createElement('div');
|
||||||
|
departmentDiv.classList.add('department-card');
|
||||||
|
|
||||||
|
const nameDiv = document.createElement('div');
|
||||||
|
nameDiv.classList.add('department-name');
|
||||||
|
nameDiv.textContent = department.name;
|
||||||
|
|
||||||
|
// Create and add Modify button
|
||||||
|
const modifyButton = document.createElement('button');
|
||||||
|
modifyButton.textContent = 'Modify';
|
||||||
|
modifyButton.classList.add('modify-button');
|
||||||
|
modifyButton.dataset.departmentId = department.id;
|
||||||
|
|
||||||
|
modifyButton.addEventListener('click', () => {
|
||||||
|
console.log(`Modify button clicked for department ID: ${department.id}`);
|
||||||
|
openModifyModal(department.id, department.name); // Call the function to open the modify modal
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create and add Delete button
|
||||||
|
const deleteButton = document.createElement('button');
|
||||||
|
deleteButton.textContent = 'Delete';
|
||||||
|
deleteButton.classList.add('delete-button');
|
||||||
|
deleteButton.dataset.departmentId = department.id;
|
||||||
|
|
||||||
|
deleteButton.addEventListener('click', async () => {
|
||||||
|
console.log(`Delete button clicked for department ID: ${department.id}`);
|
||||||
|
await deleteDepartment(department.id); // Call the function to delete the department
|
||||||
|
});
|
||||||
|
|
||||||
|
departmentDiv.appendChild(nameDiv);
|
||||||
|
departmentDiv.appendChild(modifyButton);
|
||||||
|
departmentDiv.appendChild(deleteButton);
|
||||||
|
|
||||||
|
departmentsContainer.appendChild(departmentDiv);
|
||||||
|
});
|
||||||
|
|
||||||
|
toggleScroll('departments-container', 370);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function createDepartment(departmentName) {
|
||||||
|
if (!await window.electronAPI.sendUcMessage(codeCreateDepartment, { departmentName: departmentName })) {
|
||||||
|
await window.electronAPI.showAlert('Failed to send login request.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the response using waitForResponse
|
||||||
|
const response = await waitForResponse();
|
||||||
|
|
||||||
|
if(!response || response.operationCode !== codeOk){
|
||||||
|
await window.electronAPI.showAlert('Could not create new department.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetchDepartments();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function modifyDepartment(departmentId, newDepartmentName) {
|
||||||
|
if (!await window.electronAPI.sendUcMessage(codeModifyDepartment, { departmentId: departmentId, newDepartmentName: newDepartmentName })) {
|
||||||
|
await window.electronAPI.showAlert('Failed to send modify request.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the response using waitForResponse
|
||||||
|
const response = await waitForResponse();
|
||||||
|
|
||||||
|
if(!response || response.operationCode !== codeOk){
|
||||||
|
await window.electronAPI.showAlert('Could not modify department.');
|
||||||
|
console.log(response.metaInfo.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetchDepartments();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteDepartment(departmentId) {
|
||||||
|
const confirmDelete = confirm("Are you sure you want to delete this department?");
|
||||||
|
if (!confirmDelete) return;
|
||||||
|
|
||||||
|
if (!await window.electronAPI.sendUcMessage(codeDeleteDepartment, { departmentId: departmentId })) {
|
||||||
|
await window.electronAPI.showAlert('Failed to send delete request.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await waitForResponse();
|
||||||
|
|
||||||
|
if(!response || response.operationCode !== codeOk){
|
||||||
|
await window.electronAPI.showAlert('Could not delete department.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetchDepartments();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the Modify Department Modal
|
||||||
|
function openModifyModal(departmentId, departmentName) {
|
||||||
|
document.getElementById('confirmModifyButton').value = departmentId;
|
||||||
|
document.getElementById('modifiedDepartmentName').value = departmentName;
|
||||||
|
document.getElementById('modifyDepartmentModal').style.display = 'block';
|
||||||
|
|
||||||
|
document.getElementById('closeModifyModal').addEventListener('click', () => {
|
||||||
|
document.getElementById('modifyDepartmentModal').style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('confirmModifyButton').addEventListener('click', async () => {
|
||||||
|
const newDepartmentName = document.getElementById('modifiedDepartmentName').value.trim();
|
||||||
|
const departmentId = document.getElementById('confirmModifyButton').value;
|
||||||
|
|
||||||
|
console.log(newDepartmentName, departmentId);
|
||||||
|
|
||||||
|
if (!newDepartmentName || !departmentId) {
|
||||||
|
window.electronAPI.showAlert("Please enter a new department name.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await modifyDepartment(departmentId, newDepartmentName);
|
||||||
|
document.getElementById('modifyDepartmentModal').style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
let codeGetDepartments = '';
|
||||||
|
let codeGetUsers = '';
|
||||||
|
let codeDeleteUser = '';
|
||||||
|
let codeOk = '';
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
const backButton = document.getElementById('backButton');
|
||||||
|
backButton.addEventListener('click', async () => {
|
||||||
|
await window.electronAPI.closeUcSocket();
|
||||||
|
fadeOut('main_menu');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
const operationCodes = await window.electronAPI.getOperationsCodes();
|
||||||
|
if (!operationCodes) {
|
||||||
|
await window.electronAPI.showAlert('Internal error of the application. Failed to retrieve operation codes.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
codeGetDepartments = operationCodes.GET_DEPARTMENTS;
|
||||||
|
codeGetUsers = operationCodes.GET_USERS;
|
||||||
|
codeDeleteUser = operationCodes.DELETE_USER;
|
||||||
|
codeOk = operationCodes.OK;
|
||||||
|
|
||||||
|
// Open the TCP socket right after fetching the operation codes
|
||||||
|
if (!await window.electronAPI.openUcSocket()) {
|
||||||
|
alert('Failed to open socket. Internal error of the application.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch departments
|
||||||
|
const departmentsResponse = await fetchDepartments();
|
||||||
|
if (!departmentsResponse || departmentsResponse.operationCode !== codeOk) {
|
||||||
|
alert('Failed to fetch departments. Redirecting to main menu...');
|
||||||
|
await redirectToMainMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const departments = departmentsResponse.metaInfo.departments;
|
||||||
|
|
||||||
|
// Fetch users
|
||||||
|
const usersResponse = await fetchUsers();
|
||||||
|
if (!usersResponse || usersResponse.operationCode !== codeOk) {
|
||||||
|
alert('Failed to fetch users. Redirecting to main menu...');
|
||||||
|
await redirectToMainMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = usersResponse.metaInfo.users;
|
||||||
|
|
||||||
|
const usersContainer = document.getElementById('users-container');
|
||||||
|
usersContainer.innerHTML = ''; // Clear the container
|
||||||
|
|
||||||
|
// Filter out users in the CEO department
|
||||||
|
const ceoDepartment = departments.find(dept => dept.name.toLowerCase() === 'ceo');
|
||||||
|
const filteredUsers = users.filter(user => user.departmentId !== ceoDepartment.id);
|
||||||
|
|
||||||
|
// Check if there are no users after filtering
|
||||||
|
if (!filteredUsers.length) {
|
||||||
|
const noUsersMessage = document.createElement('p');
|
||||||
|
noUsersMessage.textContent = 'No users exist.';
|
||||||
|
noUsersMessage.classList.add('no-users-message'); // Add a class for styling
|
||||||
|
usersContainer.appendChild(noUsersMessage);
|
||||||
|
fadeIn(); // Fade in the container when the message is loaded
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render users if there are any
|
||||||
|
filteredUsers.forEach(user => {
|
||||||
|
const userDiv = document.createElement('div');
|
||||||
|
userDiv.classList.add('user-card');
|
||||||
|
|
||||||
|
// Create user info div
|
||||||
|
const userInfoDiv = document.createElement('div');
|
||||||
|
userInfoDiv.classList.add('user-info');
|
||||||
|
|
||||||
|
// Create user name paragraph
|
||||||
|
const nameP = document.createElement('p');
|
||||||
|
nameP.classList.add('user-name');
|
||||||
|
nameP.textContent = user.name;
|
||||||
|
|
||||||
|
console.log(user)
|
||||||
|
|
||||||
|
console.log(departments)
|
||||||
|
|
||||||
|
// Find and display user's department name
|
||||||
|
const department = departments.find(dept => dept.id === user.departmentId);
|
||||||
|
const departmentName = department ? department.name : 'Unknown';
|
||||||
|
|
||||||
|
// Create user department paragraph
|
||||||
|
const departmentP = document.createElement('p');
|
||||||
|
departmentP.classList.add('user-department');
|
||||||
|
departmentP.textContent = `Department: ${departmentName}`;
|
||||||
|
|
||||||
|
// Append name and department to user info div
|
||||||
|
userInfoDiv.appendChild(nameP);
|
||||||
|
userInfoDiv.appendChild(departmentP);
|
||||||
|
|
||||||
|
// Create delete button
|
||||||
|
const deleteButton = document.createElement('button');
|
||||||
|
deleteButton.textContent = 'Delete';
|
||||||
|
deleteButton.classList.add('delete-button');
|
||||||
|
deleteButton.onclick = () => deleteUser(user.id);
|
||||||
|
|
||||||
|
// Append user info div and delete button to user card
|
||||||
|
userDiv.appendChild(userInfoDiv);
|
||||||
|
userDiv.appendChild(deleteButton);
|
||||||
|
|
||||||
|
// Append user card to the container
|
||||||
|
usersContainer.appendChild(userDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchDepartments() {
|
||||||
|
// Send the request to get departments
|
||||||
|
if (!await window.electronAPI.sendUcMessage(codeGetDepartments)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await waitForResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchUsers() {
|
||||||
|
// Send the request to get users
|
||||||
|
if (!await window.electronAPI.sendUcMessage(codeGetUsers)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await waitForResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteUser(userId) {
|
||||||
|
// Send the request to delete the user
|
||||||
|
if (!await window.electronAPI.sendUcMessage(codeDeleteUser, {id: userId})) {
|
||||||
|
alert('Failed to send request to delete user.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await waitForResponse();
|
||||||
|
if (!response || response.operationCode !== codeOk) {
|
||||||
|
alert('Failed to delete user.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetchData();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function redirectToMainMenu() {
|
||||||
|
await window.electronAPI.closeUcSocket();
|
||||||
|
fadeOut('main_menu');
|
||||||
|
}
|
||||||
@@ -10,8 +10,8 @@ document.addEventListener('DOMContentLoaded', async function () {
|
|||||||
usernameInput.value = name;
|
usernameInput.value = name;
|
||||||
passwordInput.value = password;
|
passwordInput.value = password;
|
||||||
|
|
||||||
const backButton = document.querySelector('button[name="login"]');
|
const backButton = document.getElementById('back');
|
||||||
const submitButton = document.querySelector('button[name="submit"]');
|
const submitButton = document.getElementById('submit');
|
||||||
|
|
||||||
// Get operation codes from the backend
|
// Get operation codes from the backend
|
||||||
const operationCodes = await window.electronAPI.getOperationsCodes();
|
const operationCodes = await window.electronAPI.getOperationsCodes();
|
||||||
@@ -39,7 +39,8 @@ document.addEventListener('DOMContentLoaded', async function () {
|
|||||||
const name = usernameInput.value;
|
const name = usernameInput.value;
|
||||||
const password = passwordInput.value;
|
const password = passwordInput.value;
|
||||||
|
|
||||||
const { id, departmentId, app_type } = await window.electronAPI.readUserConfig('user_info'); // Fetch login data
|
const { id, departmentId } = await window.electronAPI.readUserConfig('user_info'); // Fetch login data
|
||||||
|
const app_type = await window.electronAPI.readUserConfig('app_type'); // Fetch app type
|
||||||
|
|
||||||
// Prepare the data to be sent via the UC socket
|
// Prepare the data to be sent via the UC socket
|
||||||
const messageData = {
|
const messageData = {
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', async function () {
|
||||||
|
const operationCodes = await window.electronAPI.getOperationsCodes();
|
||||||
|
if (!operationCodes) {
|
||||||
|
await window.electronAPI.showAlert('Internal error of the application. Failed to retrieve operation codes.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const codeResetDatabase = operationCodes.RESET_DATABASE;
|
||||||
|
const codeOk = operationCodes.OK;
|
||||||
|
|
||||||
|
// Open a TCP socket to the stored IP
|
||||||
|
if (!await window.electronAPI.openUcSocket()) {
|
||||||
|
await window.electronAPI.showAlert('Internal error of the application. Unable to open socket.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await window.electronAPI.sendUcMessage(codeResetDatabase)) {
|
||||||
|
await window.electronAPI.showAlert('Failed to send login request.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await waitForResponse();
|
||||||
|
if (response && response.operationCode !== codeOk) {
|
||||||
|
await window.electronAPI.showAlert('Database reset failed.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await window.electronAPI.closeUcSocket();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
fadeOut('login');
|
||||||
|
});
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
let codeResetPassword = '';
|
||||||
|
let codeOk = '';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async function () {
|
document.addEventListener('DOMContentLoaded', async function () {
|
||||||
const backToLoginButton = document.getElementById('backToLogin');
|
const backToLoginButton = document.getElementById('backToLogin');
|
||||||
const resetPasswordButton = document.getElementById('resetPassword');
|
const resetPasswordButton = document.getElementById('resetPassword');
|
||||||
@@ -9,8 +12,8 @@ document.addEventListener('DOMContentLoaded', async function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const codeResetPassword = operationCodes.RESET_PASSWORD;
|
codeResetPassword = operationCodes.RESET_PASSWORD;
|
||||||
const codeOk = operationCodes.OK;
|
codeOk = operationCodes.OK;
|
||||||
|
|
||||||
// Back to login
|
// Back to login
|
||||||
backToLoginButton.addEventListener('click', function (e) {
|
backToLoginButton.addEventListener('click', function (e) {
|
||||||
@@ -45,19 +48,19 @@ document.addEventListener('DOMContentLoaded', async function () {
|
|||||||
await window.electronAPI.showAlert('Password reset successfully.');
|
await window.electronAPI.showAlert('Password reset successfully.');
|
||||||
window.electronAPI.changeContent('login');
|
window.electronAPI.changeContent('login');
|
||||||
});
|
});
|
||||||
|
|
||||||
async function attemptResetPassword(email, newPassword) {
|
|
||||||
const app_type = await window.electronAPI.readUserConfig('app_type');
|
|
||||||
const messageData = { email, newPassword, app_type };
|
|
||||||
|
|
||||||
if (!await window.electronAPI.sendUcMessage(codeResetPassword, messageData)) return false;
|
|
||||||
|
|
||||||
const response = await waitForResponse();
|
|
||||||
if (response && response.operationCode === codeOk) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
await window.electronAPI.showAlert(response?.metaInfo?.message || 'Error resetting password.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function attemptResetPassword(email, newPassword) {
|
||||||
|
const app_type = await window.electronAPI.readUserConfig('app_type');
|
||||||
|
const messageData = { email, newPassword, app_type };
|
||||||
|
|
||||||
|
if (!await window.electronAPI.sendUcMessage(codeResetPassword, messageData)) return false;
|
||||||
|
|
||||||
|
const response = await waitForResponse();
|
||||||
|
if (response && response.operationCode === codeOk) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
await window.electronAPI.showAlert(response?.metaInfo?.message || 'Error resetting password.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,62 +1,13 @@
|
|||||||
|
let pathToFile = '';
|
||||||
|
let fetchUsersInterval = null;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async function () {
|
document.addEventListener('DOMContentLoaded', async function () {
|
||||||
let user_info = {};
|
const selectFileButton = document.getElementById('selectFile');
|
||||||
let pathToFile = '';
|
const submitButton = document.getElementById('submitButton');
|
||||||
|
const backButton = document.getElementById('backButton');
|
||||||
|
|
||||||
user_info = await window.electronAPI.readUserConfig('user_info');
|
selectFileButton.addEventListener('click', async function (event) {
|
||||||
if (!user_info) {
|
event.preventDefault();
|
||||||
await window.electronAPI.showAlert('No user_info entry found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to update the file name display
|
|
||||||
function updateFileName() {
|
|
||||||
const fileNameElement = document.getElementById('fileName');
|
|
||||||
if (!fileNameElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pathToFile.trim() === '') {
|
|
||||||
fileNameElement.textContent = 'No file chosen';
|
|
||||||
} else {
|
|
||||||
fileNameElement.textContent = pathToFile.split('\\').pop().split('/').pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateFileName();
|
|
||||||
|
|
||||||
// Fetch user information from applicationInfo and create checkboxes for selecting users
|
|
||||||
async function fetchUsersAndCreateCheckboxes() {
|
|
||||||
const usersInfoId = await window.electronAPI.readApplicationInfo('active_users_info');
|
|
||||||
if (!usersInfoId) {
|
|
||||||
await window.electronAPI.showAlert('Internal error.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const usersInfo = await window.electronAPI.readMemoryEntry(usersInfoId);
|
|
||||||
if (!usersInfo || usersInfo.length === 0) {
|
|
||||||
await window.electronAPI.showAlert('No active users found.');
|
|
||||||
await window.electronAPI.changeContent('main_menu');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const usersDiv = document.querySelector('.choose_user_form_content');
|
|
||||||
usersDiv.innerHTML = '';
|
|
||||||
usersInfo.forEach(user => {
|
|
||||||
const checkbox = document.createElement('input');
|
|
||||||
checkbox.type = 'checkbox';
|
|
||||||
checkbox.name = 'users';
|
|
||||||
checkbox.value = user.ip; // Store user's IP in the value
|
|
||||||
|
|
||||||
const label = document.createElement('label');
|
|
||||||
label.innerHTML = `${user.user_info.name}`; // Display user's name
|
|
||||||
label.insertBefore(checkbox, label.firstChild);
|
|
||||||
|
|
||||||
usersDiv.appendChild(label);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event listener for selecting the file
|
|
||||||
document.getElementById('selectFile').addEventListener('click', async function () {
|
|
||||||
try {
|
try {
|
||||||
pathToFile = await window.electronAPI.selectFile();
|
pathToFile = await window.electronAPI.selectFile();
|
||||||
updateFileName();
|
updateFileName();
|
||||||
@@ -64,14 +15,7 @@ document.addEventListener('DOMContentLoaded', async function () {
|
|||||||
console.error('Error opening file dialog:', error);
|
console.error('Error opening file dialog:', error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
submitButton.addEventListener('click', async function (event) {
|
||||||
// Event listener for back button to navigate to main menu
|
|
||||||
document.getElementById('backButton').addEventListener('click', function () {
|
|
||||||
fadeOut('main_menu');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Submit button logic to queue file send tasks and then navigate to main_menu.html
|
|
||||||
document.getElementById('submitButton').addEventListener('click', async function (event) {
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
// Check if a file was chosen
|
// Check if a file was chosen
|
||||||
@@ -80,6 +24,11 @@ document.addEventListener('DOMContentLoaded', async function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const user_info = await window.electronAPI.readUserConfig('user_info');
|
||||||
|
if (!user_info) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const form = document.getElementById('userDestForm');
|
const form = document.getElementById('userDestForm');
|
||||||
const checkboxes = form.querySelectorAll('input[name="users"]');
|
const checkboxes = form.querySelectorAll('input[name="users"]');
|
||||||
const selectedUserIps = Array.from(checkboxes)
|
const selectedUserIps = Array.from(checkboxes)
|
||||||
@@ -100,19 +49,78 @@ document.addEventListener('DOMContentLoaded', async function () {
|
|||||||
userName: user_info.name // Sender's username from userConfig
|
userName: user_info.name // Sender's username from userConfig
|
||||||
};
|
};
|
||||||
await window.electronAPI.addTaskToSendFileQueue(task);
|
await window.electronAPI.addTaskToSendFileQueue(task);
|
||||||
|
await window.electronAPI.showAlert('File sent to the queue.');
|
||||||
console.log(`Task added to send file to IP ${selectedUserIp}`);
|
console.log(`Task added to send file to IP ${selectedUserIp}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error processing IP ${selectedUserIp}:`, error);
|
console.error(`Error processing IP ${selectedUserIp}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
// Navigate back to the main menu after all tasks are queued
|
backButton.addEventListener('click', function (event) {
|
||||||
fadeOut('main_menu'); // Navigate to the main menu
|
event.preventDefault();
|
||||||
|
fadeOut('main_menu');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch users and create checkboxes every 5 seconds (in case of updates)
|
|
||||||
setInterval(fetchUsersAndCreateCheckboxes, 5000);
|
setInterval(fetchUsersAndCreateCheckboxes, 5000);
|
||||||
|
|
||||||
fetchUsersAndCreateCheckboxes().then(() => console.log('User checkboxes rendered'));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Function to update the file name display
|
||||||
|
function updateFileName() {
|
||||||
|
const fileNameElement = document.getElementById('selectFile');
|
||||||
|
if (!fileNameElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathToFile.trim() === '') {
|
||||||
|
fileNameElement.textContent = 'No file chosen';
|
||||||
|
} else {
|
||||||
|
fileNameElement.textContent = pathToFile.split('\\').pop().split('/').pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchUsersAndCreateCheckboxes() {
|
||||||
|
// Step 1: Get the currently checked users before refreshing the list
|
||||||
|
const selectedUserIps = new Set(
|
||||||
|
Array.from(document.querySelectorAll('input[name="users"]:checked'))
|
||||||
|
.map(checkbox => checkbox.value)
|
||||||
|
);
|
||||||
|
|
||||||
|
const usersInfoId = await window.electronAPI.readApplicationInfo('active_users_info');
|
||||||
|
if (!usersInfoId) {
|
||||||
|
await window.electronAPI.showAlert('Internal error.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const usersInfo = await window.electronAPI.readMemoryEntry(usersInfoId);
|
||||||
|
if (!usersInfo || usersInfo.length === 0) {
|
||||||
|
await window.electronAPI.showAlert('No active users found.');
|
||||||
|
clearInterval(fetchUsersInterval);
|
||||||
|
fetchUsersInterval = null;
|
||||||
|
await window.electronAPI.changeContent('main_menu');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const usersDiv = document.getElementById('choose_user_form_content');
|
||||||
|
usersDiv.innerHTML = ''; // Clear the list before updating
|
||||||
|
|
||||||
|
usersInfo.forEach(user => {
|
||||||
|
const checkbox = document.createElement('input');
|
||||||
|
checkbox.type = 'checkbox';
|
||||||
|
checkbox.name = 'users';
|
||||||
|
checkbox.value = user.ip; // Store user's IP in the value
|
||||||
|
|
||||||
|
// Step 2: Check if this user was previously selected
|
||||||
|
if (selectedUserIps.has(user.ip)) {
|
||||||
|
checkbox.checked = true; // Restore checked state
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = document.createElement('label');
|
||||||
|
label.innerHTML = `${user.user_info.name}`; // Display user's name
|
||||||
|
label.insertBefore(checkbox, label.firstChild);
|
||||||
|
|
||||||
|
usersDiv.appendChild(label);
|
||||||
|
});
|
||||||
|
|
||||||
|
toggleScroll('choose_user_form_content');
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
document.addEventListener('DOMContentLoaded', async function () {
|
|
||||||
const nextToStep2Button = document.getElementById('nextToStep2');
|
|
||||||
const backToStep1Button = document.getElementById('backToStep1');
|
|
||||||
const submitSignupButton = document.getElementById('submitSignup');
|
|
||||||
const backToLogin = document.getElementById('backToLogin');
|
|
||||||
|
|
||||||
const step1 = document.getElementById('step1');
|
|
||||||
const step2 = document.getElementById('step2');
|
|
||||||
|
|
||||||
// This object will hold the user data
|
|
||||||
let userData = {
|
|
||||||
email: null,
|
|
||||||
name: null,
|
|
||||||
password: null,
|
|
||||||
app_type: null,
|
|
||||||
departmentId: null
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!await window.electronAPI.openUcSocket()) {
|
|
||||||
alert('Internal error of the application.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const operationCodes = await window.electronAPI.getOperationsCodes();
|
|
||||||
if (!operationCodes) {
|
|
||||||
await window.electronAPI.showAlert('Internal error of the application.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const codeGetDepartments = operationCodes.GET_DEPARTMENTS;
|
|
||||||
const codeSignUp = operationCodes.SIGN_UP;
|
|
||||||
const codeOk = operationCodes.OK;
|
|
||||||
|
|
||||||
let departments = await getDepartments();
|
|
||||||
if (departments === null) {
|
|
||||||
await window.electronAPI.showAlert('Failed to fetch departments.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(departments);
|
|
||||||
|
|
||||||
// Handle form submission for Step 1 (Profile Information)
|
|
||||||
nextToStep2Button.addEventListener('click', async function (e) {
|
|
||||||
e.preventDefault(); // Prevent the default form submission
|
|
||||||
|
|
||||||
// Collect data from Step 1 inputs
|
|
||||||
userData.email = document.querySelector('input[name="email"]').value;
|
|
||||||
userData.name = document.querySelector('input[name="name"]').value;
|
|
||||||
userData.password = document.querySelector('input[name="password"]').value;
|
|
||||||
userData.app_type = await window.electronAPI.readUserConfig('app_type');
|
|
||||||
|
|
||||||
// Move to Step 2
|
|
||||||
const departmentList = document.getElementById('departmentList');
|
|
||||||
departmentList.innerHTML = ''; // Clear existing departments
|
|
||||||
|
|
||||||
// Populate department list
|
|
||||||
departments.forEach(department => {
|
|
||||||
if (department.name !== 'CEO' && department.name !== 'ADMIN') {
|
|
||||||
const label = document.createElement('label');
|
|
||||||
label.innerHTML = `
|
|
||||||
<input type="radio" name="dept" value="${department.id}">
|
|
||||||
${department.name}
|
|
||||||
`;
|
|
||||||
departmentList.appendChild(label);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
switchStep(step1, step2);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle form submission for Step 2 (Department Selection)
|
|
||||||
submitSignupButton.addEventListener('click', async function (e) {
|
|
||||||
e.preventDefault(); // Prevent form from being submitted in the usual way
|
|
||||||
|
|
||||||
// Get the selected department
|
|
||||||
userData.departmentId = document.querySelector('input[name="dept"]:checked')?.value;
|
|
||||||
if (!userData.departmentId) {
|
|
||||||
await window.electronAPI.showAlert('Please select a department.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!await attemptSignUp()) {
|
|
||||||
switchStep(step2, step1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await window.electronAPI.showAlert('Signup successful!');
|
|
||||||
await window.electronAPI.changeContent('login');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Back to Step 1 from Step 2
|
|
||||||
backToStep1Button.addEventListener('click', function () {
|
|
||||||
switchStep(step2, step1);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Back to login
|
|
||||||
backToLogin.addEventListener('click', async function (e) {
|
|
||||||
e.preventDefault();
|
|
||||||
await window.electronAPI.closeUcSocket();
|
|
||||||
await window.electronAPI.changeContent('login');
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getDepartments() {
|
|
||||||
if (!await window.electronAPI.sendUcMessage(codeGetDepartments)) return null;
|
|
||||||
|
|
||||||
const response = await waitForResponse();
|
|
||||||
if (response && response.operationCode === codeOk) {
|
|
||||||
return response.metaInfo.departments;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function attemptSignUp() {
|
|
||||||
if (!await window.electronAPI.sendUcMessage(codeSignUp, userData)) return false;
|
|
||||||
|
|
||||||
const response = await waitForResponse();
|
|
||||||
if (response && response.operationCode === codeOk) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
await window.electronAPI.showAlert(`Signup failed: ${response?.metaInfo?.message || 'Unknown error'}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to switch between steps
|
|
||||||
function switchStep(fromStep, toStep) {
|
|
||||||
fromStep.classList.remove('active'); // Fade out the current step
|
|
||||||
setTimeout(() => {
|
|
||||||
fromStep.style.display = 'none'; // Hide the current step after transition
|
|
||||||
toStep.style.display = 'flex'; // Ensure display is flex for the next step
|
|
||||||
setTimeout(() => {
|
|
||||||
toStep.classList.add('active'); // Fade in the next step
|
|
||||||
}, 20); // Small delay to allow display change before applying opacity
|
|
||||||
}, 500); // Transition duration (0.5s)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
document.addEventListener('DOMContentLoaded', async () => {
|
|
||||||
const backButton = document.getElementById('backButton');
|
|
||||||
backButton.addEventListener('click', async () => {
|
|
||||||
await window.electronAPI.closeUcSocket();
|
|
||||||
fadeOut('main_menu');
|
|
||||||
});
|
|
||||||
|
|
||||||
const operationCodes = await window.electronAPI.getOperationsCodes();
|
|
||||||
if (!operationCodes) {
|
|
||||||
alert('Internal error of the application.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open the TCP socket right after fetching the operation codes
|
|
||||||
if (!await window.electronAPI.openUcSocket()) {
|
|
||||||
alert('Failed to open socket. Internal error of the application.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let codeGetDepartments = operationCodes.GET_DEPARTMENTS;
|
|
||||||
let codeGetUsers = operationCodes.GET_USERS;
|
|
||||||
let codeDeleteUser = operationCodes.DELETE_USER;
|
|
||||||
let codeOk = operationCodes.OK;
|
|
||||||
|
|
||||||
// Fetch both users and departments, then display the users with their departments
|
|
||||||
async function fetchData() {
|
|
||||||
try {
|
|
||||||
// Fetch departments
|
|
||||||
const departmentsResponse = await fetchDepartments();
|
|
||||||
if (!departmentsResponse || departmentsResponse.operationCode !== codeOk) {
|
|
||||||
alert('Failed to fetch departments. Redirecting to main menu...');
|
|
||||||
await redirectToMainMenu();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const departments = departmentsResponse.metaInfo.departments;
|
|
||||||
|
|
||||||
// Fetch users
|
|
||||||
const usersResponse = await fetchUsers();
|
|
||||||
if (!usersResponse || usersResponse.operationCode !== codeOk) {
|
|
||||||
alert('Failed to fetch users. Redirecting to main menu...');
|
|
||||||
await redirectToMainMenu();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const users = usersResponse.metaInfo.users;
|
|
||||||
|
|
||||||
const usersContainer = document.getElementById('users-container');
|
|
||||||
usersContainer.innerHTML = ''; // Clear the container
|
|
||||||
|
|
||||||
// Filter out users in the CEO department
|
|
||||||
const ceoDepartment = departments.find(dept => dept.name.toLowerCase() === 'ceo');
|
|
||||||
const filteredUsers = users.filter(user => user.departmentId !== ceoDepartment.id);
|
|
||||||
|
|
||||||
// Check if there are no users after filtering
|
|
||||||
if (!filteredUsers.length) {
|
|
||||||
const noUsersMessage = document.createElement('p');
|
|
||||||
noUsersMessage.textContent = 'No users exist.';
|
|
||||||
noUsersMessage.classList.add('no-users-message'); // Add a class for styling
|
|
||||||
usersContainer.appendChild(noUsersMessage);
|
|
||||||
fadeIn(); // Fade in the container when the message is loaded
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render users if there are any
|
|
||||||
filteredUsers.forEach(user => {
|
|
||||||
const userDiv = document.createElement('div');
|
|
||||||
userDiv.classList.add('user-card');
|
|
||||||
|
|
||||||
// Create user name div
|
|
||||||
const nameDiv = document.createElement('div');
|
|
||||||
nameDiv.classList.add('user-name');
|
|
||||||
nameDiv.textContent = user.name;
|
|
||||||
|
|
||||||
// Find the user's department name using departmentId
|
|
||||||
const department = departments.find(dept => dept.id === user.departmentId);
|
|
||||||
const departmentName = department ? department.name : 'Unknown';
|
|
||||||
|
|
||||||
// Create department div
|
|
||||||
const departmentDiv = document.createElement('div');
|
|
||||||
departmentDiv.classList.add('user-department');
|
|
||||||
departmentDiv.textContent = `Department: ${departmentName}`;
|
|
||||||
|
|
||||||
// Create delete button
|
|
||||||
const deleteButton = document.createElement('button');
|
|
||||||
deleteButton.textContent = 'Delete';
|
|
||||||
deleteButton.classList.add('delete-button');
|
|
||||||
deleteButton.onclick = () => deleteUser(user.id);
|
|
||||||
|
|
||||||
// Append user info and delete button to the user div
|
|
||||||
userDiv.appendChild(nameDiv);
|
|
||||||
userDiv.appendChild(departmentDiv);
|
|
||||||
userDiv.appendChild(deleteButton);
|
|
||||||
|
|
||||||
// Append user div to the container
|
|
||||||
usersContainer.appendChild(userDiv);
|
|
||||||
});
|
|
||||||
|
|
||||||
fadeIn(); // Fade in the container when the users are loaded
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching data:', error);
|
|
||||||
alert('An unexpected error occurred. Redirecting to main menu...');
|
|
||||||
await redirectToMainMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchDepartments() {
|
|
||||||
// Send the request to get departments
|
|
||||||
if (!await window.electronAPI.sendUcMessage(codeGetDepartments)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return await waitForResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchUsers() {
|
|
||||||
// Send the request to get users
|
|
||||||
if (!await window.electronAPI.sendUcMessage(codeGetUsers)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return await waitForResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteUser(userId) {
|
|
||||||
// Find the index of the user in the dummy data
|
|
||||||
const userIndex = dummyUsers.findIndex(user => user.id === userId);
|
|
||||||
|
|
||||||
// If user found, remove it from the array
|
|
||||||
if (userIndex !== -1) {
|
|
||||||
dummyUsers.splice(userIndex, 1);
|
|
||||||
alert(`User with ID ${userId} deleted.`);
|
|
||||||
await fetchData(); // Refresh the users after deleting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function redirectToMainMenu() {
|
|
||||||
await window.electronAPI.closeUcSocket();
|
|
||||||
fadeOut('main_menu');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch data on page load
|
|
||||||
await fetchData();
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import { JsonManager } from './json_manager'; // Assuming this manages JSON configurations
|
||||||
|
import { TcpCommunicator } from "./tcp_communicator";
|
||||||
|
import { operationCodes } from '../network/operation_codes'; // Assuming this holds operation codes
|
||||||
|
import { parentPort } from 'worker_threads';
|
||||||
|
import {ParsedMessage} from "../network/message_handler";
|
||||||
|
|
||||||
|
export class AnnouncementSender {
|
||||||
|
private applicationInfo: JsonManager;
|
||||||
|
private readonly clientPort: number;
|
||||||
|
private message: string = '';
|
||||||
|
private tcpCommunicator: TcpCommunicator | null = null;
|
||||||
|
|
||||||
|
constructor(applicationInfoPath: string, clientPort: number) {
|
||||||
|
this.applicationInfo = new JsonManager(applicationInfoPath);
|
||||||
|
this.clientPort = clientPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(message: string): Promise<void> {
|
||||||
|
console.log('AnnouncementWorker started.');
|
||||||
|
this.message = message;
|
||||||
|
try {
|
||||||
|
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
|
||||||
|
if (!activeUsersIp || !activeUsersIp.length) {
|
||||||
|
throw new Error('No active users found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ip of activeUsersIp) {
|
||||||
|
const success = await this.sendAnnouncementToIp(ip);
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(`Failed to send announcement to all users.`);
|
||||||
|
}
|
||||||
|
console.log(`Announcement sent and confirmed successfully from ${ip}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
parentPort?.postMessage({ success: true, message: 'Announcement sent to all active users successfully.' });
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error in AnnouncementWorker:', error);
|
||||||
|
parentPort?.postMessage({ success: false, message: `A problem occurred: ${error.message}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('AnnouncementWorker finished.');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendAnnouncementToIp(ip: string): Promise<boolean> {
|
||||||
|
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort);
|
||||||
|
if (!await this.tcpCommunicator.connect()) {
|
||||||
|
console.log(`Skipping user at IP ${ip} - unable to connect.`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the message metadata
|
||||||
|
const metaInfo = { message: this.message };
|
||||||
|
|
||||||
|
// Send the announcement message
|
||||||
|
const messageSent = await this.tcpCommunicator.sendMessage(operationCodes.SEND_ANNOUNCEMENT, metaInfo);
|
||||||
|
if (!messageSent) {
|
||||||
|
await this.tcpCommunicator.disconnect();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Await confirmation from the user
|
||||||
|
const response = await this.waitForResponse();
|
||||||
|
if (response?.operationCode === operationCodes.OK) {
|
||||||
|
await this.tcpCommunicator.disconnect();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If confirmation is not OK, disconnect and halt
|
||||||
|
await this.tcpCommunicator.disconnect();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async waitForResponse(): Promise<ParsedMessage | null> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const idResponseCheck = setInterval(async () => {
|
||||||
|
if (!this.tcpCommunicator) return null;
|
||||||
|
if (this.tcpCommunicator.hasResponseArrived()) {
|
||||||
|
clearInterval(idResponseCheck);
|
||||||
|
resolve(this.tcpCommunicator.getLastResult());
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,203 +1,181 @@
|
|||||||
import fs from 'fs';
|
import { JsonManager } from './json_manager'; // Assuming this manages JSON configurations
|
||||||
import path from 'path';
|
import { TcpCommunicator } from "./tcp_communicator";
|
||||||
import { FileEncryptor } from './file_encryptor'; // Assume the class is in this file
|
import { operationCodes } from '../network/operation_codes'; // Assuming this holds operation codes
|
||||||
import { MemoryManager } from './memory_manager'; // Assume this handles memory-based storage
|
|
||||||
import { JsonManager } from './json_manager'; // Manages JSON-based configurations
|
|
||||||
import { TcpClient } from '../network/tcp/tcp_client' // Import your TcpClient class
|
|
||||||
import { operationCodes } from '../network/operation_codes';
|
|
||||||
import { parentPort } from 'worker_threads';
|
import { parentPort } from 'worker_threads';
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import { ParsedMessage } from "../network/message_handler";
|
||||||
|
|
||||||
export class BackupManager {
|
export class BackupRetrievalWorker {
|
||||||
private fileEncryptor: FileEncryptor | null = null;
|
|
||||||
private memoryManager: MemoryManager;
|
|
||||||
private applicationInfo: JsonManager;
|
|
||||||
private userConfig: JsonManager;
|
private userConfig: JsonManager;
|
||||||
|
private applicationInfo: JsonManager;
|
||||||
private readonly clientPort: number;
|
private readonly clientPort: number;
|
||||||
|
private readonly destinationPath: string;
|
||||||
|
private encryptionKey: Buffer | null = null;
|
||||||
|
private iv: Buffer | null = null;
|
||||||
|
private tcpCommunicator: TcpCommunicator | null = null;
|
||||||
|
|
||||||
constructor(userConfigPath: string, applicationInfoPath: string, memoryManagerPath: string, clientPort: number) {
|
constructor(userConfigPath: string, applicationInfoPath: string, clientPort: number, destinationPath: string) {
|
||||||
this.applicationInfo = new JsonManager(applicationInfoPath);
|
|
||||||
this.userConfig = new JsonManager(userConfigPath);
|
this.userConfig = new JsonManager(userConfigPath);
|
||||||
this.memoryManager = new MemoryManager(memoryManagerPath);
|
this.applicationInfo = new JsonManager(applicationInfoPath);
|
||||||
this.clientPort = clientPort;
|
this.clientPort = clientPort;
|
||||||
|
this.destinationPath = destinationPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
setInterval(async () => {
|
try {
|
||||||
await this.initialize(); // Re-run every minute
|
const userInfo = await this.userConfig.readValue('user_info');
|
||||||
}, 60000); // 1 minute interval
|
if (!userInfo || !userInfo.name) {
|
||||||
}
|
throw new Error('User information or name is missing.');
|
||||||
|
}
|
||||||
|
const userName = userInfo.name;
|
||||||
|
|
||||||
// Initialize the backup process: fetch data from the app info and memory
|
const encryptionData = await this.userConfig.readValue('encryption_key');
|
||||||
private async initialize(): Promise<void> {
|
if (!encryptionData || !encryptionData.key || !encryptionData.iv) {
|
||||||
// Initialize the encryption settings from UserConfig
|
throw new Error('Encryption key or IV is missing.');
|
||||||
const encryptionKeyData = await this.userConfig.readValue('encryption_key');
|
|
||||||
if (!encryptionKeyData || !encryptionKeyData.key || !encryptionKeyData.iv) {
|
|
||||||
console.error('Encryption key data is missing in user configuration.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new FileEncryptor with the retrieved key and IV
|
|
||||||
this.fileEncryptor = new FileEncryptor(encryptionKeyData.key, encryptionKeyData.iv);
|
|
||||||
|
|
||||||
// Get the name of the user
|
|
||||||
const userInfo = await this.userConfig.readValue('user_info');
|
|
||||||
if (!userInfo || !userInfo.name) {
|
|
||||||
console.error('User information is missing in user configuration.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userName = userInfo.name;
|
|
||||||
|
|
||||||
// Get the backup directory information from ApplicationInfo
|
|
||||||
const backupDirectoryData = await this.applicationInfo.readValue('backupDirectory');
|
|
||||||
if (!backupDirectoryData || !backupDirectoryData.id || !backupDirectoryData.path) {
|
|
||||||
console.error('Backup directory information is missing in application info.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
|
|
||||||
if (!activeUsersIp || !activeUsersIp.length) {
|
|
||||||
console.error('No active users found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const backupDirectoryId = backupDirectoryData.id;
|
|
||||||
const backupDirectoryPath = backupDirectoryData.path;
|
|
||||||
|
|
||||||
// Get the file structure from MemoryManager
|
|
||||||
const directoryData = await this.memoryManager.retrieveMetaInformation(backupDirectoryId);
|
|
||||||
if (!directoryData || !directoryData.structure) {
|
|
||||||
console.error('Backup directory structure is missing in memory.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send files to the list of IPs
|
|
||||||
await this.sendFilesToUsers(directoryData.structure, userName, activeUsersIp, backupDirectoryPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method to encrypt a file and return the base64 string
|
|
||||||
private encryptFile(filePath: string): string {
|
|
||||||
if (!this.fileEncryptor) {
|
|
||||||
return filePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(filePath)) {
|
|
||||||
console.error(`File not found: ${filePath}`);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use FileEncryptor to encrypt the file and return the base64 string
|
|
||||||
return this.fileEncryptor.encryptFileToBase64(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send files to the list of users and remove successfully sent files from the list
|
|
||||||
private async sendFilesToUsers(fileStructure: { [key: string]: string }, userName: string, usersIp: string[], backupDirectoryPath: string): Promise<void> {
|
|
||||||
let unsentFiles = Object.keys(fileStructure); // Keep track of unsent files
|
|
||||||
|
|
||||||
for (const fileName of unsentFiles) {
|
|
||||||
const filePath = fileStructure[fileName];
|
|
||||||
const encryptedFileContent = this.encryptFile(filePath);
|
|
||||||
|
|
||||||
if (!encryptedFileContent) {
|
|
||||||
console.error(`Failed to encrypt file: ${fileName}`);
|
|
||||||
continue; // Skip to the next file
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate relative file path
|
this.encryptionKey = Buffer.from(encryptionData.key, 'base64');
|
||||||
const relativeFilePath = path.relative(backupDirectoryPath, filePath); // Get the relative file path
|
this.iv = Buffer.from(encryptionData.iv, 'base64');
|
||||||
|
|
||||||
// Meta information to send
|
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
|
||||||
const metaInfo = {
|
if (!activeUsersIp || !activeUsersIp.length) {
|
||||||
userName, // Name of the user
|
parentPort?.postMessage({ success: false, message: 'No active users found.' });
|
||||||
relativeFilePath // Relative path to preserve the directory structure
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
for (const ip of usersIp) {
|
let backupSuccessful = true;
|
||||||
const tcpClient = new TcpClient(this.clientPort);
|
for (const ip of activeUsersIp) {
|
||||||
tcpClient.openSocket(ip);
|
const success = await this.processBackupForIp(ip, userName);
|
||||||
|
if (!success) {
|
||||||
// Wait for AES key and send the file
|
backupSuccessful = false;
|
||||||
try {
|
parentPort?.postMessage({ success: false, message: 'Backup could not be completed due to an internal error.' });
|
||||||
const sendSuccess = await this.waitForAesKeyAndSendFile(tcpClient, operationCodes.BACKUP_FILE, metaInfo, Buffer.from(encryptedFileContent, 'base64'));
|
break;
|
||||||
if (sendSuccess) {
|
|
||||||
console.log(`Successfully sent file: ${fileName} to ${ip}`);
|
|
||||||
unsentFiles = unsentFiles.filter(f => f !== fileName); // Remove the file from the unsent list
|
|
||||||
tcpClient.closeSocket();
|
|
||||||
break; // Move to the next file after successful send
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to send file: ${fileName} to ${ip}. Error: ${error}`);
|
|
||||||
tcpClient.closeSocket();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (backupSuccessful) {
|
||||||
|
parentPort?.postMessage({ success: true, message: 'Backup successful.' });
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error in BackupRetrievalWorker:', error);
|
||||||
|
parentPort?.postMessage({ success: false, message: `Backup could not be completed due to an internal error: ${error.message}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async processBackupForIp(ip: string, userName: string): Promise<boolean> {
|
||||||
|
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort);
|
||||||
|
if (!await this.tcpCommunicator.connect()) {
|
||||||
|
console.error(`Failed to connect to ${ip}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const backupExists = await this.checkIfBackupExists(userName);
|
||||||
|
if (!backupExists) {
|
||||||
|
console.log(`No backup found for user ${userName} on IP ${ip}`);
|
||||||
|
await this.tcpCommunicator.disconnect();
|
||||||
|
return true; // Skip user if no backup found, do not mark as error
|
||||||
|
}
|
||||||
|
|
||||||
|
const backupStructure = await this.requestBackupStructure(userName);
|
||||||
|
if (!backupStructure || Object.keys(backupStructure).length === 0) {
|
||||||
|
console.log(`No files found in backup structure for user ${userName} on IP ${ip}`);
|
||||||
|
await this.tcpCommunicator.disconnect();
|
||||||
|
return true; // Skip user if no files found, do not mark as error
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const relativeFilePath of Object.keys(backupStructure)) {
|
||||||
|
const fileRequestSuccess = await this.requestBackupFile(userName, relativeFilePath);
|
||||||
|
if (!fileRequestSuccess) {
|
||||||
|
console.error(`Failed to retrieve file ${relativeFilePath} from backup for user ${userName} on IP ${ip}`);
|
||||||
|
await this.tcpCommunicator.disconnect();
|
||||||
|
return false; // Stop if any file fails to be retrieved
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are any unsent files, notify the parent process
|
await this.tcpCommunicator.disconnect();
|
||||||
if (unsentFiles.length > 0) {
|
return true;
|
||||||
parentPort?.postMessage({ success: false, message: 'Backup could not be completed for all files', unsentFiles });
|
}
|
||||||
} else {
|
|
||||||
parentPort?.postMessage({ success: true, message: 'Backup completed successfully' });
|
private async checkIfBackupExists(userName: string): Promise<boolean> {
|
||||||
|
if (!this.tcpCommunicator) return false;
|
||||||
|
|
||||||
|
const metaInfo = { name: userName };
|
||||||
|
if (!await this.tcpCommunicator.sendMessage(operationCodes.IS_BACKUP_CREATED, metaInfo)) return false;
|
||||||
|
|
||||||
|
const response = await this.waitForResponse();
|
||||||
|
return response?.metaInfo?.backupExists === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async requestBackupStructure(userName: string): Promise<any> {
|
||||||
|
if (!this.tcpCommunicator) return false;
|
||||||
|
|
||||||
|
const metaInfo = { name: userName };
|
||||||
|
if (!await this.tcpCommunicator.sendMessage(operationCodes.GET_BACKUP_STRUCTURE, metaInfo)) return null;
|
||||||
|
|
||||||
|
const response = await this.waitForResponse();
|
||||||
|
return response?.metaInfo?.structure || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async requestBackupFile(userName: string, relativeFilePath: string): Promise<boolean> {
|
||||||
|
if (!this.tcpCommunicator) return false;
|
||||||
|
|
||||||
|
const metaInfo = { name: userName, relativeFilePath };
|
||||||
|
if (!await this.tcpCommunicator.sendMessage(operationCodes.REQ_FILE_FROM_BACKUP, metaInfo)) return false;
|
||||||
|
|
||||||
|
const response = await this.waitForResponse();
|
||||||
|
if (response?.operationCode === operationCodes.OK && response.metaInfo && response.fileContent) {
|
||||||
|
return this.saveFile(response.metaInfo.relativeFilePath, response.fileContent.toString('base64'));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveFile(relativeFilePath: string, fileContent: string): boolean {
|
||||||
|
if (!this.encryptionKey || !this.iv) {
|
||||||
|
console.error('Encryption key or IV is not set.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let encryptedBuffer: Buffer;
|
||||||
|
try {
|
||||||
|
encryptedBuffer = Buffer.from(fileContent, 'base64');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error decoding base64 file content:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let decryptedContent: Buffer;
|
||||||
|
try {
|
||||||
|
const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv);
|
||||||
|
decryptedContent = Buffer.concat([decipher.update(encryptedBuffer), decipher.final()]);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error decrypting file:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullFilePath = path.join(this.destinationPath, relativeFilePath);
|
||||||
|
try {
|
||||||
|
const dirPath = path.dirname(fullFilePath);
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
fs.writeFileSync(fullFilePath, decryptedContent);
|
||||||
|
console.log(`File saved successfully: ${fullFilePath}`);
|
||||||
|
return true;
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(`Error saving file ${relativeFilePath}: ${error.message}`);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for AES key to be set, send the file, and wait for the response
|
private async waitForResponse(): Promise<ParsedMessage | null> {
|
||||||
private async waitForAesKeyAndSendFile(tcpClient: TcpClient, operationCode: string, metaInfo: { [key: string]: any }, fileContent: Buffer, checkInterval: number = 500, timeout: number = 10000): Promise<boolean> {
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const intervalId = setInterval(async () => {
|
|
||||||
const elapsedTime = Date.now() - startTime;
|
|
||||||
|
|
||||||
// Check if AES key is set, if timeout occurs, reject
|
|
||||||
if (!tcpClient.isAesKeySet()) {
|
|
||||||
if (elapsedTime > timeout) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
console.error('Timeout waiting for AES key.');
|
|
||||||
reject(new Error('Timeout waiting for AES key.'));
|
|
||||||
}
|
|
||||||
return; // Continue waiting for AES key
|
|
||||||
}
|
|
||||||
|
|
||||||
// AES key is set, send the message
|
|
||||||
clearInterval(intervalId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const success = await tcpClient.sendMessage(operationCode, metaInfo, fileContent);
|
|
||||||
if (!success) {
|
|
||||||
reject(new Error('Failed to send the file content.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for a response after sending the message
|
|
||||||
const responseReceived = await this.waitForMessageResponse(tcpClient, timeout);
|
|
||||||
if (!responseReceived) {
|
|
||||||
reject(new Error('Timeout waiting for the message response.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Everything went fine
|
|
||||||
resolve(true);
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
}, checkInterval); // Check for AES key every `checkInterval`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method to wait for the response from the TCP client
|
|
||||||
private waitForMessageResponse(tcpClient: TcpClient, timeout: number = 10000): Promise<any> {
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const startTime = Date.now();
|
const idResponseCheck = setInterval(async () => {
|
||||||
const intervalId = setInterval(() => {
|
if (!this.tcpCommunicator) return null;
|
||||||
const response = tcpClient.getLastResult();
|
if (this.tcpCommunicator.hasResponseArrived()) {
|
||||||
const elapsedTime = Date.now() - startTime;
|
clearInterval(idResponseCheck);
|
||||||
|
resolve(this.tcpCommunicator.getLastResult());
|
||||||
if (response) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
resolve(true);
|
|
||||||
} else if (elapsedTime > timeout) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
resolve(false); // No response after timeout
|
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { JsonManager } from './json_manager'; // Assuming this manages JSON configurations
|
import { JsonManager } from './json_manager'; // Assuming this manages JSON configurations
|
||||||
import { TcpClient } from '../network/tcp/tcp_client'; // Assuming this handles TCP client connections
|
import { TcpCommunicator } from "./tcp_communicator";
|
||||||
import { operationCodes } from '../network/operation_codes'; // Assuming this holds operation codes
|
import { operationCodes } from '../network/operation_codes'; // Assuming this holds operation codes
|
||||||
import { parentPort } from 'worker_threads';
|
import { parentPort } from 'worker_threads';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import crypto from 'crypto'; // Import the crypto module for encryption and decryption
|
import crypto from 'crypto';
|
||||||
|
import { ParsedMessage } from "../network/message_handler"; // Import the crypto module for encryption and decryption
|
||||||
|
|
||||||
export class BackupRetrievalWorker {
|
export class BackupRetrievalWorker {
|
||||||
private userConfig: JsonManager;
|
private userConfig: JsonManager;
|
||||||
@@ -13,6 +14,7 @@ export class BackupRetrievalWorker {
|
|||||||
private readonly destinationPath: string;
|
private readonly destinationPath: string;
|
||||||
private encryptionKey: Buffer | null = null;
|
private encryptionKey: Buffer | null = null;
|
||||||
private iv: Buffer | null = null;
|
private iv: Buffer | null = null;
|
||||||
|
private tcpCommunicator: TcpCommunicator | null = null;
|
||||||
|
|
||||||
constructor(userConfigPath: string, applicationInfoPath: string, clientPort: number, destinationPath: string) {
|
constructor(userConfigPath: string, applicationInfoPath: string, clientPort: number, destinationPath: string) {
|
||||||
this.userConfig = new JsonManager(userConfigPath);
|
this.userConfig = new JsonManager(userConfigPath);
|
||||||
@@ -22,203 +24,149 @@ export class BackupRetrievalWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
// Retrieve the necessary data from userConfig and applicationInfo
|
try {
|
||||||
const userInfo = await this.userConfig.readValue('user_info');
|
const userInfo = await this.userConfig.readValue('user_info');
|
||||||
if (!userInfo || !userInfo.name) {
|
if (!userInfo || !userInfo.name) {
|
||||||
console.error('User information or name is missing.');
|
throw new Error('User information or name is missing.');
|
||||||
return;
|
|
||||||
}
|
|
||||||
const userName = userInfo.name;
|
|
||||||
|
|
||||||
// Load encryption key and IV from userConfig
|
|
||||||
const encryptionData = await this.userConfig.readValue('encryption_key');
|
|
||||||
if (!encryptionData || !encryptionData.key || !encryptionData.iv) {
|
|
||||||
console.error('Encryption key or IV is missing.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert encryption key and IV from base64 to buffer
|
|
||||||
this.encryptionKey = Buffer.from(encryptionData.key, 'base64');
|
|
||||||
this.iv = Buffer.from(encryptionData.iv, 'base64');
|
|
||||||
|
|
||||||
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
|
|
||||||
if (!activeUsersIp || !activeUsersIp.length) {
|
|
||||||
console.error('No active users found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process each IP and request backup data
|
|
||||||
for (const ip of activeUsersIp) {
|
|
||||||
try {
|
|
||||||
const success = await this.processBackupForIp(ip, userName);
|
|
||||||
if (success) {
|
|
||||||
console.log(`Backup retrieved successfully from ${ip}`);
|
|
||||||
} else {
|
|
||||||
console.error(`Failed to retrieve backup from ${ip}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error processing backup from ${ip}: ${error}`);
|
|
||||||
}
|
}
|
||||||
}
|
const userName = userInfo.name;
|
||||||
|
|
||||||
// Notify the parent that the worker is done
|
const encryptionData = await this.userConfig.readValue('encryption_key');
|
||||||
parentPort?.postMessage({ success: true, message: 'Backup retrieval completed.' });
|
if (!encryptionData || !encryptionData.key || !encryptionData.iv) {
|
||||||
|
throw new Error('Encryption key or IV is missing.');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.encryptionKey = Buffer.from(encryptionData.key, 'base64');
|
||||||
|
this.iv = Buffer.from(encryptionData.iv, 'base64');
|
||||||
|
|
||||||
|
const activeUsersIp = await this.applicationInfo.readValue('users_ip');
|
||||||
|
if (!activeUsersIp || !activeUsersIp.length) {
|
||||||
|
throw new Error('No active users found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ip of activeUsersIp) {
|
||||||
|
const success = await this.processBackupForIp(ip, userName);
|
||||||
|
if (!success) {
|
||||||
|
throw new Error(`Failed to retrieve backup from ${ip}`);
|
||||||
|
}
|
||||||
|
console.log(`Backup retrieved successfully from ${ip}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
parentPort?.postMessage({ success: true, message: 'Backup retrieval completed successfully.' });
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Error in BackupRetrievalWorker:', error);
|
||||||
|
parentPort?.postMessage({ success: false, message: `A problem occurred: ${error.message}` });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async processBackupForIp(ip: string, userName: string): Promise<boolean> {
|
private async processBackupForIp(ip: string, userName: string): Promise<boolean> {
|
||||||
const tcpClient = new TcpClient(this.clientPort);
|
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort);
|
||||||
tcpClient.openSocket(ip);
|
if (!await this.tcpCommunicator.connect()) {
|
||||||
|
return true;
|
||||||
try {
|
|
||||||
// Wait for the AES key to be set before continuing
|
|
||||||
const aesSet = await this.waitForAesKey(tcpClient);
|
|
||||||
if (!aesSet) {
|
|
||||||
console.error(`Timeout waiting for AES key on IP ${ip}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 1: Check if a backup exists for the user on this IP
|
|
||||||
const backupExists = await this.checkIfBackupExists(tcpClient, userName);
|
|
||||||
if (!backupExists) {
|
|
||||||
console.log(`No backup found for user ${userName} on IP ${ip}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Request the structure of the backup directory
|
|
||||||
const backupStructure = await this.requestBackupStructure(tcpClient, userName);
|
|
||||||
if (!backupStructure || Object.keys(backupStructure).length === 0) {
|
|
||||||
console.log(`No files found in backup structure for user ${userName} on IP ${ip}`);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: Request and retrieve each file from the backup
|
|
||||||
for (const relativeFilePath of Object.keys(backupStructure)) {
|
|
||||||
const fileRequestSuccess = await this.requestBackupFile(tcpClient, userName, relativeFilePath);
|
|
||||||
if (!fileRequestSuccess) {
|
|
||||||
console.error(`Failed to retrieve file ${relativeFilePath} from backup for user ${userName} on IP ${ip}`);
|
|
||||||
return false; // Stop if any file fails to be retrieved
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true; // All files retrieved successfully
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error during backup processing for IP ${ip}: ${error}`);
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
tcpClient.closeSocket(); // Ensure socket is closed
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const backupExists = await this.checkIfBackupExists(userName);
|
||||||
|
if (!backupExists) {
|
||||||
|
console.log(`No backup found for user ${userName} on IP ${ip}`);
|
||||||
|
await this.tcpCommunicator.disconnect();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const backupStructure = await this.requestBackupStructure(userName);
|
||||||
|
if (!backupStructure || Object.keys(backupStructure).length === 0) {
|
||||||
|
console.log(`No files found in backup structure for user ${userName} on IP ${ip}`);
|
||||||
|
await this.tcpCommunicator.disconnect();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const relativeFilePath of Object.keys(backupStructure)) {
|
||||||
|
const fileRequestSuccess = await this.requestBackupFile(userName, relativeFilePath);
|
||||||
|
if (!fileRequestSuccess) {
|
||||||
|
await this.tcpCommunicator.disconnect();
|
||||||
|
throw new Error(`Failed to retrieve file ${relativeFilePath} from backup for user ${userName} on IP ${ip}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.tcpCommunicator.disconnect();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for AES key to be set
|
private async checkIfBackupExists(userName: string): Promise<boolean> {
|
||||||
private async waitForAesKey(tcpClient: TcpClient, timeout: number = 10000): Promise<boolean> {
|
if (!this.tcpCommunicator) return false;
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const intervalId = setInterval(() => {
|
|
||||||
const elapsedTime = Date.now() - startTime;
|
|
||||||
|
|
||||||
if (tcpClient.isAesKeySet()) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
resolve(true); // AES key is set, we can proceed
|
|
||||||
} else if (elapsedTime > timeout) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
resolve(false); // Timeout reached, AES key not set
|
|
||||||
}
|
|
||||||
}, 500); // Check every 500ms
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for a message response with timeout
|
|
||||||
private async waitForMessageResponse(tcpClient: TcpClient, timeout: number = 10000): Promise<any> {
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
const intervalId = setInterval(() => {
|
|
||||||
const elapsedTime = Date.now() - startTime;
|
|
||||||
const response = tcpClient.getLastResult();
|
|
||||||
|
|
||||||
if (response) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
resolve(response); // Return the response
|
|
||||||
} else if (elapsedTime > timeout) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
resolve(null); // Timeout, no response
|
|
||||||
}
|
|
||||||
}, 500); // Check every 500ms
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the backup exists for the user on the remote IP
|
|
||||||
private async checkIfBackupExists(tcpClient: TcpClient, userName: string): Promise<boolean> {
|
|
||||||
const metaInfo = { name: userName };
|
const metaInfo = { name: userName };
|
||||||
await tcpClient.sendMessage(operationCodes.IS_BACKUP_CREATED, metaInfo);
|
if (!await this.tcpCommunicator.sendMessage(operationCodes.IS_BACKUP_CREATED, metaInfo)) return false;
|
||||||
|
|
||||||
const response = await this.waitForMessageResponse(tcpClient);
|
const response = await this.waitForResponse();
|
||||||
return response?.metaInfo?.backupExists === true;
|
return response?.metaInfo?.backupExists === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request the backup structure from the remote IP
|
private async requestBackupStructure(userName: string): Promise<any> {
|
||||||
private async requestBackupStructure(tcpClient: TcpClient, userName: string): Promise<any> {
|
if (!this.tcpCommunicator) return false;
|
||||||
const metaInfo = { name: userName };
|
|
||||||
await tcpClient.sendMessage(operationCodes.GET_BACKUP_STRUCTURE, metaInfo);
|
|
||||||
|
|
||||||
const response = await this.waitForMessageResponse(tcpClient);
|
const metaInfo = { name: userName };
|
||||||
return response?.metaInfo?.structure ? response.metaInfo.structure : null;
|
if (!await this.tcpCommunicator.sendMessage(operationCodes.GET_BACKUP_STRUCTURE, metaInfo)) return null;
|
||||||
|
|
||||||
|
const response = await this.waitForResponse();
|
||||||
|
return response?.metaInfo?.structure || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request a file from the backup and wait for it to be decrypted and stored
|
private async requestBackupFile(userName: string, relativeFilePath: string): Promise<boolean> {
|
||||||
private async requestBackupFile(tcpClient: TcpClient, userName: string, relativeFilePath: string): Promise<boolean> {
|
if (!this.tcpCommunicator) return false;
|
||||||
const metaInfo = { name: userName, relativeFilePath };
|
|
||||||
await tcpClient.sendMessage(operationCodes.REQ_FILE_FROM_BACKUP, metaInfo);
|
|
||||||
|
|
||||||
const response = await this.waitForMessageResponse(tcpClient);
|
const metaInfo = { name: userName, relativeFilePath };
|
||||||
|
if (!await this.tcpCommunicator.sendMessage(operationCodes.REQ_FILE_FROM_BACKUP, metaInfo)) return false;
|
||||||
|
|
||||||
|
const response = await this.waitForResponse();
|
||||||
if (response?.operationCode === operationCodes.OK && response.metaInfo && response.fileContent) {
|
if (response?.operationCode === operationCodes.OK && response.metaInfo && response.fileContent) {
|
||||||
return this.saveFile(response.metaInfo.relativeFilePath, response.fileContent);
|
return this.saveFile(response.metaInfo.relativeFilePath, response.fileContent.toString('base64'));
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt the file content using AES-256-CBC and save the decrypted file
|
|
||||||
private saveFile(relativeFilePath: string, fileContent: string): boolean {
|
private saveFile(relativeFilePath: string, fileContent: string): boolean {
|
||||||
if (!this.encryptionKey || !this.iv) {
|
if (!this.encryptionKey || !this.iv) {
|
||||||
console.error('Encryption key or IV is not set.');
|
throw new Error('Encryption key or IV is not set.');
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the base64-encoded file content into a buffer
|
|
||||||
let encryptedBuffer: Buffer;
|
let encryptedBuffer: Buffer;
|
||||||
try {
|
try {
|
||||||
encryptedBuffer = Buffer.from(fileContent, 'base64');
|
encryptedBuffer = Buffer.from(fileContent, 'base64');
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('Error decoding base64 file content:', error);
|
throw new Error(`Error decoding base64 file content: ${error.message}`);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt the file content
|
|
||||||
let decryptedContent: Buffer;
|
let decryptedContent: Buffer;
|
||||||
try {
|
try {
|
||||||
const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv);
|
const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv);
|
||||||
decryptedContent = Buffer.concat([decipher.update(encryptedBuffer), decipher.final()]);
|
decryptedContent = Buffer.concat([decipher.update(encryptedBuffer), decipher.final()]);
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
console.error('Error decrypting file:', error);
|
throw new Error(`Error decrypting file: ${error.message}`);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the decrypted file content
|
|
||||||
const fullFilePath = path.join(this.destinationPath, relativeFilePath);
|
const fullFilePath = path.join(this.destinationPath, relativeFilePath);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dirPath = path.dirname(fullFilePath);
|
const dirPath = path.dirname(fullFilePath);
|
||||||
if (!fs.existsSync(dirPath)) {
|
if (!fs.existsSync(dirPath)) {
|
||||||
fs.mkdirSync(dirPath, { recursive: true }); // Ensure directory exists
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFileSync(fullFilePath, decryptedContent);
|
fs.writeFileSync(fullFilePath, decryptedContent);
|
||||||
console.log(`File saved successfully: ${fullFilePath}`);
|
console.log(`File saved successfully: ${fullFilePath}`);
|
||||||
return true;
|
return true;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`Error saving file ${relativeFilePath}: ${error.message}`);
|
throw new Error(`Error saving file ${relativeFilePath}: ${error.message}`);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async waitForResponse(): Promise<ParsedMessage | null> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const idResponseCheck = setInterval(async () => {
|
||||||
|
if (!this.tcpCommunicator) return null;
|
||||||
|
if (this.tcpCommunicator.hasResponseArrived()) {
|
||||||
|
clearInterval(idResponseCheck);
|
||||||
|
resolve(this.tcpCommunicator.getLastResult());
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { TcpClient } from '../network/tcp/tcp_client'; // Assuming this class exists
|
import { TcpCommunicator } from './tcp_communicator'; // Using TcpCommunicator instead of TcpClient
|
||||||
import { operationCodes } from '../network/operation_codes';
|
import { operationCodes } from '../network/operation_codes';
|
||||||
import { JsonManager } from './json_manager'; // Manages JSON configurations
|
import { JsonManager } from './json_manager'; // Manages JSON configurations
|
||||||
import { MemoryManager } from './memory_manager'; // Manages in-memory data structures
|
import { MemoryManager } from './memory_manager'; // Manages in-memory data structures
|
||||||
|
import { ParsedMessage } from "../network/message_handler";
|
||||||
|
|
||||||
export class DepartmentSharer {
|
export class DepartmentSharer {
|
||||||
private userConfig: JsonManager;
|
private userConfig: JsonManager;
|
||||||
@@ -11,6 +12,8 @@ export class DepartmentSharer {
|
|||||||
private memoryManager: MemoryManager; // To read the department files
|
private memoryManager: MemoryManager; // To read the department files
|
||||||
private departmentDirectory: string | null;
|
private departmentDirectory: string | null;
|
||||||
private readonly clientPort: number;
|
private readonly clientPort: number;
|
||||||
|
private isBusy: boolean = false; // Busy flag to prevent simultaneous sharing
|
||||||
|
private tcpCommunicator: TcpCommunicator | null = null; // For each user connection
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
userConfigPath: string,
|
userConfigPath: string,
|
||||||
@@ -20,7 +23,7 @@ export class DepartmentSharer {
|
|||||||
) {
|
) {
|
||||||
this.userConfig = new JsonManager(userConfigPath);
|
this.userConfig = new JsonManager(userConfigPath);
|
||||||
this.applicationInfo = new JsonManager(applicationInfoPath);
|
this.applicationInfo = new JsonManager(applicationInfoPath);
|
||||||
this.memoryManager = new MemoryManager(memoryManagerPath); // To retrieve files
|
this.memoryManager = new MemoryManager(memoryManagerPath);
|
||||||
this.clientPort = clientPort;
|
this.clientPort = clientPort;
|
||||||
this.departmentDirectory = null;
|
this.departmentDirectory = null;
|
||||||
}
|
}
|
||||||
@@ -28,7 +31,11 @@ export class DepartmentSharer {
|
|||||||
// Start sharing files with the department every minute
|
// Start sharing files with the department every minute
|
||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await this.shareFilesWithDepartment(); // Retry every minute
|
if (!this.isBusy) {
|
||||||
|
this.isBusy = true;
|
||||||
|
await this.shareFilesWithDepartment();
|
||||||
|
this.isBusy = false;
|
||||||
|
}
|
||||||
}, 10000); // 10-second interval for testing
|
}, 10000); // 10-second interval for testing
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +68,6 @@ export class DepartmentSharer {
|
|||||||
|
|
||||||
// Filter users who belong to the same department
|
// Filter users who belong to the same department
|
||||||
const departmentUsers = activeUsers.filter((user: any) => user.user_info.departmentId === departmentId);
|
const departmentUsers = activeUsers.filter((user: any) => user.user_info.departmentId === departmentId);
|
||||||
|
|
||||||
if (departmentUsers.length === 0) {
|
if (departmentUsers.length === 0) {
|
||||||
console.log('No users found in the same department.');
|
console.log('No users found in the same department.');
|
||||||
return;
|
return;
|
||||||
@@ -83,15 +89,41 @@ export class DepartmentSharer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send each file to every department user
|
// Iterate over all department users and perform the operations
|
||||||
await this.shareFilesWithUsers(departmentUsers, departmentFiles.structure, userName);
|
for (const user of departmentUsers) {
|
||||||
|
const userIp = user.ip;
|
||||||
|
this.tcpCommunicator = new TcpCommunicator(userIp, this.clientPort);
|
||||||
|
|
||||||
// After sending all files, clear the department directory
|
if(await this.tcpCommunicator.connect()) continue;
|
||||||
await this.clearDepartmentDirectory(departmentUsers);
|
|
||||||
|
// First clear the department directory
|
||||||
|
const clearSuccess = await this.clearDepartmentDirectory();
|
||||||
|
if (clearSuccess) {
|
||||||
|
await this.sendFilesToUser(departmentFiles.structure, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.tcpCommunicator.disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the files to the users in the department
|
// Clear the department directory for a user
|
||||||
private async shareFilesWithUsers(users: any[], files: { [key: string]: string }, userName: string): Promise<void> {
|
private async clearDepartmentDirectory(): Promise<boolean> {
|
||||||
|
if(!this.tcpCommunicator) return false;
|
||||||
|
|
||||||
|
if (!await this.tcpCommunicator.sendMessage(operationCodes.CLEAR_DEPARTMENT)) return false;
|
||||||
|
const response = await this.waitForResponse();
|
||||||
|
|
||||||
|
if (!response || response.operationCode !== operationCodes.OK){
|
||||||
|
console.error('Failed to clear the department directory.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the files to a user in the department
|
||||||
|
private async sendFilesToUser(files: { [key: string]: string }, userName: string): Promise<void> {
|
||||||
|
if(!this.tcpCommunicator) return;
|
||||||
const unsentFiles = Object.keys(files); // Keep track of unsent files
|
const unsentFiles = Object.keys(files); // Keep track of unsent files
|
||||||
|
|
||||||
for (const fileName of unsentFiles) {
|
for (const fileName of unsentFiles) {
|
||||||
@@ -116,163 +148,29 @@ export class DepartmentSharer {
|
|||||||
relativeFilePath // Use the relative path to preserve directory structure
|
relativeFilePath // Use the relative path to preserve directory structure
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send the file to each user in the same department
|
// Send the file
|
||||||
for (const user of users) {
|
if (!await this.tcpCommunicator.sendMessage(operationCodes.DEPARTMENT_FILE, metaInfo, Buffer.from(fileContent))) return;
|
||||||
const userIp = user.ip;
|
|
||||||
const tcpClient = new TcpClient(this.clientPort);
|
|
||||||
|
|
||||||
// Open a connection to the user's IP
|
const response = await this.waitForResponse();
|
||||||
tcpClient.openSocket(userIp);
|
if (!response || response.operationCode !== operationCodes.OK) {
|
||||||
|
console.error(`Failed to send file: ${fileName}`);
|
||||||
try {
|
return;
|
||||||
// Wait for the AES key and send the file
|
|
||||||
const success = await this.waitForAesKeyAndSendFile(tcpClient, operationCodes.DEPARTMENT_FILE, metaInfo, fileContent);
|
|
||||||
if (success) {
|
|
||||||
console.log(`File successfully sent: ${fileName} to user ${userIp}`);
|
|
||||||
unsentFiles.splice(unsentFiles.indexOf(fileName), 1); // Remove successfully sent file
|
|
||||||
tcpClient.closeSocket(); // Close the connection after sending
|
|
||||||
break; // Move to the next file after a successful send
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to send file: ${fileName} to IP: ${userIp}. Error: ${error}`);
|
|
||||||
tcpClient.closeSocket(); // Ensure socket is closed on error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (unsentFiles.length > 0) {
|
unsentFiles.splice(unsentFiles.indexOf(fileName), 1);
|
||||||
console.log('Some files could not be sent, retrying later.');
|
await this.tcpCommunicator.disconnect();
|
||||||
} else {
|
|
||||||
console.log('All files shared successfully.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the department directory for all users after sharing
|
private async waitForResponse(): Promise<ParsedMessage | null> {
|
||||||
private async clearDepartmentDirectory(users: any[]): Promise<void> {
|
|
||||||
for (const user of users) {
|
|
||||||
const userIp = user.ip;
|
|
||||||
const tcpClient = new TcpClient(this.clientPort);
|
|
||||||
|
|
||||||
// Open a connection to the user's IP
|
|
||||||
tcpClient.openSocket(userIp);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const success = await this.waitForAesKeyAndSendClearDepartment(tcpClient, operationCodes.CLEAR_DEPARTMENT);
|
|
||||||
if (success) {
|
|
||||||
console.log(`Department directory cleared successfully for user ${userIp}`);
|
|
||||||
} else {
|
|
||||||
console.error(`Failed to clear department directory for user ${userIp}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error clearing department directory for user ${userIp}:`, error);
|
|
||||||
} finally {
|
|
||||||
tcpClient.closeSocket(); // Ensure the socket is closed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for AES key to be set, send the file, and wait for the response
|
|
||||||
private async waitForAesKeyAndSendFile(tcpClient: TcpClient, operationCode: string, metaInfo: { [key: string]: any }, fileContent: Buffer, checkInterval: number = 500, timeout: number = 10000): Promise<boolean> {
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const intervalId = setInterval(async () => {
|
|
||||||
const elapsedTime = Date.now() - startTime;
|
|
||||||
|
|
||||||
// Check if AES key is set, if timeout occurs, reject
|
|
||||||
if (!tcpClient.isAesKeySet()) {
|
|
||||||
if (elapsedTime > timeout) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
console.error('Timeout waiting for AES key.');
|
|
||||||
reject(new Error('Timeout waiting for AES key.'));
|
|
||||||
}
|
|
||||||
return; // Continue waiting for AES key
|
|
||||||
}
|
|
||||||
|
|
||||||
// AES key is set, send the file
|
|
||||||
clearInterval(intervalId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const success = await tcpClient.sendMessage(operationCode, metaInfo, fileContent);
|
|
||||||
if (!success) {
|
|
||||||
reject(new Error('Failed to send the file content.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for a response after sending the message
|
|
||||||
const responseReceived = await this.waitForMessageResponse(tcpClient, timeout);
|
|
||||||
if (!responseReceived) {
|
|
||||||
reject(new Error('Timeout waiting for the message response.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(true); // Everything went fine
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
}, checkInterval); // Check for AES key every `checkInterval`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for AES key to be set, send the clear department message, and wait for the response
|
|
||||||
private async waitForAesKeyAndSendClearDepartment(tcpClient: TcpClient, operationCode: string, checkInterval: number = 500, timeout: number = 10000): Promise<boolean> {
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const intervalId = setInterval(async () => {
|
|
||||||
const elapsedTime = Date.now() - startTime;
|
|
||||||
|
|
||||||
// Check if AES key is set, if timeout occurs, reject
|
|
||||||
if (!tcpClient.isAesKeySet()) {
|
|
||||||
if (elapsedTime > timeout) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
console.error('Timeout waiting for AES key.');
|
|
||||||
reject(new Error('Timeout waiting for AES key.'));
|
|
||||||
}
|
|
||||||
return; // Continue waiting for AES key
|
|
||||||
}
|
|
||||||
|
|
||||||
// AES key is set, send the clear department message
|
|
||||||
clearInterval(intervalId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const success = await tcpClient.sendMessage(operationCode, {});
|
|
||||||
if (!success) {
|
|
||||||
reject(new Error('Failed to send the clear department operation.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for a response after sending the message
|
|
||||||
const responseReceived = await this.waitForMessageResponse(tcpClient, timeout);
|
|
||||||
if (!responseReceived) {
|
|
||||||
reject(new Error('Timeout waiting for the message response.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(true); // Everything went fine
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
}, checkInterval); // Check for AES key every `checkInterval`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the response from the TCP client
|
|
||||||
private waitForMessageResponse(tcpClient: TcpClient, timeout: number = 10000): Promise<boolean> {
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const startTime = Date.now();
|
const idResponseCheck = setInterval(async () => {
|
||||||
const intervalId = setInterval(() => {
|
if (!this.tcpCommunicator) return null;
|
||||||
const response = tcpClient.getLastResult();
|
if (this.tcpCommunicator.hasResponseArrived()) {
|
||||||
const elapsedTime = Date.now() - startTime;
|
clearInterval(idResponseCheck);
|
||||||
|
resolve(this.tcpCommunicator.getLastResult()); // Resolve the response or null if not available
|
||||||
if (response?.operationCode === operationCodes.OK) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
resolve(true); // Message successfully received
|
|
||||||
} else if (elapsedTime > timeout) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
resolve(false); // No response after timeout
|
|
||||||
}
|
}
|
||||||
}, 100); // Check every 100ms
|
}, 100); // Check every 100 milliseconds if the response has arrived
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { QueueManager } from './queue_manager'; // Assume the QueueManager is in this path
|
import {QueueManager} from './queue_manager';
|
||||||
import { TcpClient } from '../network/tcp/tcp_client'; // Import your TcpClient class
|
import {TcpCommunicator} from "./tcp_communicator"; // Updated to use TcpCommunicator
|
||||||
import { operationCodes } from '../network/operation_codes';
|
import {operationCodes} from '../network/operation_codes';
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import {compareFnFileItemTask, FileItemTask} from "../interfaces/file_item_task";
|
import {compareFnFileItemTask, FileItemTask} from "../interfaces/file_item_task";
|
||||||
|
import {ParsedMessage} from "../network/message_handler";
|
||||||
|
|
||||||
interface FileSendTask {
|
interface FileSendTask {
|
||||||
ip: string;
|
ip: string;
|
||||||
@@ -14,45 +15,56 @@ interface FileSendTask {
|
|||||||
export class FileSharer {
|
export class FileSharer {
|
||||||
private queueManager: QueueManager<FileSendTask>;
|
private queueManager: QueueManager<FileSendTask>;
|
||||||
private readonly clientPort: number;
|
private readonly clientPort: number;
|
||||||
|
private isBusy: boolean;
|
||||||
|
private tcpCommunicator: TcpCommunicator | null = null;
|
||||||
|
|
||||||
constructor(queueFilePath: string, clientPort: number) {
|
constructor(queueFilePath: string, clientPort: number) {
|
||||||
|
|
||||||
|
|
||||||
this.queueManager = new QueueManager<FileItemTask>(queueFilePath, compareFnFileItemTask);
|
this.queueManager = new QueueManager<FileItemTask>(queueFilePath, compareFnFileItemTask);
|
||||||
this.clientPort = clientPort;
|
this.clientPort = clientPort;
|
||||||
|
this.isBusy = false; // Initialize the busy flag
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start processing the file queue
|
// Start processing the file queue
|
||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await this.processQueue(); // Process the queue at regular intervals
|
if (!this.isBusy) { // Check if the queue is already being processed
|
||||||
|
await this.processQueue(); // Process the queue at regular intervals
|
||||||
|
}
|
||||||
}, 10000); // 10 seconds interval
|
}, 10000); // 10 seconds interval
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method to process the queue
|
// Method to process the queue
|
||||||
private async processQueue(): Promise<void> {
|
private async processQueue(): Promise<void> {
|
||||||
|
if (this.isBusy) {
|
||||||
|
console.log("Queue is already being processed. Skipping this interval.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isBusy = true; // Set busy flag to true before starting
|
||||||
|
|
||||||
while (!this.queueManager.isEmpty()) {
|
while (!this.queueManager.isEmpty()) {
|
||||||
const task = this.queueManager.peek();
|
const task = this.queueManager.peek();
|
||||||
|
|
||||||
if (task) {
|
if (task) {
|
||||||
|
console.log(task);
|
||||||
const success = await this.sendFile(task);
|
const success = await this.sendFile(task);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
console.error(`Failed to send file: ${task.path} to IP: ${task.ip}. Re-adding to queue.`);
|
console.error(`Failed to send file: ${task.path} to IP: ${task.ip}. Re-adding to queue.`);
|
||||||
this.queueManager.dequeue();
|
this.queueManager.dequeue();
|
||||||
this.queueManager.enqueue(task); // Re-add to queue if failed
|
this.queueManager.enqueue(task); // Re-add to queue if failed
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
console.log(`File sent successfully: ${task.path} to IP: ${task.ip}.`);
|
console.log(`File sent successfully: ${task.path} to IP: ${task.ip}.`);
|
||||||
this.queueManager.dequeue();
|
this.queueManager.dequeue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.isBusy = false; // Reset busy flag after the queue is processed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method to send the file to a specific IP using the TcpClient
|
// Method to send the file to a specific IP using TcpCommunicator
|
||||||
private async sendFile(task: FileSendTask): Promise<boolean> {
|
private async sendFile(task: FileSendTask): Promise<boolean> {
|
||||||
const { ip, path: filePath, userName } = task;
|
const {ip, path: filePath, userName} = task;
|
||||||
|
|
||||||
// Ensure the file exists before attempting to send
|
// Ensure the file exists before attempting to send
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
@@ -63,7 +75,7 @@ export class FileSharer {
|
|||||||
// Read the file contents
|
// Read the file contents
|
||||||
const fileContent = fs.readFileSync(filePath);
|
const fileContent = fs.readFileSync(filePath);
|
||||||
|
|
||||||
// Extract the file name from the file path using path.basename (handles both Windows and Unix)
|
// Extract the file name from the file path using path.basename
|
||||||
const fileName = path.basename(filePath);
|
const fileName = path.basename(filePath);
|
||||||
|
|
||||||
const metaInfo = {
|
const metaInfo = {
|
||||||
@@ -71,82 +83,36 @@ export class FileSharer {
|
|||||||
relativeFilePath: fileName, // Use the file name instead of the full path
|
relativeFilePath: fileName, // Use the file name instead of the full path
|
||||||
};
|
};
|
||||||
|
|
||||||
const tcpClient = new TcpClient(this.clientPort);
|
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort);
|
||||||
tcpClient.openSocket(ip);
|
|
||||||
|
|
||||||
// Wait for the AES key and then send the file
|
if (!await this.tcpCommunicator.connect()) {
|
||||||
try {
|
console.error(`Failed to connect to IP: ${ip}`);
|
||||||
const sendSuccess = await this.waitForAesKeyAndSendFile(tcpClient, operationCodes.SHARE_FILE, metaInfo, fileContent, task);
|
|
||||||
tcpClient.closeSocket();
|
|
||||||
|
|
||||||
return sendSuccess;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error sending file: ${filePath} to IP: ${ip}. Error: ${error}`);
|
|
||||||
tcpClient.closeSocket();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`Sending file: ${filePath} to IP: ${ip}`);
|
||||||
|
|
||||||
|
if (!await this.tcpCommunicator.sendMessage(operationCodes.SHARE_FILE, metaInfo, fileContent)) return false;
|
||||||
|
|
||||||
|
const response = await this.waitForResponse();
|
||||||
|
if (!response || response.operationCode !== operationCodes.OK) {
|
||||||
|
console.error(`Failed to send file: ${filePath} to IP: ${ip}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.tcpCommunicator.disconnect();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for AES key to be set and send the file, handling the response
|
private async waitForResponse(): Promise<ParsedMessage | null> {
|
||||||
private async waitForAesKeyAndSendFile(tcpClient: TcpClient, operationCode: string, metaInfo: { [key: string]: any }, fileContent: Buffer, task: FileSendTask, checkInterval: number = 500, timeout: number = 10000): Promise<boolean> {
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const intervalId = setInterval(async () => {
|
|
||||||
const elapsedTime = Date.now() - startTime;
|
|
||||||
|
|
||||||
// Check if AES key is set, if timeout occurs, reject
|
|
||||||
if (!tcpClient.isAesKeySet()) {
|
|
||||||
if (elapsedTime > timeout) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
console.error('Timeout waiting for AES key.');
|
|
||||||
|
|
||||||
reject(new Error('Timeout waiting for AES key.'));
|
|
||||||
}
|
|
||||||
return; // Continue waiting for AES key
|
|
||||||
}
|
|
||||||
|
|
||||||
// AES key is set, send the message
|
|
||||||
clearInterval(intervalId);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const success = await tcpClient.sendMessage(operationCode, metaInfo, fileContent);
|
|
||||||
if (!success) {
|
|
||||||
reject(new Error('Failed to send the file content.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for a response after sending the message
|
|
||||||
const responseReceived = await this.waitForMessageResponse(tcpClient, timeout);
|
|
||||||
if (!responseReceived) {
|
|
||||||
reject(new Error('Timeout waiting for the message response.'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(true); // Everything went fine
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
}, checkInterval); // Check for AES key every `checkInterval`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method to wait for the response from the TCP client
|
|
||||||
private waitForMessageResponse(tcpClient: TcpClient, timeout: number = 10000): Promise<boolean> {
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const startTime = Date.now();
|
const idResponseCheck = setInterval(async () => {
|
||||||
const intervalId = setInterval(() => {
|
if (!this.tcpCommunicator) return null;
|
||||||
const response = tcpClient.getLastResult();
|
if (this.tcpCommunicator.hasResponseArrived()) {
|
||||||
const elapsedTime = Date.now() - startTime;
|
clearInterval(idResponseCheck);
|
||||||
|
resolve(this.tcpCommunicator.getLastResult()); // Resolve the response or null if not available
|
||||||
if (response?.operationCode === operationCodes.OK) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
resolve(true); // Message successfully received
|
|
||||||
} else if (elapsedTime > timeout) {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
resolve(false); // No response after timeout
|
|
||||||
}
|
}
|
||||||
}, 100); // Check every 100ms
|
}, 100); // Check every 100 milliseconds if the response has arrived
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
import { JsonManager } from './json_manager'; // Assuming you have this class for managing user/memory
|
|
||||||
import { WindowManager } from './window_manager'; // For managing app navigation
|
|
||||||
import { UdpClient } from '../network/udp/udp_client';
|
|
||||||
|
|
||||||
export class TaskScheduler {
|
|
||||||
private applicationInfo: JsonManager;
|
|
||||||
private windowManager: WindowManager;
|
|
||||||
private appStarted: boolean = false;
|
|
||||||
private intervalIds: NodeJS.Timeout[] = []; // Array to store interval IDs
|
|
||||||
|
|
||||||
constructor(applicationInfo: JsonManager, windowManager: WindowManager) {
|
|
||||||
this.applicationInfo = applicationInfo;
|
|
||||||
this.windowManager = windowManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to schedule the UC check task with dynamic UDP client creation
|
|
||||||
public startUCCheck(udpPort: number, okPage: string, errorPage: string, interval: number = 30000) {
|
|
||||||
const intervalId = setInterval(async () => {
|
|
||||||
try {
|
|
||||||
const udpClient = new UdpClient(udpPort); // Create UdpClient with the provided port
|
|
||||||
const aliveClients = await udpClient.getAliveClients();
|
|
||||||
const storedIp = await this.applicationInfo.readValue('serverIp');
|
|
||||||
const foundClient = aliveClients.length > 0;
|
|
||||||
|
|
||||||
if (foundClient) {
|
|
||||||
const ipAddress = aliveClients[0]; // Just using the first alive client
|
|
||||||
|
|
||||||
if (!storedIp || storedIp !== ipAddress) {
|
|
||||||
await this.applicationInfo.writeValue('serverIp', ipAddress);
|
|
||||||
if (!this.appStarted) {
|
|
||||||
await this.windowManager.changeContent(okPage);
|
|
||||||
}
|
|
||||||
this.appStarted = true;
|
|
||||||
} else if (!this.appStarted) {
|
|
||||||
await this.windowManager.changeContent(okPage);
|
|
||||||
this.appStarted = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await this.windowManager.changeContent(errorPage);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error checking UC:', err);
|
|
||||||
await this.windowManager.changeContent(errorPage);
|
|
||||||
}
|
|
||||||
}, interval);
|
|
||||||
|
|
||||||
this.intervalIds.push(intervalId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to schedule the IP lookup task, storing the active addresses in memory
|
|
||||||
public async startUserIPLookup(udpPort: number, interval: number = 30000) {
|
|
||||||
const serverIp = await this.applicationInfo.readValue('serverIp'); // Ensure it's awaited if it's an async function
|
|
||||||
const intervalId = setInterval(async () => {
|
|
||||||
try {
|
|
||||||
const udpClient = new UdpClient(udpPort); // Create a UDP client with the provided port
|
|
||||||
const activeIPs = await udpClient.getAliveClients(); // Get the list of active IPs
|
|
||||||
|
|
||||||
// Filter out the serverIp from the list of active clients
|
|
||||||
const filteredIPs = activeIPs.filter(ip => ip !== serverIp);
|
|
||||||
|
|
||||||
// Save the filtered IPs to 'users_ip'
|
|
||||||
await this.applicationInfo.writeValue('users_ip', filteredIPs);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error during user IP lookup:', err);
|
|
||||||
}
|
|
||||||
}, interval);
|
|
||||||
|
|
||||||
this.intervalIds.push(intervalId); // Store the interval ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to stop all tasks (UC check, user worker, etc.)
|
|
||||||
public stopAllTasks() {
|
|
||||||
this.intervalIds.forEach(clearInterval); // Clear all intervals
|
|
||||||
this.intervalIds = []; // Reset the array
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,6 +2,7 @@ import { JsonManager } from "./json_manager";
|
|||||||
import { MemoryManager } from "./memory_manager";
|
import { MemoryManager } from "./memory_manager";
|
||||||
import { operationCodes } from "../network/operation_codes";
|
import { operationCodes } from "../network/operation_codes";
|
||||||
import { TcpCommunicator } from "./tcp_communicator";
|
import { TcpCommunicator } from "./tcp_communicator";
|
||||||
|
import {ParsedMessage} from "../network/message_handler";
|
||||||
|
|
||||||
export class UsersInfoFetcher {
|
export class UsersInfoFetcher {
|
||||||
private applicationInfo: JsonManager;
|
private applicationInfo: JsonManager;
|
||||||
@@ -24,7 +25,7 @@ export class UsersInfoFetcher {
|
|||||||
async start(): Promise<void> {
|
async start(): Promise<void> {
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
await this.initialize(); // Re-run every minute
|
await this.initialize(); // Re-run every minute
|
||||||
}, 60000); // 1 minute interval
|
}, 5000); // 1 minute interval
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize and fetch user IPs and process users info
|
// Initialize and fetch user IPs and process users info
|
||||||
@@ -57,16 +58,19 @@ export class UsersInfoFetcher {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!await this.tcpCommunicator?.sendMessage(operationCodes.GET_USER_INFORMATION)){
|
||||||
|
await this.tcpCommunicator.disconnect();
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for the response for 10 seconds
|
// Wait for the response for 10 seconds
|
||||||
const response = await this.waitForResponse();
|
const response = await this.waitForResponse();
|
||||||
console.log(response);
|
|
||||||
|
|
||||||
// If a response is received and is successful, append it to usersInfo
|
// If a response is received and is successful, append it to usersInfo
|
||||||
if (response && response.metaInfo) {
|
if (response && response.metaInfo) {
|
||||||
console.log(`Response received from ${ip}:`, response);
|
|
||||||
usersInfo.push({
|
usersInfo.push({
|
||||||
ip: ip,
|
ip: ip,
|
||||||
user_info: response.metaInfo // Push the IP and user_info object into the array
|
user_info: response.metaInfo
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,31 +80,15 @@ export class UsersInfoFetcher {
|
|||||||
await this.updateActiveUsers(usersInfo); // Update active users information in the memory
|
await this.updateActiveUsers(usersInfo); // Update active users information in the memory
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the message once and wait for the response for a specific timeout
|
private async waitForResponse(): Promise<ParsedMessage | null> {
|
||||||
private async waitForResponse(timeout: number = 10000): Promise<any> {
|
|
||||||
// Send the message once
|
|
||||||
const status = await this.tcpCommunicator?.sendMessage(operationCodes.GET_USER_INFORMATION);
|
|
||||||
if (!status) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the response until the timeout
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const startTime = Date.now();
|
const idResponseCheck = setInterval(async () => {
|
||||||
|
if (!this.tcpCommunicator) return null;
|
||||||
const checkResponseInterval = setInterval(async () => {
|
if (this.tcpCommunicator.hasResponseArrived()) {
|
||||||
// Check if the message has been received
|
clearInterval(idResponseCheck);
|
||||||
if (this.tcpCommunicator?.hasResponseArrived()) {
|
resolve(this.tcpCommunicator.getLastResult()); // Resolve the response or null if not available
|
||||||
clearInterval(checkResponseInterval);
|
|
||||||
resolve(this.tcpCommunicator?.getLastResult()); // Return the response once it arrives
|
|
||||||
}
|
}
|
||||||
|
}, 100); // Check every 100 milliseconds if the response has arrived
|
||||||
// If the timeout is reached, stop checking and resolve with null
|
|
||||||
if (Date.now() - startTime > timeout) {
|
|
||||||
clearInterval(checkResponseInterval);
|
|
||||||
resolve(null);
|
|
||||||
}
|
|
||||||
}, 100); // Check every 100ms if the response has arrived
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,40 @@ export class WorkerManager {
|
|||||||
this.workers = []; // Initialize the array to store workers
|
this.workers = []; // Initialize the array to store workers
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the Connection Pool Worker
|
async startNetworkScannerWorker(udpPort: number, okPage: string, errorPage: string, applicationInfoPath: string): Promise<void> {
|
||||||
async startWatchersWorker(memoryManagerPath: string, applicationInfoPath: string): Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const worker = new Worker(path.join(this.pathToWorkerDir, 'watcher_worker.js'), {
|
const worker = new Worker(path.join(this.pathToWorkerDir, 'network_scanner_worker.js'), {
|
||||||
|
workerData: { udpPort, okPage, errorPage, applicationInfoPath }, // Pass necessary data to the worker
|
||||||
|
});
|
||||||
|
|
||||||
|
this.workers.push(worker); // Store the worker reference
|
||||||
|
|
||||||
|
worker.on('message', (data) => {
|
||||||
|
console.log(data);
|
||||||
|
if (data.type === 'changeContent') {
|
||||||
|
this.windowManager.changeContent(data.page);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
worker.on('error', (err) => {
|
||||||
|
console.error('Network Scanner Worker error:', err);
|
||||||
|
worker.terminate();
|
||||||
|
this.removeWorker(worker);
|
||||||
|
reject(err); // Reject the promise if there's an error
|
||||||
|
});
|
||||||
|
|
||||||
|
worker.on('exit', (code) => {
|
||||||
|
console.log(`Network Scanner Worker exited with code ${code}`);
|
||||||
|
this.removeWorker(worker); // Remove worker reference when it exits
|
||||||
|
resolve(); // Resolve when the worker exits cleanly
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the Connection Pool Worker
|
||||||
|
async startDirectoriesWatchersWorker(memoryManagerPath: string, applicationInfoPath: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const worker = new Worker(path.join(this.pathToWorkerDir, 'directories_watcher_worker.js'), {
|
||||||
workerData: { memoryManagerPath, applicationInfoPath }, // Pass the port to the worker
|
workerData: { memoryManagerPath, applicationInfoPath }, // Pass the port to the worker
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -110,17 +140,18 @@ export class WorkerManager {
|
|||||||
clientPort: number,
|
clientPort: number,
|
||||||
destinationPath: string
|
destinationPath: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const worker = new Worker(path.join(this.pathToWorkerDir, 'backup_retrieval_worker.js'), {
|
const worker = new Worker(path.join(this.pathToWorkerDir, 'backup_retrieval_worker.js'), {
|
||||||
workerData: { userConfigPath, applicationInfoPath, clientPort, destinationPath }, // Pass parameters to the worker
|
workerData: { userConfigPath, applicationInfoPath, clientPort, destinationPath }, // Pass parameters to the worker
|
||||||
});
|
});
|
||||||
|
|
||||||
this.workers.push(worker); // Store the worker reference
|
this.workers.push(worker); // Store the worker reference
|
||||||
|
|
||||||
worker.on('message', (data) => {
|
worker.on('message', async (data) => {
|
||||||
console.log('Backup Retrieval Worker message:', data);
|
console.log('Backup Retrieval Worker message:', data);
|
||||||
this.windowManager.changeContent('main_menu');
|
await new Promise((resolve) => setTimeout(resolve, 3000)); // Wait for 1 second
|
||||||
this.windowManager.showAlert(data.message);
|
await this.windowManager.changeContent('main_menu');
|
||||||
|
await this.windowManager.showAlert(data.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
worker.on('error', (err) => {
|
worker.on('error', (err) => {
|
||||||
@@ -138,6 +169,36 @@ export class WorkerManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method to start the Announcement Worker
|
||||||
|
async startAnnouncementWorker(applicationInfoPath: string, clientPort: number, message: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const worker = new Worker(path.join(this.pathToWorkerDir, 'send_announcement_worker.js'), {
|
||||||
|
workerData: { applicationInfoPath, clientPort, message }, // Pass necessary data to the worker
|
||||||
|
});
|
||||||
|
|
||||||
|
this.workers.push(worker); // Store the worker reference
|
||||||
|
|
||||||
|
worker.on('message', async (data) => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 3000)); // Wait for 1 second
|
||||||
|
this.windowManager.changeContent('main_menu');
|
||||||
|
this.windowManager.showAlert(`${data.message}`)
|
||||||
|
});
|
||||||
|
|
||||||
|
worker.on('error', (err) => {
|
||||||
|
console.error('Announcement Worker error:', err);
|
||||||
|
worker.terminate();
|
||||||
|
this.removeWorker(worker);
|
||||||
|
reject(err); // Reject the promise if there's an error
|
||||||
|
});
|
||||||
|
|
||||||
|
worker.on('exit', (code) => {
|
||||||
|
console.log(`Announcement Worker exited with code ${code}`);
|
||||||
|
this.removeWorker(worker); // Remove worker reference when it exits
|
||||||
|
resolve(); // Resolve when the worker exits cleanly
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Close all running workers
|
// Close all running workers
|
||||||
closeAllWorkers(): void {
|
closeAllWorkers(): void {
|
||||||
console.log('Terminating all running workers...');
|
console.log('Terminating all running workers...');
|
||||||
|
|||||||
@@ -7,20 +7,20 @@ import {WorkerManager} from "../helpers/worker_manager";
|
|||||||
import {DirectoryWatcher} from "../helpers/directory_watcher";
|
import {DirectoryWatcher} from "../helpers/directory_watcher";
|
||||||
import {QueueManager} from "../helpers/queue_manager";
|
import {QueueManager} from "../helpers/queue_manager";
|
||||||
import {compareFnFileItemTask, FileItemTask} from "../interfaces/file_item_task";
|
import {compareFnFileItemTask, FileItemTask} from "../interfaces/file_item_task";
|
||||||
import {TaskScheduler} from "../helpers/task_scheduler";
|
|
||||||
import {WindowManager} from "../helpers/window_manager";
|
import {WindowManager} from "../helpers/window_manager";
|
||||||
import {JsonManager} from "../helpers/json_manager";
|
import {JsonManager} from "../helpers/json_manager";
|
||||||
import {MemoryManager} from "../helpers/memory_manager";
|
import {MemoryManager} from "../helpers/memory_manager";
|
||||||
import {TcpCommunicator} from "../helpers/tcp_communicator";
|
import {TcpCommunicator} from "../helpers/tcp_communicator";
|
||||||
|
|
||||||
import {operationCodes} from "../network/operation_codes";
|
import {operationCodes} from "../network/operation_codes";
|
||||||
|
import os from "os";
|
||||||
|
|
||||||
// Load environment variables
|
// Load environment variables
|
||||||
dotenv.config({ path: path.join(__dirname, '..', '..', '.env') });
|
dotenv.config({ path: path.join(__dirname, '..', '..', '.env') });
|
||||||
|
|
||||||
const UDP_PORT = process.env.UDP_PORT ? parseInt(process.env.UDP_PORT) : 41233;
|
const UDP_PORT = process.env.UDP_PORT ? parseInt(process.env.UDP_PORT) : 41233;
|
||||||
const TCP_PORT = process.env.TCP_PORT ? parseInt(process.env.TCP_PORT) : 41234;
|
const TCP_PORT = process.env.TCP_PORT ? parseInt(process.env.TCP_PORT) : 41234;
|
||||||
const HOST = process.env.HOST || '0.0.0.0';
|
const HOST = getLocalIp();
|
||||||
|
|
||||||
let mainWindow: BrowserWindow | null = null;
|
let mainWindow: BrowserWindow | null = null;
|
||||||
let windowManager: WindowManager | null = null;
|
let windowManager: WindowManager | null = null;
|
||||||
@@ -29,7 +29,6 @@ let userConfig: JsonManager | null = null;
|
|||||||
let applicationInfo: JsonManager | null = null;
|
let applicationInfo: JsonManager | null = null;
|
||||||
let memoryManager: MemoryManager | null = null;
|
let memoryManager: MemoryManager | null = null;
|
||||||
let workerManager: WorkerManager | null = null;
|
let workerManager: WorkerManager | null = null;
|
||||||
let taskScheduler: TaskScheduler | null = null;
|
|
||||||
let backupDirectoryManager: DirectoryWatcher | null = null;
|
let backupDirectoryManager: DirectoryWatcher | null = null;
|
||||||
let departmentShareManager: DirectoryWatcher | null = null;
|
let departmentShareManager: DirectoryWatcher | null = null;
|
||||||
let sendFileQueue: QueueManager<FileItemTask> | null = null;
|
let sendFileQueue: QueueManager<FileItemTask> | null = null;
|
||||||
@@ -62,11 +61,6 @@ async function cleanupAndExit() {
|
|||||||
departmentShareManager.closeWatcher(); // Add this method to DirectoryWatcher to close the watcher
|
departmentShareManager.closeWatcher(); // Add this method to DirectoryWatcher to close the watcher
|
||||||
}
|
}
|
||||||
|
|
||||||
if(taskScheduler){
|
|
||||||
console.log('Stopping all tasks...');
|
|
||||||
taskScheduler.stopAllTasks()
|
|
||||||
}
|
|
||||||
|
|
||||||
if(workerManager){
|
if(workerManager){
|
||||||
console.log('Terminating all workers...');
|
console.log('Terminating all workers...');
|
||||||
workerManager.closeAllWorkers()
|
workerManager.closeAllWorkers()
|
||||||
@@ -86,6 +80,21 @@ async function ensureDirectoryExists(dirPath: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLocalIp() {
|
||||||
|
const interfaces = os.networkInterfaces();
|
||||||
|
for (let interfaceName in interfaces) {
|
||||||
|
const addresses = interfaces[interfaceName];
|
||||||
|
if(!addresses) continue;
|
||||||
|
for (let address of addresses) {
|
||||||
|
// Filter for IPv4 and ignore internal (127.0.0.1) addresses
|
||||||
|
if (address.family === 'IPv4' && !address.internal) {
|
||||||
|
return address.address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ''; // Fallback if no IP is found
|
||||||
|
}
|
||||||
|
|
||||||
app.whenReady().then(async () => {
|
app.whenReady().then(async () => {
|
||||||
const title = 'Application';
|
const title = 'Application';
|
||||||
const mainScreen = require('electron').screen.getPrimaryDisplay();
|
const mainScreen = require('electron').screen.getPrimaryDisplay();
|
||||||
@@ -103,6 +112,8 @@ app.whenReady().then(async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
mainWindow.removeMenu();
|
||||||
|
|
||||||
await ensureDirectoryExists(pathToJsons);
|
await ensureDirectoryExists(pathToJsons);
|
||||||
await ensureDirectoryExists(pathToClientsBackups);
|
await ensureDirectoryExists(pathToClientsBackups);
|
||||||
|
|
||||||
@@ -112,14 +123,16 @@ app.whenReady().then(async () => {
|
|||||||
memoryManager = new MemoryManager(path.join(pathToJsons, 'memory.json'));
|
memoryManager = new MemoryManager(path.join(pathToJsons, 'memory.json'));
|
||||||
sendFileQueue = new QueueManager(path.join(pathToJsons, 'sendFileTasks.json'), compareFnFileItemTask);
|
sendFileQueue = new QueueManager(path.join(pathToJsons, 'sendFileTasks.json'), compareFnFileItemTask);
|
||||||
|
|
||||||
taskScheduler = new TaskScheduler(applicationInfo, windowManager);
|
|
||||||
workerManager = new WorkerManager(pathToWorkerDir, windowManager);
|
workerManager = new WorkerManager(pathToWorkerDir, windowManager);
|
||||||
|
|
||||||
await userConfig.writeValue('app_type', 'ceo');
|
await userConfig.writeValue('app_type', 'ceo');
|
||||||
await applicationInfo.writeValue('users_ip', []);
|
await applicationInfo.writeValue('users_ip', []);
|
||||||
|
await applicationInfo.writeValue('serverIp', '');
|
||||||
|
await applicationInfo.writeValue('announcement', '');
|
||||||
await memoryManager.resetFile();
|
await memoryManager.resetFile();
|
||||||
|
|
||||||
workerManager.startWatchersWorker(path.join(pathToJsons, 'memory.json'), path.join(pathToJsons, 'application.json'));
|
workerManager.startNetworkScannerWorker(UDP_PORT, 'login', 'uc_not_found', path.join(pathToJsons, 'application.json'));
|
||||||
|
workerManager.startDirectoriesWatchersWorker(path.join(pathToJsons, 'memory.json'), path.join(pathToJsons, 'application.json'));
|
||||||
workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT);
|
workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT);
|
||||||
workerManager.startResourceCoordinatorWorker(
|
workerManager.startResourceCoordinatorWorker(
|
||||||
path.join(pathToJsons, 'userConfig.json'),
|
path.join(pathToJsons, 'userConfig.json'),
|
||||||
@@ -129,10 +142,6 @@ app.whenReady().then(async () => {
|
|||||||
TCP_PORT
|
TCP_PORT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
taskScheduler.startUCCheck(UDP_PORT, 'login', 'uc_not_found');
|
|
||||||
taskScheduler.startUserIPLookup(UDP_PORT);
|
|
||||||
|
|
||||||
registerIPCHandlers();
|
registerIPCHandlers();
|
||||||
|
|
||||||
await windowManager.changeContent('welcome');
|
await windowManager.changeContent('welcome');
|
||||||
@@ -156,6 +165,43 @@ app.on('before-quit', async () => {
|
|||||||
|
|
||||||
// Register IPC handlers
|
// Register IPC handlers
|
||||||
function registerIPCHandlers() {
|
function registerIPCHandlers() {
|
||||||
|
// ResetApplicationPreferences IPC Handlers
|
||||||
|
ipcMain.handle('reset-application-preferences', async () => {
|
||||||
|
if(applicationInfo) {
|
||||||
|
const serverIp = await applicationInfo.readValue('serverIp');
|
||||||
|
await applicationInfo.resetFile();
|
||||||
|
await applicationInfo.writeValue('serverIp', serverIp);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(userConfig) {
|
||||||
|
await userConfig.resetFile();
|
||||||
|
await userConfig.writeValue('app_type', 'ceo');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(memoryManager) {
|
||||||
|
await memoryManager.resetFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(sendFileQueue) {
|
||||||
|
sendFileQueue.clearQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(workerManager) {
|
||||||
|
workerManager.closeAllWorkers();
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||||
|
workerManager.startNetworkScannerWorker(UDP_PORT, 'login', 'uc_not_found', path.join(pathToJsons, 'application.json'));
|
||||||
|
workerManager.startDirectoriesWatchersWorker(path.join(pathToJsons, 'memory.json'), path.join(pathToJsons, 'application.json'));
|
||||||
|
workerManager.startServersWorker(HOST, UDP_PORT, TCP_PORT);
|
||||||
|
workerManager.startResourceCoordinatorWorker(
|
||||||
|
path.join(pathToJsons, 'userConfig.json'),
|
||||||
|
path.join(pathToJsons, 'application.json'),
|
||||||
|
path.join(pathToJsons, 'memory.json'),
|
||||||
|
path.join(pathToJsons, 'sendFileTasks.json'),
|
||||||
|
TCP_PORT
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Window Manager IPC Handlers
|
// Window Manager IPC Handlers
|
||||||
ipcMain.handle('show-alert', async (event: IpcMainInvokeEvent, message: string) => {
|
ipcMain.handle('show-alert', async (event: IpcMainInvokeEvent, message: string) => {
|
||||||
if (!windowManager) throw new Error('WindowManager is not initialized.');
|
if (!windowManager) throw new Error('WindowManager is not initialized.');
|
||||||
@@ -251,7 +297,11 @@ function registerIPCHandlers() {
|
|||||||
|
|
||||||
ipcMain.handle('reset-application-json-files', async () => {
|
ipcMain.handle('reset-application-json-files', async () => {
|
||||||
if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.');
|
if (!applicationInfo) throw new Error('ApplicationInfo is not initialized.');
|
||||||
return applicationInfo.resetFile();
|
const serverIp = await applicationInfo.readValue('serverIp');
|
||||||
|
await applicationInfo.resetFile();
|
||||||
|
if(serverIp) {
|
||||||
|
await applicationInfo.writeValue('serverIp', serverIp);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('remove-application-json-files-key', async (_event: IpcMainInvokeEvent, key: string) => {
|
ipcMain.handle('remove-application-json-files-key', async (_event: IpcMainInvokeEvent, key: string) => {
|
||||||
@@ -280,13 +330,18 @@ function registerIPCHandlers() {
|
|||||||
return memoryManager.removeMetaInformation(id);
|
return memoryManager.removeMetaInformation(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('memory-reset', async () => {
|
||||||
|
if (!memoryManager) throw new Error('MemoryManager is not initialized.');
|
||||||
|
return memoryManager.resetFile();
|
||||||
|
});
|
||||||
|
|
||||||
// Queue IPC Handlers
|
// Queue IPC Handlers
|
||||||
ipcMain.handle('add-task-to-send-file-queue', async (event: IpcMainInvokeEvent, task: FileItemTask) => {
|
ipcMain.handle('add-task-to-send-file-queue', async (event: IpcMainInvokeEvent, task: FileItemTask) => {
|
||||||
if (!sendFileQueue) throw new Error('SendFileQueue is not initialized.');
|
if (!sendFileQueue) throw new Error('SendFileQueue is not initialized.');
|
||||||
sendFileQueue.enqueue(task);
|
sendFileQueue.enqueue(task);
|
||||||
});
|
});
|
||||||
|
|
||||||
// BackupRetrievalWorker IPC Handler
|
// Workers IPC Handlers
|
||||||
ipcMain.handle('start-backup-retrieval', async (_event: IpcMainInvokeEvent, destinationPath: string) => {
|
ipcMain.handle('start-backup-retrieval', async (_event: IpcMainInvokeEvent, destinationPath: string) => {
|
||||||
if (!workerManager) throw new Error('WorkerManager is not initialized.');
|
if (!workerManager) throw new Error('WorkerManager is not initialized.');
|
||||||
return workerManager.startBackupRetrievalWorker(
|
return workerManager.startBackupRetrievalWorker(
|
||||||
@@ -296,4 +351,13 @@ function registerIPCHandlers() {
|
|||||||
destinationPath
|
destinationPath
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('start-announcement-worker', async (_event: IpcMainInvokeEvent, message: string) => {
|
||||||
|
if (!workerManager) throw new Error('WorkerManager is not initialized.');
|
||||||
|
return workerManager.startAnnouncementWorker(
|
||||||
|
path.join(pathToJsons, 'application.json'),
|
||||||
|
TCP_PORT,
|
||||||
|
message
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
readMemoryEntry: (id: string): Promise<any> => ipcRenderer.invoke('memory-read-entry', id),
|
readMemoryEntry: (id: string): Promise<any> => ipcRenderer.invoke('memory-read-entry', id),
|
||||||
updateMemoryEntry: (id: string, data: any): Promise<boolean> => ipcRenderer.invoke('memory-update-entry', id, data),
|
updateMemoryEntry: (id: string, data: any): Promise<boolean> => ipcRenderer.invoke('memory-update-entry', id, data),
|
||||||
removeMemoryEntry: (id: string): Promise<boolean> => ipcRenderer.invoke('memory-remove-entry', id),
|
removeMemoryEntry: (id: string): Promise<boolean> => ipcRenderer.invoke('memory-remove-entry', id),
|
||||||
|
resetMemory: (): Promise<boolean> => ipcRenderer.invoke('memory-reset'),
|
||||||
|
|
||||||
// UI methods
|
// UI methods
|
||||||
showAlert: (message: string): Promise<void> => ipcRenderer.invoke('show-alert', message),
|
showAlert: (message: string): Promise<void> => ipcRenderer.invoke('show-alert', message),
|
||||||
@@ -38,6 +39,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
// Queue methods
|
// Queue methods
|
||||||
addTaskToSendFileQueue: (task: FileItemTask): Promise<void> => ipcRenderer.invoke('add-task-to-send-file-queue', task),
|
addTaskToSendFileQueue: (task: FileItemTask): Promise<void> => ipcRenderer.invoke('add-task-to-send-file-queue', task),
|
||||||
|
|
||||||
// BackupRetrievalWorker
|
// Workers
|
||||||
startBackupRetrieval: (destinationPath: string): Promise<void> => ipcRenderer.invoke('start-backup-retrieval', destinationPath)
|
startBackupRetrieval: (destinationPath: string): Promise<void> => ipcRenderer.invoke('start-backup-retrieval', destinationPath),
|
||||||
|
startAnnouncementWorker: (message: string): Promise<void> => ipcRenderer.invoke('start-announcement-worker', message),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export let operationCodes = {
|
export let operationCodes = {
|
||||||
|
// General Operations
|
||||||
HEARTBEAT: 'HEARTBEAT',
|
HEARTBEAT: 'HEARTBEAT',
|
||||||
ALIVE: 'ALIVE',
|
ALIVE: 'ALIVE',
|
||||||
SET_PUBLIC_KEY: 'SET_PUBLIC_KEY',
|
SET_PUBLIC_KEY: 'SET_PUBLIC_KEY',
|
||||||
@@ -9,20 +10,23 @@ export let operationCodes = {
|
|||||||
END: 'END',
|
END: 'END',
|
||||||
UNKNOWN_COMMAND: 'UNKNOWN_COMMAND',
|
UNKNOWN_COMMAND: 'UNKNOWN_COMMAND',
|
||||||
|
|
||||||
|
// Auth Operations
|
||||||
LOGIN: 'LOGIN',
|
LOGIN: 'LOGIN',
|
||||||
SIGN_UP: 'SIGN_UP',
|
SIGN_UP: 'SIGN_UP',
|
||||||
RESET_PASSWORD: 'RESET_PASSWORD',
|
RESET_PASSWORD: 'RESET_PASSWORD',
|
||||||
|
|
||||||
|
FIND_BY_EMAIL: 'FIND_BY_EMAIL',
|
||||||
|
MODIFY_USER: 'MODIFY_USER',
|
||||||
|
|
||||||
GET_DEPARTMENTS: 'GET_DEPARTMENTS',
|
GET_DEPARTMENTS: 'GET_DEPARTMENTS',
|
||||||
CREATE_DEPARTMENT: 'CREATE_DEPARTMENT',
|
CREATE_DEPARTMENT: 'CREATE_DEPARTMENT',
|
||||||
MODIFY_DEPARTMENT: 'MODIFY_DEPARTMENT',
|
MODIFY_DEPARTMENT: 'MODIFY_DEPARTMENT',
|
||||||
DELETE_DEPARTMENT: 'DELETE_DEPARTMENT',
|
DELETE_DEPARTMENT: 'DELETE_DEPARTMENT',
|
||||||
|
|
||||||
GET_USERS: 'GET_USERS',
|
GET_USERS: 'GET_USERS',
|
||||||
DELETE_USER: 'DELETE_USER',
|
|
||||||
FIND_KEY_BY_USER_ID: 'FIND_KEY_BY_USER_ID',
|
FIND_KEY_BY_USER_ID: 'FIND_KEY_BY_USER_ID',
|
||||||
FIND_BY_EMAIL: 'FIND_BY_EMAIL',
|
DELETE_USER: 'DELETE_USER',
|
||||||
MODIFY_USER: 'MODIFY_USER',
|
SEND_ANNOUNCEMENT: 'SEND_ANNOUNCEMENT',
|
||||||
|
|
||||||
GET_USER_INFORMATION: 'GET_USER_INFORMATION',
|
GET_USER_INFORMATION: 'GET_USER_INFORMATION',
|
||||||
CLEAR_BACKUP: 'CLEAR_BACKUP',
|
CLEAR_BACKUP: 'CLEAR_BACKUP',
|
||||||
|
|||||||
@@ -12,19 +12,16 @@ export abstract class OperationBase implements OperationPlugin {
|
|||||||
|
|
||||||
// Default handler for OK operation
|
// Default handler for OK operation
|
||||||
public static handleOk(parsedMessage: ParsedMessage): ParsedMessage {
|
public static handleOk(parsedMessage: ParsedMessage): ParsedMessage {
|
||||||
console.log('OK operation received');
|
|
||||||
return parsedMessage; // Typically, you would just acknowledge with OK, returning as-is
|
return parsedMessage; // Typically, you would just acknowledge with OK, returning as-is
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default handler for ERR operation
|
// Default handler for ERR operation
|
||||||
public static handleErr(parsedMessage: ParsedMessage): ParsedMessage {
|
public static handleErr(parsedMessage: ParsedMessage): ParsedMessage {
|
||||||
console.log('ERR operation received: ', parsedMessage.metaInfo?.message || 'No error details provided');
|
|
||||||
return parsedMessage; // Typically, you would log the error and return
|
return parsedMessage; // Typically, you would log the error and return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default handler for END operation
|
// Default handler for END operation
|
||||||
public static handleEnd(parsedMessage: ParsedMessage): ParsedMessage {
|
public static handleEnd(parsedMessage: ParsedMessage): ParsedMessage {
|
||||||
console.log('END operation received');
|
|
||||||
return {
|
return {
|
||||||
operationCode: OperationBase.operationCodes.END,
|
operationCode: OperationBase.operationCodes.END,
|
||||||
metaInfo: { message: 'Connection ended.' },
|
metaInfo: { message: 'Connection ended.' },
|
||||||
|
|||||||
@@ -137,8 +137,6 @@ export class TcpClientCommunicator extends SocketCommunicatorBase {
|
|||||||
messageToProcess = this.decryptWithRsa(incomingMessage);
|
messageToProcess = this.decryptWithRsa(incomingMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`\n\nComplete Message:\n${messageToProcess}\n\n`);
|
|
||||||
|
|
||||||
const result = this.operationHandler.handleOperation(messageToProcess);
|
const result = this.operationHandler.handleOperation(messageToProcess);
|
||||||
|
|
||||||
if(result.operationCode === operationCodes.SET_AES_KEY){
|
if(result.operationCode === operationCodes.SET_AES_KEY){
|
||||||
|
|||||||
@@ -80,9 +80,6 @@ export class TcpClient {
|
|||||||
|
|
||||||
// Check if the message is received (based on if lastResult is available)
|
// Check if the message is received (based on if lastResult is available)
|
||||||
isMessageReceived(): boolean {
|
isMessageReceived(): boolean {
|
||||||
console.log('Is message received?');
|
|
||||||
console.log(this.lastResult);
|
|
||||||
console.log(this.lastResult !== null);
|
|
||||||
return this.lastResult !== null;
|
return this.lastResult !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,16 +23,4 @@ const backupRetrievalWorker = new BackupRetrievalWorker(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Start the backup retrieval process
|
// Start the backup retrieval process
|
||||||
backupRetrievalWorker.start()
|
backupRetrievalWorker.start();
|
||||||
.then(() => {
|
|
||||||
parentPort?.postMessage({
|
|
||||||
success: true,
|
|
||||||
message: 'Backup retrieval completed successfully.'
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error: any) => {
|
|
||||||
parentPort?.postMessage({
|
|
||||||
success: false,
|
|
||||||
message: `Backup retrieval failed: ${error.message}`
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const backupIntervalId = setInterval(async () => {
|
|||||||
} else {
|
} else {
|
||||||
console.log('Retrying BackupDirectoryManager initialization...');
|
console.log('Retrying BackupDirectoryManager initialization...');
|
||||||
}
|
}
|
||||||
}, 60000); // Check every 60 seconds
|
}, 10000); // Check every 60 seconds
|
||||||
|
|
||||||
|
|
||||||
// Department share manager check loop
|
// Department share manager check loop
|
||||||
@@ -29,7 +29,7 @@ const departmentIntervalId = setInterval(async () => {
|
|||||||
} else {
|
} else {
|
||||||
console.log('Retrying DepartmentShareManager initialization...');
|
console.log('Retrying DepartmentShareManager initialization...');
|
||||||
}
|
}
|
||||||
}, 60000); // Check every 60 seconds
|
}, 10000); // Check every 60 seconds
|
||||||
|
|
||||||
// Department share manager check loop
|
// Department share manager check loop
|
||||||
const shareIntervalId = setInterval(async () => {
|
const shareIntervalId = setInterval(async () => {
|
||||||
@@ -39,5 +39,5 @@ const shareIntervalId = setInterval(async () => {
|
|||||||
} else {
|
} else {
|
||||||
console.log('Retrying DepartmentShareManager initialization...');
|
console.log('Retrying DepartmentShareManager initialization...');
|
||||||
}
|
}
|
||||||
}, 60000); // Check every 60 seconds
|
}, 10000); // Check every 60 seconds
|
||||||
|
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { parentPort, workerData } from 'worker_threads';
|
||||||
|
import { JsonManager } from '../helpers/json_manager';
|
||||||
|
import { UdpClient } from '../network/udp/udp_client';
|
||||||
|
|
||||||
|
// Define the structure of workerData
|
||||||
|
interface WorkerData {
|
||||||
|
udpPort: number;
|
||||||
|
okPage: string;
|
||||||
|
errorPage: string;
|
||||||
|
applicationInfoPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the data passed to the worker
|
||||||
|
const { udpPort, okPage, errorPage, applicationInfoPath }: WorkerData = workerData;
|
||||||
|
|
||||||
|
// Create a JsonManager instance for application info
|
||||||
|
const applicationInfo = new JsonManager(applicationInfoPath);
|
||||||
|
let appStarted = false;
|
||||||
|
let intervalIds: NodeJS.Timeout[] = []; // Store interval IDs for future clearing
|
||||||
|
|
||||||
|
// Flags to prevent overlapping executions
|
||||||
|
let ucCheckBusy = false;
|
||||||
|
let ipLookupBusy = false;
|
||||||
|
|
||||||
|
// Function to schedule the UC check task with dynamic UDP client creation
|
||||||
|
function startUCCheck(udpPort: number, okPage: string, errorPage: string, interval: number = 5000): void {
|
||||||
|
const intervalId = setInterval(async () => {
|
||||||
|
if (ucCheckBusy) return; // If already running, skip this iteration
|
||||||
|
ucCheckBusy = true; // Mark as busy
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('UC Check running...');
|
||||||
|
const udpClient = new UdpClient(udpPort); // Create UdpClient with the provided port
|
||||||
|
const aliveClients = await udpClient.getAliveClients();
|
||||||
|
const storedIp = await applicationInfo.readValue('serverIp');
|
||||||
|
const foundClient = aliveClients.length > 0;
|
||||||
|
|
||||||
|
if (foundClient) {
|
||||||
|
const ipAddress = aliveClients[0]; // Just using the first alive client
|
||||||
|
|
||||||
|
if (!storedIp || storedIp !== ipAddress) {
|
||||||
|
await applicationInfo.writeValue('serverIp', ipAddress);
|
||||||
|
if (!appStarted) {
|
||||||
|
parentPort?.postMessage({ type: 'changeContent', page: okPage });
|
||||||
|
}
|
||||||
|
appStarted = true;
|
||||||
|
} else if (!appStarted) {
|
||||||
|
parentPort?.postMessage({ type: 'changeContent', page: okPage });
|
||||||
|
appStarted = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parentPort?.postMessage({ type: 'changeContent', page: errorPage });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error checking UC:', err);
|
||||||
|
parentPort?.postMessage({ type: 'changeContent', page: errorPage });
|
||||||
|
} finally {
|
||||||
|
ucCheckBusy = false; // Mark as not busy
|
||||||
|
}
|
||||||
|
}, interval);
|
||||||
|
|
||||||
|
intervalIds.push(intervalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to schedule the IP lookup task, storing the active addresses in memory
|
||||||
|
function startUserIPLookup(udpPort: number, interval: number = 10000): void {
|
||||||
|
const intervalId = setInterval(async () => {
|
||||||
|
if (ipLookupBusy) return; // If already running, skip this iteration
|
||||||
|
ipLookupBusy = true; // Mark as busy
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('IP Lookup running...');
|
||||||
|
const serverIp = await applicationInfo.readValue('serverIp');
|
||||||
|
const udpClient = new UdpClient(udpPort); // Create a UDP client with the provided port
|
||||||
|
const activeIPs = await udpClient.getAliveClients(); // Get the list of active IPs
|
||||||
|
|
||||||
|
// Filter out the serverIp from the list of active clients
|
||||||
|
const filteredIPs = activeIPs.filter(ip => ip !== serverIp);
|
||||||
|
|
||||||
|
// Save the filtered IPs to 'users_ip'
|
||||||
|
await applicationInfo.writeValue('users_ip', filteredIPs);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error during user IP lookup:', err);
|
||||||
|
} finally {
|
||||||
|
ipLookupBusy = false; // Mark as not busy
|
||||||
|
}
|
||||||
|
}, interval);
|
||||||
|
|
||||||
|
intervalIds.push(intervalId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the UC Check and User IP Lookup tasks
|
||||||
|
startUCCheck(udpPort, okPage, errorPage);
|
||||||
|
startUserIPLookup(udpPort);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { workerData } from 'worker_threads';
|
import { workerData } from 'worker_threads';
|
||||||
import { UsersInfoFetcher } from '../helpers/users_info_fetcher';
|
import { UsersInfoFetcher } from '../helpers/users_info_fetcher';
|
||||||
import {BackupManager} from "../helpers/backup_manager";
|
import {BackupRetrievalWorker} from "../helpers/backup_manager";
|
||||||
import {FileSharer} from "../helpers/file_sharer";
|
import {FileSharer} from "../helpers/file_sharer";
|
||||||
import {DepartmentSharer} from "../helpers/department_sharer";
|
import {DepartmentSharer} from "../helpers/department_sharer";
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ usersInfoFetcher.start()
|
|||||||
console.error('Error starting Users Info Fetcher:', error);
|
console.error('Error starting Users Info Fetcher:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
const backupManager = new BackupManager(
|
const backupManager = new BackupRetrievalWorker(
|
||||||
usersConfigPath,
|
usersConfigPath,
|
||||||
applicationInfoPath,
|
applicationInfoPath,
|
||||||
memoryManagerPath,
|
memoryManagerPath,
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { workerData } from 'worker_threads';
|
||||||
|
import { AnnouncementSender} from "../helpers/announcement_sender";
|
||||||
|
|
||||||
|
// Destructure data passed from the main thread
|
||||||
|
const {
|
||||||
|
applicationInfoPath,
|
||||||
|
clientPort,
|
||||||
|
message
|
||||||
|
}: {
|
||||||
|
applicationInfoPath: string,
|
||||||
|
clientPort: number,
|
||||||
|
message: string
|
||||||
|
} = workerData;
|
||||||
|
|
||||||
|
// Initialize the AnnouncementWorker
|
||||||
|
const announcementWorker = new AnnouncementSender(applicationInfoPath, clientPort);
|
||||||
|
|
||||||
|
// Start the announcement process and handle results
|
||||||
|
announcementWorker.start(message);
|
||||||
@@ -30,14 +30,15 @@ export class UserDatabase {
|
|||||||
// Create the table if it doesn't exist
|
// Create the table if it doesn't exist
|
||||||
private createTableIfNotExists() {
|
private createTableIfNotExists() {
|
||||||
const createTableQuery = `
|
const createTableQuery = `
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
email TEXT NOT NULL UNIQUE,
|
email TEXT NOT NULL UNIQUE,
|
||||||
salt TEXT NOT NULL,
|
salt TEXT NOT NULL,
|
||||||
hashedPassword TEXT NOT NULL,
|
hashedPassword TEXT NOT NULL,
|
||||||
departmentId TEXT NOT NULL,
|
departmentId TEXT NOT NULL,
|
||||||
app_type TEXT NOT NULL
|
app_type TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (departmentId) REFERENCES departments(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
`;
|
`;
|
||||||
this.db.exec(createTableQuery);
|
this.db.exec(createTableQuery);
|
||||||
|
|||||||
@@ -1,4 +1,2 @@
|
|||||||
UDP_PORT=41234
|
UDP_PORT=41234
|
||||||
TCP_PORT=41233
|
TCP_PORT=41233
|
||||||
|
|
||||||
HOST=0.0.0.0
|
|
||||||
@@ -4,25 +4,68 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Aplicatie P2P pentru stocarea resurselor digitale",
|
"description": "Aplicatie P2P pentru stocarea resurselor digitale",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "del-cli dist",
|
"clean": "del-cli dist && del-cli out",
|
||||||
"start": "tsc && copyfiles -u 1 'src/**/*' dist && electron dist/main/main.js"
|
"build-dist": "tsc && copyfiles -u 1 'src/**/*' dist",
|
||||||
|
"start-dev": "tsc && copyfiles -u 1 'src/**/*' dist && electron dist/main/main.js",
|
||||||
|
"start": "npm run clean && npm run build-dist && electron-forge start",
|
||||||
|
"package": "npm run clean && npm run build-dist && electron-forge package",
|
||||||
|
"make": "npm run clean && npm run build-dist && electron-forge make"
|
||||||
},
|
},
|
||||||
"main": "dist/main/main.js",
|
"main": "dist/main/main.js",
|
||||||
"author": "Cerbu Andrei - Mihnea",
|
"author": "Cerbu Andrei - Mihnea",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"electron": "^32.0.1",
|
|
||||||
"ping": "^0.4.4",
|
"ping": "^0.4.4",
|
||||||
"uuid": "^10.0.0"
|
"uuid": "^10.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/electron": "^1.6.10",
|
"@electron-forge/cli": "^6.0.0",
|
||||||
"@types/node": "^22.6.1",
|
"@electron-forge/maker-deb": "^6.0.0",
|
||||||
|
"@electron-forge/maker-rpm": "^6.0.0",
|
||||||
|
"@electron-forge/maker-squirrel": "^6.0.0",
|
||||||
|
"@electron-forge/maker-zip": "^6.0.0",
|
||||||
"@types/ping": "^0.4.4",
|
"@types/ping": "^0.4.4",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
"del-cli": "^5.0.0",
|
"del-cli": "^5.0.0",
|
||||||
|
"electron": "^33.0.2",
|
||||||
"typescript": "^5.6.2"
|
"typescript": "^5.6.2"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"forge": {
|
||||||
|
"packagerConfig": {
|
||||||
|
"name": "ClientApp",
|
||||||
|
"icon": "../app_icons/icon.ico",
|
||||||
|
"ignore": [
|
||||||
|
"src",
|
||||||
|
".idea",
|
||||||
|
"tsconfig.json"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"makers": [
|
||||||
|
{
|
||||||
|
"name": "@electron-forge/maker-squirrel",
|
||||||
|
"config": {
|
||||||
|
"name": "ClientApp",
|
||||||
|
"setupIcon": "../app_icons/icon.ico"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@electron-forge/maker-zip",
|
||||||
|
"platforms": ["darwin"],
|
||||||
|
"config": {
|
||||||
|
"icon": "../app_icons/icon.icns"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "@electron-forge/maker-deb",
|
||||||
|
"platforms": ["linux"],
|
||||||
|
"config": {
|
||||||
|
"icon": "../app_icons/icon.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 298 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
@@ -0,0 +1,79 @@
|
|||||||
|
body, html {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: "Pridi", sans-serif;
|
||||||
|
background-color: #4A628A;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement-body {
|
||||||
|
max-height: 300px; /* Limit the height to make it scrollable if content overflows */
|
||||||
|
width: 80%;
|
||||||
|
overflow-y: auto; /* Enable vertical scrolling */
|
||||||
|
padding: 20px;
|
||||||
|
color: white;
|
||||||
|
background-color: rgba(0, 0, 0, 0.2); /* Slight background to make text stand out */
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Customize scrollbar appearance */
|
||||||
|
.announcement-body::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement-body::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement-body::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement-body::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgb(158, 158, 158);
|
||||||
|
}
|
||||||
|
|
||||||
|
.announcement-footer {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#closeButton {
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #4A628A;
|
||||||
|
background-color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#closeButton:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
body, html {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
|
background-color: #4A628A;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4, h2, h1{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
img{
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-section{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
border: 3px solid #f3f3f3;
|
||||||
|
border-top: 3px solid #2196F3;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
@@ -1,110 +1,143 @@
|
|||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
font-weight: 500;
|
background-color: #4A628A;
|
||||||
font-style: normal;
|
}
|
||||||
|
|
||||||
background-image: url('../assets/background.png');
|
h4, h2, h1{
|
||||||
background-size: cover;
|
margin: 0;
|
||||||
background-repeat: no-repeat;
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
color: #1B1A55;
|
|
||||||
font-size: 4vh;
|
|
||||||
text-transform: uppercase;
|
|
||||||
line-height: 2.5rem;
|
|
||||||
margin-bottom: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form {
|
|
||||||
background-color: #535C91;
|
|
||||||
opacity: 71;
|
|
||||||
padding: 9vh 3vh 5vh;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form-title {
|
|
||||||
margin: 0 0 5vh 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form-title h2 {
|
|
||||||
color: #FFFFFF;
|
|
||||||
font-size: 5vh;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form hr {
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form-content {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
width: 70%;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 0.7rem;
|
|
||||||
background-color: #1B1A55;
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
button {
|
display: flex;
|
||||||
font-weight: bold;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
height: 90%;
|
||||||
width: 35%;
|
width: 35%;
|
||||||
font-size: 1rem;
|
|
||||||
padding: 10px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-top: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
.header{
|
||||||
filter: brightness(85%);
|
text-align: center;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subheading{
|
||||||
|
padding-top: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
color: rgba(255,255,255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-content input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: 1px solid #ccc; /* Light gray border */
|
||||||
|
border-radius: 8px; /* Rounded corners */
|
||||||
|
font-size: 1em;
|
||||||
|
font-family: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #f9f9f9; /* Light background */
|
||||||
|
color: #333; /* Dark text color */
|
||||||
|
transition: border-color 0.3s ease, box-shadow 0.3s ease; /* Smooth transition for interaction */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Placeholder Text Styling */
|
||||||
|
.login-form-content input::placeholder {
|
||||||
|
color: #aaa; /* Lighter color for placeholders */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus State Styling */
|
||||||
|
.login-form-content input:focus {
|
||||||
|
outline: none; /* Remove default focus outline */
|
||||||
|
border-color: #4A628A; /* Blue border on focus */
|
||||||
|
box-shadow: 0 0 5px rgba(74, 98, 138, 0.5); /* Subtle shadow for focus */
|
||||||
|
background-color: #fff; /* Slightly brighter background on focus */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Field Hover Effect */
|
||||||
|
.login-form-content input:hover {
|
||||||
|
border-color: #888; /* Darker gray border on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disabled Input Styling */
|
||||||
|
.login-form-content input:disabled {
|
||||||
|
background-color: #e0e0e0; /* Light gray background for disabled input */
|
||||||
|
cursor: not-allowed; /* Show "not allowed" cursor */
|
||||||
|
opacity: 0.7; /* Slight transparency */
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-form-footer {
|
.login-form-footer {
|
||||||
margin-top: 3vh;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
width: 100%;
|
||||||
align-content: center;
|
flex-direction: column;
|
||||||
|
justify-content: space-between; /* Align buttons next to each other */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-evenly;
|
gap: 10px; /* Space between buttons */
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling for buttons */
|
||||||
|
.login-form-footer button {
|
||||||
|
flex: 1; /* Ensure buttons are equal width */
|
||||||
|
padding: 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[name="underline"] {
|
||||||
|
background-color: transparent; /* No background */
|
||||||
|
border: none; /* Remove default button border */
|
||||||
|
color: #4A628A; /* Set text color */
|
||||||
|
font-size: 1em; /* Set font size */
|
||||||
|
cursor: pointer; /* Add pointer cursor for interactivity */
|
||||||
|
text-decoration: underline; /* Underline text */
|
||||||
|
padding: 0; /* Remove default padding */
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name="submit"] {
|
button[name="submit"] {
|
||||||
background-color: #F44336;
|
width: 100%;
|
||||||
color: white;
|
background-color: #4A628A; /* Blue background */
|
||||||
}
|
color: white; /* White text color */
|
||||||
|
|
||||||
button[name="signup"] {
|
|
||||||
background-color: #2196F3;
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
@@ -1,232 +1,204 @@
|
|||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
|
background-color: #4A628A;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4, h2, h1{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
font-size: 32px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
letter-spacing: -1px;
|
||||||
|
|
||||||
background-image: url('../assets/background.png');
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.overlay {
|
|
||||||
display: none;
|
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.85);
|
|
||||||
z-index: 2;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ceo-validation-form {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
width: 300px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #1B1A55;
|
|
||||||
border-radius: 10px;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ceo-validation-form h2 {
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-actions {
|
.user{
|
||||||
text-align: center;
|
display: inline;
|
||||||
padding-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-actions button {
|
|
||||||
padding: 10px 20px;
|
|
||||||
margin: 0 10px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
width: 80%;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 0.7rem;
|
|
||||||
background-color: #1B1A55;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#back_button {
|
|
||||||
background-color: #f44336;
|
|
||||||
width: 35%;
|
|
||||||
height: auto;
|
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
.navbar{
|
||||||
#submit_button {
|
padding-top: 8px;
|
||||||
background-color: #4CAF50;
|
padding-right: 32px;
|
||||||
width: 35%;
|
padding-left: 32px;
|
||||||
height: auto;
|
height: 10%;
|
||||||
color: white;
|
display:flex;
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background-color: #535C91;
|
|
||||||
opacity: 71;
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_top {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
text-align: left;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.welcome{
|
||||||
.left_block_top h1 {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_top img {
|
|
||||||
margin: 0 3vw 0 5vw;
|
|
||||||
width: 8vw;
|
|
||||||
height: 8vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_content {
|
|
||||||
margin: 2rem 0 2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_buttons {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-content: center;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
img{
|
||||||
|
padding-right: 8px;
|
||||||
|
width:25px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container{
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
.left_block{
|
||||||
margin: 0 1rem 0 1rem;
|
background: linear-gradient(to bottom, #131A24, #4A628A);
|
||||||
width: 15vw;
|
width: 25%;
|
||||||
height: 10vh;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
border: none;
|
.notification-title{
|
||||||
border-radius: 10px;
|
padding-top: 16px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
text-align: center;
|
||||||
|
height: 10%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
color: white;
|
.notifications{
|
||||||
font-size: 1rem;
|
overflow-y: auto;
|
||||||
font-weight: bold;
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-grow: 1; /* Allow it to take available space */
|
||||||
|
max-height: calc(90% - 80px);
|
||||||
|
}
|
||||||
|
|
||||||
text-transform: uppercase;
|
.right_block_content button:last-child {
|
||||||
|
background-color: #FF6347;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right_block_content button:last-child:hover {
|
||||||
|
background-color: #fd4c29;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert, .logout-button{
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
border-radius: 15px;
|
||||||
outline: none;
|
width: 80%;
|
||||||
transition: background-color 0.3s ease;
|
min-height: 64px;
|
||||||
|
height: auto;
|
||||||
|
padding:12px;
|
||||||
|
background: #FF6347;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.notification{
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 15px;
|
||||||
|
width: 80%;
|
||||||
|
min-height: 64px;
|
||||||
|
height: auto;
|
||||||
|
padding:12px;
|
||||||
|
background: rgba(186, 229, 255);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
.notification:hover{
|
||||||
filter: brightness(85%);
|
background: #d9efff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left_block_footer {
|
.alert:hover, .logout-button:hover{
|
||||||
|
background: rgb(244, 44, 44);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notif-title{
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 300;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-content{
|
||||||
|
height: 90%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left_block_footer{
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: end;
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right_block {
|
.right_block_content{
|
||||||
display: flex;
|
height: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right_block{
|
||||||
|
width: 75%;
|
||||||
|
display:flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: #535C91;
|
justify-content: center;
|
||||||
opacity: 71;
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.notifications {
|
.right_block_content {
|
||||||
display: flex;
|
display: grid;
|
||||||
margin: 1rem 2rem 2rem 3rem;
|
grid-template-columns: repeat(3, 1fr); /* 3 equal-width columns */
|
||||||
flex-direction: column;
|
gap: 8px; /* Spacing between buttons */
|
||||||
align-items: start;
|
padding: 32px; /* Optional padding */
|
||||||
|
|
||||||
height: 40vh; /* Fixed height */
|
|
||||||
width: 80%; /* Full width */
|
|
||||||
overflow: auto; /* Enable scrolling */
|
|
||||||
|
|
||||||
font-size: 1.2rem;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications::-webkit-scrollbar {
|
|
||||||
width: 10px; /* Reduced width of the scrollbar by 2px */
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications::-webkit-scrollbar-track {
|
|
||||||
background: #1B1A55; /* Updated track color */
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications::-webkit-scrollbar-thumb {
|
|
||||||
background: #535C91; /* Updated handle color */
|
|
||||||
border: 2px solid #1B1A55; /* Adding a border to effectively reduce the thumb size */
|
|
||||||
}
|
|
||||||
|
|
||||||
.notifications::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #555; /* Updated handle color on hover */
|
|
||||||
}
|
|
||||||
|
|
||||||
button[name="logout"] {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 10vw;
|
|
||||||
height: 5vh;
|
|
||||||
background-color: #F44336;
|
|
||||||
}
|
|
||||||
|
|
||||||
button[name="alert"] {
|
|
||||||
margin: 0.7rem;
|
|
||||||
background-color: #F44336;
|
|
||||||
}
|
|
||||||
|
|
||||||
button[name="notification"] {
|
|
||||||
margin: 0.7rem;
|
|
||||||
background-color: #23BDEE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name="menu_button"] {
|
button[name="menu_button"] {
|
||||||
margin: 0.7rem;
|
padding: 10px 20px;
|
||||||
background-color: #1B1A55;
|
font-size: 16px;
|
||||||
|
background-color: rgba(186,229,244,0.4); /* Button background */
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[name="menu_button"]:hover {
|
||||||
|
background-color: #3B5173; /* Darker background on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications::-webkit-scrollbar {
|
||||||
|
width: 4px; /* Width of the scrollbar */
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.1); /* Track background color */
|
||||||
|
border-radius: 10px; /* Rounded corners for the track */
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(74, 98, 138, 0.8); /* Scrollbar handle color */
|
||||||
|
border-radius: 10px; /* Rounded corners for the scrollbar handle */
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(74, 98, 138, 1); /* Darker color on hover */
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
html, body, div, span, applet, object, iframe,
|
||||||
|
h1, h2, h3, h4, h5, h6, p, blockquote, pre, hr
|
||||||
|
a, abbr, acronym, address, big, cite, code,
|
||||||
|
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||||
|
small, strike, strong, sub, sup, tt, var,
|
||||||
|
b, u, i, center,
|
||||||
|
dl, dt, dd, ol, ul, li,
|
||||||
|
fieldset, form, label, legend,
|
||||||
|
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||||
|
article, aside, canvas, details, embed,
|
||||||
|
figure, figcaption, footer, header, hgroup,
|
||||||
|
menu, nav, output, ruby, section, summary,
|
||||||
|
time, mark, audio, video {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font: inherit;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
/* HTML5 display-role reset for older browsers */
|
||||||
|
article, aside, details, figcaption, figure,
|
||||||
|
footer, header, hgroup, menu, nav, section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
ol, ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
blockquote, q {
|
||||||
|
quotes: none;
|
||||||
|
}
|
||||||
|
blockquote:before, blockquote:after,
|
||||||
|
q:before, q:after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
@@ -1,110 +1,136 @@
|
|||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
font-weight: 500;
|
background-color: #4A628A;
|
||||||
font-style: normal;
|
}
|
||||||
|
|
||||||
background-image: url('../assets/background.png');
|
h4, h2, h1{
|
||||||
background-size: cover;
|
margin: 0;
|
||||||
background-repeat: no-repeat;
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
opacity: 0;
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 100vh;
|
justify-content: space-evenly;
|
||||||
text-align: center; /* Center the text for all child elements */
|
height: 90%;
|
||||||
|
width: 35%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header{
|
||||||
color: #1B1A55;
|
|
||||||
font-size: 4vh;
|
|
||||||
text-transform: uppercase;
|
|
||||||
line-height: 2.5rem;
|
|
||||||
margin-bottom: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-form {
|
|
||||||
background-color: #535C91;
|
|
||||||
opacity: 71;
|
|
||||||
padding: 13vh 3vh 5vh;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-form-title {
|
|
||||||
margin-bottom: 5vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-form hr {
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-form-title h2 {
|
|
||||||
color: #FFFFFF;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 5vh;
|
padding-bottom: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-form-content {
|
.title{
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subheading{
|
||||||
|
padding-top: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
color: rgba(255,255,255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-form{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-direction: column;
|
||||||
justify-content: center;
|
align-items: center;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-form-content input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: 1px solid #ccc; /* Light gray border */
|
||||||
|
border-radius: 8px; /* Rounded corners */
|
||||||
|
font-size: 1em;
|
||||||
|
font-family: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #f9f9f9; /* Light background */
|
||||||
|
color: #333; /* Dark text color */
|
||||||
|
transition: border-color 0.3s ease, box-shadow 0.3s ease; /* Smooth transition for interaction */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Placeholder Text Styling */
|
||||||
|
.profile-form-content input::placeholder {
|
||||||
|
color: #aaa; /* Lighter color for placeholders */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus State Styling */
|
||||||
|
.profile-form-content input:focus {
|
||||||
|
outline: none; /* Remove default focus outline */
|
||||||
|
border-color: #4A628A; /* Blue border on focus */
|
||||||
|
box-shadow: 0 0 5px rgba(74, 98, 138, 0.5); /* Subtle shadow for focus */
|
||||||
|
background-color: #fff; /* Slightly brighter background on focus */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Field Hover Effect */
|
||||||
|
.profile-form-content input:hover {
|
||||||
|
border-color: #888; /* Darker gray border on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disabled Input Styling */
|
||||||
|
.profile-form-content input:disabled {
|
||||||
|
background-color: #e0e0e0; /* Light gray background for disabled input */
|
||||||
|
cursor: not-allowed; /* Show "not allowed" cursor */
|
||||||
|
opacity: 0.7; /* Slight transparency */
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-form-footer {
|
.profile-form-footer {
|
||||||
margin-top: 7vh;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
width: 100%;
|
||||||
justify-content: center;
|
flex-direction: column;
|
||||||
|
justify-content: space-between; /* Align buttons next to each other */
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px; /* Space between buttons */
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
/* Styling for buttons */
|
||||||
text-align: center;
|
.profile-form button {
|
||||||
color: white;
|
flex: 1; /* Ensure buttons are equal width */
|
||||||
font-weight: bold;
|
padding: 16px;
|
||||||
font-size: 1.2rem;
|
font-weight: 300;
|
||||||
width: 70%;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 0.7rem;
|
|
||||||
background-color: #1B1A55;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
font-weight: bold;
|
|
||||||
width: 35%;
|
|
||||||
font-size: 1rem;
|
|
||||||
padding: 10px;
|
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 8px;
|
||||||
margin-top: 1rem;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button[name="underline"] {
|
||||||
filter: brightness(85%);
|
background-color: transparent; /* No background */
|
||||||
|
border: none; /* Remove default button border */
|
||||||
|
color: #4A628A; /* Set text color */
|
||||||
|
font-size: 1em; /* Set font size */
|
||||||
|
cursor: pointer; /* Add pointer cursor for interactivity */
|
||||||
|
text-decoration: underline; /* Underline text */
|
||||||
|
padding: 0; /* Remove default padding */
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name="submit"] {
|
button[name="submit"] {
|
||||||
background-color: #F44336;
|
width: 100%;
|
||||||
color: white;
|
background-color: #4A628A; /* Blue background */
|
||||||
}
|
color: white; /* White text color */
|
||||||
|
|
||||||
button[name="login"] {
|
|
||||||
background-color: #2196F3;
|
|
||||||
color: white;
|
|
||||||
margin-right: 5rem;
|
|
||||||
}
|
}
|
||||||
@@ -1,110 +1,151 @@
|
|||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
font-weight: 500;
|
background-color: #4A628A;
|
||||||
font-style: normal;
|
}
|
||||||
|
|
||||||
background-image: url('../assets/background.png');
|
h4, h2, h1{
|
||||||
background-size: cover;
|
margin: 0;
|
||||||
background-repeat: no-repeat;
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
color: #1B1A55;
|
|
||||||
font-size: 4vh;
|
|
||||||
text-transform: uppercase;
|
|
||||||
line-height: 2.5rem;
|
|
||||||
margin-bottom: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form {
|
|
||||||
background-color: #535C91;
|
|
||||||
opacity: 71;
|
|
||||||
padding: 9vh 3vh 5vh;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form-title {
|
|
||||||
margin: 0 0 5vh 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form-title h2 {
|
|
||||||
color: #FFFFFF;
|
|
||||||
font-size: 5vh;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form hr {
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form-content {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.2rem;
|
|
||||||
width: 70%;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 0.7rem;
|
|
||||||
background-color: #1B1A55;
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
button {
|
display: flex;
|
||||||
font-weight: bold;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 90%;
|
||||||
width: 35%;
|
width: 35%;
|
||||||
font-size: 1rem;
|
|
||||||
padding: 10px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-top: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
.header{
|
||||||
filter: brightness(85%);
|
display: flex;
|
||||||
|
width: 80%;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 20%;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subheading{
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
padding-top: 8px;
|
||||||
|
color: rgba(255,255,255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form{
|
||||||
|
height: 80%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-content input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: 1px solid #ccc; /* Light gray border */
|
||||||
|
border-radius: 8px; /* Rounded corners */
|
||||||
|
font-size: 1em;
|
||||||
|
font-family: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #f9f9f9; /* Light background */
|
||||||
|
color: #333; /* Dark text color */
|
||||||
|
transition: border-color 0.3s ease, box-shadow 0.3s ease; /* Smooth transition for interaction */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Placeholder Text Styling */
|
||||||
|
.login-form-content input::placeholder {
|
||||||
|
color: #aaa; /* Lighter color for placeholders */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus State Styling */
|
||||||
|
.login-form-content input:focus {
|
||||||
|
outline: none; /* Remove default focus outline */
|
||||||
|
border-color: #4A628A; /* Blue border on focus */
|
||||||
|
box-shadow: 0 0 5px rgba(74, 98, 138, 0.5); /* Subtle shadow for focus */
|
||||||
|
background-color: #fff; /* Slightly brighter background on focus */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Field Hover Effect */
|
||||||
|
.login-form-content input:hover {
|
||||||
|
border-color: #888; /* Darker gray border on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disabled Input Styling */
|
||||||
|
.login-form-content input:disabled {
|
||||||
|
background-color: #e0e0e0; /* Light gray background for disabled input */
|
||||||
|
cursor: not-allowed; /* Show "not allowed" cursor */
|
||||||
|
opacity: 0.7; /* Slight transparency */
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-form-footer {
|
.login-form-footer {
|
||||||
margin-top: 3vh;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
width: 100%;
|
||||||
align-content: center;
|
flex-direction: column;
|
||||||
|
justify-content: space-between; /* Align buttons next to each other */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-evenly;
|
gap: 10px; /* Space between buttons */
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styling for buttons */
|
||||||
|
.login-form-footer button {
|
||||||
|
flex: 1; /* Ensure buttons are equal width */
|
||||||
|
padding: 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button[name="underline"] {
|
||||||
|
background-color: transparent; /* No background */
|
||||||
|
border: none; /* Remove default button border */
|
||||||
|
color: #4A628A; /* Set text color */
|
||||||
|
font-size: 1em; /* Set font size */
|
||||||
|
cursor: pointer; /* Add pointer cursor for interactivity */
|
||||||
|
text-decoration: underline; /* Underline text */
|
||||||
|
padding: 0; /* Remove default padding */
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name="submit"] {
|
button[name="submit"] {
|
||||||
background-color: #F44336;
|
width: 100%;
|
||||||
color: white;
|
background-color: #4A628A; /* Blue background */
|
||||||
}
|
color: white; /* White text color */
|
||||||
|
|
||||||
button[name="signup"] {
|
|
||||||
background-color: #2196F3;
|
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
@@ -1,191 +1,162 @@
|
|||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
font-weight: 500;
|
background-color: #4A628A;
|
||||||
font-style: normal;
|
}
|
||||||
|
|
||||||
background-image: url('../assets/background.png');
|
h4, h2, h1{
|
||||||
background-size: cover;
|
margin: 0;
|
||||||
background-repeat: no-repeat;
|
}
|
||||||
|
|
||||||
|
.page{
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2 {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background-color: #535C91;
|
|
||||||
opacity: 71;
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_top {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_top_left {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: start;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_top_left hr {
|
|
||||||
margin: 0 0 1rem 0;
|
|
||||||
width: 45%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_top_right {
|
|
||||||
width: 10vw;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-content: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
padding: 0 2vh 0 2vh;
|
|
||||||
background-color: #1B1A55;
|
|
||||||
margin: 0 0 0 5vw;
|
|
||||||
border-radius: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_content {
|
|
||||||
display: flex;
|
|
||||||
justify-content: start;
|
|
||||||
align-items: center;
|
|
||||||
margin: 2rem 0 2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_buttons {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-content: center;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin: 0 1rem 0 1rem;
|
|
||||||
width: 10vw;
|
|
||||||
height: 5vh;
|
|
||||||
|
|
||||||
border: none;
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||||
color: white;
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: bold;
|
|
||||||
|
|
||||||
text-transform: uppercase;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
||||||
outline: none;
|
|
||||||
transition: background-color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
filter: brightness(85%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.left_block_footer {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: end;
|
height: 90%;
|
||||||
|
width: 35%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right_block {
|
.header{
|
||||||
display: flex;
|
text-align: center;
|
||||||
flex-direction: column;
|
padding-bottom: 16px;
|
||||||
background-color: #535C91;
|
padding-top: 32px;
|
||||||
opacity: 71;
|
}
|
||||||
padding: 2rem 4rem 2rem 2rem;
|
|
||||||
border-radius: 1rem;
|
.title{
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
font-size: 48px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: -1px;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.choose_user_form_title {
|
.subheading{
|
||||||
display: flex;
|
padding-top: 8px;
|
||||||
flex-wrap: wrap;
|
font-size: 16px;
|
||||||
flex-direction: column;
|
letter-spacing: -0.5px;
|
||||||
justify-content: left;
|
color: rgba(255,255,255, 0.8);
|
||||||
align-content: start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.choose_user_form_title hr {
|
.form{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between; /* Align buttons next to each other */
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px; /* Space between buttons */
|
||||||
|
}
|
||||||
|
|
||||||
|
.filename-title{
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
color: white;
|
||||||
|
padding: 48px 24px 48px 24px;
|
||||||
|
background-color: rgba(0,0,0,0.25);
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-top: 16px;
|
||||||
|
box-shadow: 2px 2px 2px 1px rgb(0 0 0 / 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.users_block{
|
||||||
|
padding-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label{
|
||||||
|
margin-top: 8px;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.choose_user_form_content {
|
.choose_user_form_content {
|
||||||
|
overflow-y: hidden;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 180px;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 1rem 7rem 2rem 0.5rem;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: start;
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
height: 20vh; /* Fixed height */
|
h5{
|
||||||
width: 100%; /* Full width */
|
font-size: 20px;
|
||||||
overflow: auto; /* Enable scrolling */
|
font-weight: 300;
|
||||||
|
text-align: center;
|
||||||
font-size: 1.2rem;
|
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.choose_user_form_content::-webkit-scrollbar {
|
/* Styling for buttons */
|
||||||
width: 10px; /* Reduced width of the scrollbar by 2px */
|
.form button {
|
||||||
|
flex: 1; /* Ensure buttons are equal width */
|
||||||
|
padding: 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.choose_user_form_content::-webkit-scrollbar-track {
|
button[name="underline"] {
|
||||||
background: #1B1A55; /* Updated track color */
|
background-color: transparent; /* No background */
|
||||||
}
|
border: none; /* Remove default button border */
|
||||||
|
color: #4A628A; /* Set text color */
|
||||||
.choose_user_form_content::-webkit-scrollbar-thumb {
|
font-size: 1em; /* Set font size */
|
||||||
background: #535C91; /* Updated handle color */
|
cursor: pointer; /* Add pointer cursor for interactivity */
|
||||||
border: 2px solid #1B1A55; /* Adding a border to effectively reduce the thumb size */
|
text-decoration: underline; /* Underline text */
|
||||||
}
|
padding: 0; /* Remove default padding */
|
||||||
|
|
||||||
.choose_user_form_content::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #555; /* Updated handle color on hover */
|
|
||||||
}
|
|
||||||
|
|
||||||
button[name="back"] {
|
|
||||||
background-color: #23BDEE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name="submit"] {
|
button[name="submit"] {
|
||||||
background-color: #F44336;
|
width: 100%;
|
||||||
|
background-color: #4A628A; /* Blue background */
|
||||||
|
color: white; /* White text color */
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name="select_file"] {
|
button[name="submit"]:hover {
|
||||||
margin: 0;
|
background-color: #3B5173; /* Darker background on hover */
|
||||||
width: 8vw;
|
}
|
||||||
background-color: #1B1A55;
|
|
||||||
|
.choose_user_form_content::-webkit-scrollbar {
|
||||||
|
width: 4px; /* Width of the scrollbar */
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose_user_form_content::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.1); /* Track background color */
|
||||||
|
border-radius: 10px; /* Rounded corners for the track */
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose_user_form_content::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(74, 98, 138, 0.8); /* Scrollbar handle color */
|
||||||
|
border-radius: 10px; /* Rounded corners for the scrollbar handle */
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose_user_form_content::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(74, 98, 138, 1); /* Darker color on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
.choose_user_form_content:empty {
|
||||||
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
@@ -1,117 +1,183 @@
|
|||||||
|
/* General Styles */
|
||||||
body, html {
|
body, html {
|
||||||
height: 100%;
|
height: 100vh;
|
||||||
width: 100%;
|
width: 100vw;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-family: "Roboto", sans-serif;
|
|
||||||
font-weight: 500;
|
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||||
font-style: normal;
|
background-color: #4A628A; /* Background color */
|
||||||
background-image: url('../assets/background.png');
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
h4, h2, h1 {
|
||||||
min-height: 100vh;
|
margin: 0; /* Remove default margin */
|
||||||
}
|
}
|
||||||
|
|
||||||
.step {
|
/* Page Layout */
|
||||||
|
.page {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
justify-content: center;
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 100vh;
|
}
|
||||||
opacity: 0; /* Make steps invisible by default */
|
.container{
|
||||||
visibility: hidden; /* Hide steps by default */
|
height: 100%;
|
||||||
transition: opacity 0.5s ease, visibility 0.5s ease; /* Smooth transition */
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
}
|
}
|
||||||
|
|
||||||
.step.active {
|
/* Active Step Styles */
|
||||||
opacity: 1; /* Show the active step */
|
.step {
|
||||||
visibility: visible; /* Make it visible */
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: stretch;
|
||||||
|
height: 90%;
|
||||||
|
width: 35%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Header Styling */
|
||||||
.header {
|
.header {
|
||||||
color: #1B1A55;
|
display: flex;
|
||||||
font-size: 4vh;
|
flex-direction: column;
|
||||||
text-transform: uppercase;
|
align-items: center;
|
||||||
line-height: 2.5rem;
|
justify-content: center;
|
||||||
margin-bottom: 10rem;
|
height: 20%;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
padding-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title{
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subheading{
|
||||||
|
font-size: 16px;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
padding-top: 8px;
|
||||||
|
color: rgba(255,255,255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Signup Form Styles */
|
||||||
.signup-form {
|
.signup-form {
|
||||||
background-color: #535C91;
|
display: flex;
|
||||||
opacity: 71;
|
height: 80%;
|
||||||
padding: 9vh 3vh 5vh;
|
flex-direction: column;
|
||||||
border-radius: 1rem;
|
align-items: center;
|
||||||
box-shadow: 10px 10px 10px rgba(0, 0, 0, 0.9);
|
justify-content: space-between;
|
||||||
width: 25%;
|
width: 80%; /* Width of the form */
|
||||||
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.signup-form-title {
|
.signup-form-title {
|
||||||
margin: 0 0 5vh 0;
|
font-size: 24px;
|
||||||
text-align: center;
|
font-weight: 500;
|
||||||
}
|
|
||||||
|
|
||||||
.signup-form-title h2 {
|
|
||||||
color: #FFFFFF;
|
|
||||||
font-size: 5vh;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-form hr {
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-form-content {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: bold;
|
margin-bottom: 16px; /* Space below title */
|
||||||
font-size: 1.2rem;
|
|
||||||
width: 70%;
|
|
||||||
padding: 1rem;
|
|
||||||
margin: 0.7rem;
|
|
||||||
background-color: #1B1A55;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
/* Input Styles */
|
||||||
font-weight: bold;
|
.signup-form-content input {
|
||||||
width: 35%;
|
width: 100%;
|
||||||
font-size: 1rem;
|
padding: 15px;
|
||||||
padding: 10px;
|
margin: 10px 0;
|
||||||
border: none;
|
border: 1px solid #ccc; /* Light gray border */
|
||||||
border-radius: 5px;
|
border-radius: 8px; /* Rounded corners */
|
||||||
margin-top: 1rem;
|
font-size: 1em;
|
||||||
cursor: pointer;
|
font-family: inherit;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #f9f9f9; /* Light background */
|
||||||
|
color: #333; /* Dark text color */
|
||||||
|
transition: border-color 0.3s ease, box-shadow 0.3s ease; /* Smooth transition for interaction */
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
/* Placeholder Text Styling */
|
||||||
filter: brightness(85%);
|
.signup-form-content input::placeholder {
|
||||||
|
color: #aaa; /* Lighter color for placeholders */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.signup-form-content-radio{
|
||||||
|
width: 100%;
|
||||||
|
display: flex; /* Use flexbox to arrange items in a row */
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup-form-content-radio label {
|
||||||
|
width: 100%;
|
||||||
|
display: flex; /* Make label a flex container */
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
cursor: pointer; /* Change cursor to pointer when hovering */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optional: Styling the radio button */
|
||||||
|
.signup-form-content-radio input[type="radio"] {
|
||||||
|
margin-right: 8px; /* Space between radio button and label text */
|
||||||
|
}
|
||||||
|
/* Focus State Styling */
|
||||||
|
.signup-form-content input:focus {
|
||||||
|
outline: none; /* Remove default focus outline */
|
||||||
|
border-color: #4A628A; /* Change border color on focus */
|
||||||
|
box-shadow: 0 0 5px rgba(74, 98, 138, 0.5); /* Subtle shadow for focus */
|
||||||
|
background-color: #fff; /* Slightly brighter background on focus */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Field Hover Effect */
|
||||||
|
.signup-form-content input:hover {
|
||||||
|
border-color: #888; /* Darker gray border on hover */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disabled Input Styling */
|
||||||
|
.signup-form-content input:disabled {
|
||||||
|
background-color: #e0e0e0; /* Light gray background for disabled input */
|
||||||
|
cursor: not-allowed; /* Show "not allowed" cursor */
|
||||||
|
opacity: 0.7; /* Slight transparency */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Signup Form Footer */
|
||||||
.signup-form-footer {
|
.signup-form-footer {
|
||||||
margin-top: 3vh;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
width: 100%;
|
||||||
align-content: center;
|
justify-content: space-between; /* Align buttons next to each other */
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-evenly;
|
gap: 10px; /* Space between buttons */
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button Styles */
|
||||||
|
.signup-form-footer button {
|
||||||
|
flex: 1; /* Ensure buttons are equal width */
|
||||||
|
padding: 16px;
|
||||||
|
font-weight: 300;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name="back"] {
|
button[name="back"] {
|
||||||
background-color: #F44336;
|
background-color: transparent; /* No background for 'back' button */
|
||||||
color: white;
|
color: #4A628A; /* Set text color */
|
||||||
|
text-decoration: underline; /* Underline text */
|
||||||
}
|
}
|
||||||
|
|
||||||
button[name="continue"] {
|
button[name="continue"] {
|
||||||
background-color: #2196F3;
|
width: 100%;
|
||||||
color: white;
|
background-color: #4A628A; /* Blue background */
|
||||||
|
color: white; /* White text color */
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
body, html {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
font-family: "Roboto", sans-serif;
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
|
|
||||||
background-image: url('../assets/background.png');
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
color: #1B1A55;
|
|
||||||
font-size: 4vh;
|
|
||||||
text-transform: uppercase;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-section {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border: 6px solid #f3f3f3;
|
|
||||||
border-top: 6px solid #2196F3;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-section p {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: white;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
body, html {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
font-family: "Roboto", sans-serif;
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
|
|
||||||
background-image: url('../assets/background.png');
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
opacity: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
color: #1B1A55;
|
|
||||||
font-size: 4vh;
|
|
||||||
text-transform: uppercase;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-section {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border: 6px solid #f3f3f3;
|
|
||||||
border-top: 6px solid #2196F3;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-section p {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: white;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
|
<link href="../css/announcement.css" rel="stylesheet">
|
||||||
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<script src="../js/announcement.js"></script>
|
||||||
|
<script src="../js/helpers.js"></script>
|
||||||
|
<title>Announcement</title>
|
||||||
|
</head>
|
||||||
|
<body onload="loadAnnouncement(); fadeIn()">
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1 class="title">Announcement</h1>
|
||||||
|
</div>
|
||||||
|
<div class="announcement-body">
|
||||||
|
<p id="announcement-content">Loading announcement...</p>
|
||||||
|
</div>
|
||||||
|
<div class="announcement-footer">
|
||||||
|
<button id="closeButton" onclick="closeWindow()">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
|
||||||
|
|
||||||
<!-- Reuse the same CSS files as UC Not Found -->
|
|
||||||
<link href="../css/welcome.css" rel="stylesheet">
|
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<script src="../js/helpers.js"></script>
|
|
||||||
<title>Welcome</title>
|
|
||||||
</head>
|
|
||||||
<body onload="fadeIn()">
|
|
||||||
<div class="container">
|
|
||||||
<div class="header">
|
|
||||||
<h1>BACKUP FETCHER</h1>
|
|
||||||
</div>
|
|
||||||
<div class="loading-section">
|
|
||||||
<div class="spinner"></div>
|
|
||||||
<p>Backup is not searched, please wait...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -4,10 +4,13 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
<!-- Reuse the same CSS files as UC Not Found -->
|
<!-- Reuse the same CSS files as UC Not Found -->
|
||||||
<link href="../css/welcome.css" rel="stylesheet">
|
<link href="../css/loading.css" rel="stylesheet">
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<script src="../js/helpers.js"></script>
|
<script src="../js/helpers.js"></script>
|
||||||
<title>Welcome</title>
|
<title>Welcome</title>
|
||||||
@@ -15,11 +18,12 @@
|
|||||||
<body onload="fadeIn()">
|
<body onload="fadeIn()">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>BACKUP FETCHER</h1>
|
<h1 class="title">Team Vault</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<img src="../assets/refresh-data.webp" alt="Backup Fetcher" class="backup-img">
|
||||||
<div class="loading-section">
|
<div class="loading-section">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<p>Backup is not searched, please wait...</p>
|
<p>Backup is now searched, please wait...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
@@ -4,33 +4,39 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
<link href="../css/login.css" rel="stylesheet">
|
<link href="../css/login.css" rel="stylesheet">
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<title>Login</title>
|
<title>Login</title>
|
||||||
</head>
|
</head>
|
||||||
<body onload="fadeIn()">
|
<body onload="fadeIn()">
|
||||||
|
<div class="page">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<h1>DO WE KNOW</h1>
|
<h1 class="title">TeamVault</h1>
|
||||||
<h1>EACH OTHER?</h1>
|
<h4 class="subheading">Do we know each other?</h4>
|
||||||
</div>
|
</div>
|
||||||
<form class="login-form" id="loginForm">
|
<form class="login-form" id="loginForm">
|
||||||
<div class="login-form-title">
|
<div class="login-form-title">
|
||||||
<h2>Login</h2>
|
<h2 class="login-title">Login</h2>
|
||||||
<hr>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="login-form-content">
|
<div class="login-form-content">
|
||||||
<input name="email" placeholder="Email" type="email">
|
<input name="email" placeholder="Email" type="email">
|
||||||
<input name="password" placeholder="Password" type="password">
|
<input name="password" placeholder="Password" type="password">
|
||||||
</div>
|
</div>
|
||||||
<div class="login-form-footer">
|
<div class="login-form-footer">
|
||||||
<button id="signup" name="submit" type="button">Sign Up</button>
|
<div>
|
||||||
|
<button id="signup" name="underline" type="button">Sign Up</button>
|
||||||
|
<button id="resetPassword" name="underline" type="button">Reset Password</button>
|
||||||
|
</div>
|
||||||
<button id="submit" name="submit" type="submit">Submit</button>
|
<button id="submit" name="submit" type="submit">Submit</button>
|
||||||
<button id="resetPassword" name="resetPassword" type="button">Reset Password</button>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="../js/login.js"></script>
|
<script src="../js/login.js"></script>
|
||||||
<script src="../js/helpers.js"></script>
|
<script src="../js/helpers.js"></script>
|
||||||
|
|||||||
@@ -4,9 +4,12 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link href="../css/normalizer.css" rel="stylesheet">
|
||||||
<link href="../css/main_menu.css" rel="stylesheet">
|
<link href="../css/main_menu.css" rel="stylesheet">
|
||||||
<link href="../css/transition.css" rel="stylesheet">
|
<link href="../css/transition.css" rel="stylesheet">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<script src="../js/main_menu.js"></script>
|
<script src="../js/main_menu.js"></script>
|
||||||
<script src="../js/helpers.js"></script>
|
<script src="../js/helpers.js"></script>
|
||||||
@@ -14,41 +17,43 @@
|
|||||||
<title>Main Page</title>
|
<title>Main Page</title>
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body onload="fadeIn()">
|
<body onload="initialSetup(); fadeIn()">
|
||||||
<div class="container">
|
<div class="page">
|
||||||
<div class="left_block">
|
<div class="container">
|
||||||
<div class="left_block_top">
|
<div class="left_block">
|
||||||
<div>
|
<div class="notification-title">
|
||||||
<h1>WELCOME BACK,</h1>
|
<h1 class="notif-title">Notifications</h1>
|
||||||
<h1 id="username-field"></h1>
|
</div>
|
||||||
<h2>Hope you have a productive day!</h2>
|
<div class=left-content>
|
||||||
|
<div class="notifications" id="notifications">
|
||||||
|
<!--Notifications are generated here-->
|
||||||
|
</div>
|
||||||
|
<div class="left_block_footer">
|
||||||
|
<button class="logout-button" id="logout" name="logout">Logout</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<img alt="" src="../assets/user_1144760.png">
|
|
||||||
</div>
|
</div>
|
||||||
<div class="left_block_content">
|
|
||||||
<div class="left_block_buttons">
|
<div class="right_block">
|
||||||
|
<div class="navbar">
|
||||||
|
<div class="welcome">
|
||||||
|
<img alt="" src="../assets/user_1144760.png">
|
||||||
|
<h3 class="user">Welcome Back, </h3>
|
||||||
|
<h3 class="user" id="username-field"></h3>
|
||||||
|
<h3 class="user">!</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<h1 class="title">TeamVault</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="right_block_content">
|
||||||
<button id="change-info" name="menu_button">Change your info</button>
|
<button id="change-info" name="menu_button">Change your info</button>
|
||||||
<button id="share-file" name="menu_button">Share a file</button>
|
<button id="share-file" name="menu_button">Share a file</button>
|
||||||
</div>
|
|
||||||
<div class="left_block_buttons">
|
|
||||||
<button id="backup-dir" name="menu_button">Set backup directory</button>
|
<button id="backup-dir" name="menu_button">Set backup directory</button>
|
||||||
<button id="department-dir" name="menu_button">Set department directory</button>
|
<button id="department-dir" name="menu_button">Set department directory</button>
|
||||||
<button id="share-dir" name="menu_button">Set share directory</button>
|
<button id="share-dir" name="menu_button">Set share directory</button>
|
||||||
</div>
|
|
||||||
<div class="left_block_buttons">
|
|
||||||
<button id="restore-backup" name="menu_button">Restore backup</button>
|
<button id="restore-backup" name="menu_button">Restore backup</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="left_block_footer">
|
|
||||||
<button id="logout" name="logout">Logout</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="right_block">
|
|
||||||
<div>
|
|
||||||
<h1>NOTIFICATIONS</h1>
|
|
||||||
<hr>
|
|
||||||
</div>
|
|
||||||
<div class="notifications" id="notifications">
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||