program finalizat

This commit is contained in:
andrei-mihnea-cerbu
2024-10-29 15:07:26 +02:00
parent ab1eaec413
commit 979539d3db
138 changed files with 14602 additions and 5068 deletions
+2
View File
@@ -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
+4972 -114
View File
File diff suppressed because it is too large Load Diff
+48 -5
View File
@@ -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"
}
}
]
}
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

+133
View File
@@ -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 */
}
-176
View File
@@ -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;
}
+59
View File
@@ -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); }
}
+121 -88
View File
@@ -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;
} }
+167 -204
View File
@@ -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 */
} }
+41
View File
@@ -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;
}
+215
View File
@@ -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 */
}
+153
View File
@@ -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);
}
+105 -79
View File
@@ -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;
} }
+129 -88
View File
@@ -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;
} }
+125 -154
View File
@@ -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;
} }
-117
View File
@@ -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;
}
-59
View File
@@ -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;
}
-141
View File
@@ -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;
}
-59
View File
@@ -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;
}
+36
View File
@@ -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">&times;</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>
+12 -6
View File
@@ -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>
+51 -60
View File
@@ -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,&nbsp; </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>
+57
View File
@@ -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">&times;</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">&times;</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>
+34
View File
@@ -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>
+22 -17
View File
@@ -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>
+30
View File
@@ -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>
+11 -7
View File
@@ -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>
+30
View File
@@ -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>
+23 -27
View File
@@ -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>
-63
View File
@@ -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>
+7 -4
View File
@@ -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>
-29
View File
@@ -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>
+6 -3
View File
@@ -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>
+22
View File
@@ -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');
});
});
-216
View File
@@ -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');
}
});
+16 -4
View File
@@ -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
}
}
+93 -79
View File
@@ -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;
}
+219 -197
View File
@@ -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');
}
+209
View File
@@ -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';
});
}
+150
View File
@@ -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');
}
+4 -3
View File
@@ -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 = {
+31
View File
@@ -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');
});
+20 -17
View File
@@ -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;
}
}
+80 -72
View File
@@ -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');
}
-134
View File
@@ -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)
}
});
-142
View File
@@ -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();
});
+84
View File
@@ -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);
});
}
}
+155 -177
View File
@@ -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);
}); });
+102 -154
View File
@@ -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);
});
}
} }
+57 -159
View File
@@ -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
}); });
} }
} }
+46 -80
View File
@@ -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
}); });
} }
} }
-76
View File
@@ -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
}
}
+15 -27
View File
@@ -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
}); });
} }
+68 -7
View File
@@ -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...');
+80 -16
View File
@@ -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
);
});
} }
+4 -2
View File
@@ -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),
}); });
+7 -3
View File
@@ -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){
-3
View File
@@ -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;
} }
+1 -13
View File
@@ -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
+94
View File
@@ -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);
+3 -2
View File
@@ -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);
-2
View File
@@ -1,4 +1,2 @@
UDP_PORT=41234 UDP_PORT=41234
TCP_PORT=41233 TCP_PORT=41233
HOST=0.0.0.0
+4086 -26
View File
File diff suppressed because it is too large Load Diff
+48 -5
View File
@@ -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"
}
}
]
}
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

+79
View File
@@ -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;
}
+59
View File
@@ -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); }
}
+121 -88
View File
@@ -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;
} }
+166 -194
View File
@@ -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 */
} }
+41
View File
@@ -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;
}
+105 -79
View File
@@ -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;
} }
+129 -88
View File
@@ -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;
} }
+125 -154
View File
@@ -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;
} }
+146 -80
View File
@@ -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 */
} }
-59
View File
@@ -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;
}
-59
View File
@@ -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;
}
+31
View File
@@ -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>
-26
View File
@@ -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>
+13 -7
View File
@@ -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>
+32 -27
View File
@@ -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,&nbsp; </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>

Some files were not shown because too many files have changed in this diff Show More