Merged CEO and Client App
@@ -0,0 +1,3 @@
|
||||
UDP_PORT=41234
|
||||
TCP_PORT=41233
|
||||
IS_CLIENT=true
|
||||
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
dist
|
||||
package-lock.json
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"printWidth": 100
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"name": "clientapp",
|
||||
"productName": "ClientApp",
|
||||
"version": "1.0.0",
|
||||
"description": "Aplicatie P2P pentru stocarea resurselor digitale",
|
||||
"scripts": {
|
||||
"format": "prettier --write ./src/**/*.ts",
|
||||
"clean": "del-cli dist && del-cli out",
|
||||
"build-dist": "tsc && copyfiles -u 1 'src/**/*' dist",
|
||||
"start-dev": "tsc && copyfiles -u 1 'src/**/*' dist && electron dist/main/main.js -- --expose-gc",
|
||||
"start": "npm run clean && npm run build-dist && electron-forge start -- --expose-gc",
|
||||
"package": "npm run clean && npm run build-dist && electron-forge package -- --expose-gc",
|
||||
"make": "npm run clean && npm run build-dist && electron-forge make -- --expose-gc"
|
||||
},
|
||||
"main": "dist/main/main.js",
|
||||
"author": "Cerbu Andrei - Mihnea",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"check-disk-space": "^3.4.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"jsonfile": "^6.1.0",
|
||||
"ping": "^0.4.4",
|
||||
"uuid": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^6.0.0",
|
||||
"@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/jsonfile": "^6.1.4",
|
||||
"@types/ping": "^0.4.4",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
"del-cli": "^5.0.0",
|
||||
"electron": "^33.0.2",
|
||||
"node-disk-info": "^1.3.0",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript": "^5.6.2"
|
||||
},
|
||||
"config": {
|
||||
"forge": {
|
||||
"packagerConfig": {
|
||||
"executableName": "clientapp",
|
||||
"name": "ClientApp",
|
||||
"icon": "../app_icons/icon.ico",
|
||||
"ignore": [
|
||||
"src",
|
||||
".idea",
|
||||
"tsconfig.json"
|
||||
]
|
||||
},
|
||||
"makers": [
|
||||
{
|
||||
"name": "@electron-forge/maker-squirrel",
|
||||
"config": {
|
||||
"name": "CEOApp",
|
||||
"setupIcon": "../app_icons/icon.ico"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "@electron-forge/maker-zip",
|
||||
"platforms": [
|
||||
"darwin"
|
||||
],
|
||||
"config": {
|
||||
"icon": "../app_icons/icon.icns"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "@electron-forge/maker-deb",
|
||||
"platforms": [
|
||||
"linux"
|
||||
],
|
||||
"config": {
|
||||
"icon": "../app_icons/icon.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1,133 @@
|
||||
body, html {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Pridi", sans-serif;
|
||||
background-color: #4A628A;
|
||||
}
|
||||
|
||||
h4, h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
height: 90%;
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 48px;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.subheading {
|
||||
padding-top: 8px;
|
||||
font-size: 16px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.announcement-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 230px;
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
font-size: 1em;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ccc;
|
||||
resize: none;
|
||||
background-color: #3A4A6C; /* Darker background for the textarea */
|
||||
color: #f0f0f0; /* Light text color */
|
||||
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
textarea::placeholder {
|
||||
opacity: 0.7;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Customize the textarea scrollbar */
|
||||
textarea::-webkit-scrollbar {
|
||||
width: 10px; /* Width of the scrollbar */
|
||||
}
|
||||
|
||||
textarea::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1); /* Track background color */
|
||||
border-radius: 10px; /* Rounded corners for the track */
|
||||
}
|
||||
|
||||
textarea::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(74, 98, 138, 0.8); /* Scrollbar handle color */
|
||||
border-radius: 10px; /* Rounded corners for the scrollbar handle */
|
||||
}
|
||||
|
||||
textarea::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(74, 98, 138, 1); /* Darker color on hover */
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
border-color: #4A628A;
|
||||
box-shadow: 0 0 5px rgba(74, 98, 138, 0.5);
|
||||
background-color: #334466; /* Slightly brighter background on focus */
|
||||
}
|
||||
|
||||
|
||||
.announcement-form-footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
gap: 10px; /* Space between buttons */
|
||||
}
|
||||
|
||||
button#submitAnnouncement {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
font-weight: 300;
|
||||
background-color: #4A628A;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
button#backButton {
|
||||
background-color: transparent; /* No background */
|
||||
border: none; /* Remove default button border */
|
||||
color: #4A628A; /* Set text color */
|
||||
font-size: 1em; /* Set font size */
|
||||
cursor: pointer; /* Add pointer cursor for interactivity */
|
||||
text-decoration: underline; /* Underline text */
|
||||
padding: 0; /* Remove default padding */
|
||||
}
|
||||
@@ -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); }
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
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: 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 {
|
||||
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 */
|
||||
.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"] {
|
||||
width: 100%;
|
||||
background-color: #4A628A; /* Blue background */
|
||||
color: white; /* White text color */
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
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;
|
||||
}
|
||||
|
||||
.title{
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
letter-spacing: -1px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.user{
|
||||
display: inline;
|
||||
color: white;
|
||||
}
|
||||
.navbar{
|
||||
padding-top: 8px;
|
||||
padding-right: 32px;
|
||||
padding-left: 32px;
|
||||
height: 10%;
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.welcome{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
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;
|
||||
}
|
||||
|
||||
.left_block{
|
||||
background: linear-gradient(to bottom, #131A24, #4A628A);
|
||||
width: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.notification-title{
|
||||
padding-top: 16px;
|
||||
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;
|
||||
border-radius: 15px;
|
||||
width: 80%;
|
||||
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;
|
||||
}
|
||||
|
||||
.notification:hover{
|
||||
background: #d9efff;
|
||||
}
|
||||
|
||||
.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;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.right_block_content{
|
||||
height: 90%;
|
||||
}
|
||||
|
||||
.right_block{
|
||||
width: 75%;
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right_block_content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr); /* 3 equal-width columns */
|
||||
gap: 8px; /* Spacing between buttons */
|
||||
padding: 32px; /* Optional padding */
|
||||
}
|
||||
|
||||
button[name="menu_button"] {
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
background-color: rgba(186,229,244,0.4); /* Button background */
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
button[name="menu_button"]:hover {
|
||||
background-color: #3B5173; /* Darker background on hover */
|
||||
}
|
||||
|
||||
.notifications::-webkit-scrollbar {
|
||||
width: 4px; /* Width of the scrollbar */
|
||||
}
|
||||
|
||||
.notifications::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1); /* Track background color */
|
||||
border-radius: 10px; /* Rounded corners for the track */
|
||||
}
|
||||
|
||||
.notifications::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(74, 98, 138, 0.8); /* Scrollbar handle color */
|
||||
border-radius: 10px; /* Rounded corners for the scrollbar handle */
|
||||
}
|
||||
|
||||
.notifications::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(74, 98, 138, 1); /* Darker color on hover */
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre, hr
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
body, html {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||
background-color: #4A628A;
|
||||
}
|
||||
|
||||
h4, h2, h1{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page{
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
height: 90%;
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
.header{
|
||||
text-align: center;
|
||||
padding-bottom: 16px;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
.title{
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.departments-container{
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
height: 370px;
|
||||
max-height: 370px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.department-name{
|
||||
width:70%;
|
||||
font-size: 16px;
|
||||
letter-spacing: -0.5px;
|
||||
color: rgba(255,255,255, 1);
|
||||
}
|
||||
|
||||
.user-management-container{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
justify-content: space-between; /* Align buttons next to each other */
|
||||
align-items: center;
|
||||
gap: 10px; /* Space between buttons */
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* Styling for buttons */
|
||||
.user-management-container button, .modal button {
|
||||
flex: 1; /* Ensure buttons are equal width */
|
||||
padding: 8px 16px 8px 16px;
|
||||
font-weight: 300;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
button[name="underline"] {
|
||||
background-color: transparent; /* No background */
|
||||
border: none; /* Remove default button border */
|
||||
color: #4A628A; /* Set text color */
|
||||
font-size: 1em; /* Set font size */
|
||||
cursor: pointer; /* Add pointer cursor for interactivity */
|
||||
text-decoration: underline; /* Underline text */
|
||||
padding: 0; /* Remove default padding */
|
||||
}
|
||||
|
||||
.department-card{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
width: auto;
|
||||
margin-right: 8px;
|
||||
background-color: #ED4B4B; /* Blue background */
|
||||
color: white; /* White text color */
|
||||
}
|
||||
|
||||
button[name="submit"] {
|
||||
width: 100%;
|
||||
background-color: #4A628A; /* Blue background */
|
||||
color: white; /* White text color */
|
||||
}
|
||||
|
||||
.departments-container::-webkit-scrollbar {
|
||||
width: 4px; /* Width of the scrollbar */
|
||||
}
|
||||
|
||||
.departments-container::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1); /* Track background color */
|
||||
border-radius: 10px; /* Rounded corners for the track */
|
||||
}
|
||||
|
||||
.departments-container::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(74, 98, 138, 0.8); /* Scrollbar handle color */
|
||||
border-radius: 10px; /* Rounded corners for the scrollbar handle */
|
||||
}
|
||||
|
||||
.departments-container::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(74, 98, 138, 1); /* Darker color on hover */
|
||||
}
|
||||
|
||||
.departments-container:empty {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.user-name{
|
||||
padding-top: 8px;
|
||||
font-size: 20px;
|
||||
letter-spacing: -0.5px;
|
||||
color: rgba(255,255,255, 1);
|
||||
}
|
||||
|
||||
.user-department{
|
||||
font-weight: lighter;
|
||||
padding-top: 8px;
|
||||
font-size: 14px;
|
||||
letter-spacing: -0.5px;
|
||||
color: rgba(255,255,255, 0.8);
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none; /* Hidden by default */
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(5px);/* Black with opacity */
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: 15% auto;
|
||||
padding: 16px;
|
||||
border: 1px solid #888;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
float: right;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.close-button:hover,
|
||||
.close-button:focus {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input{
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid #ccc; /* Light gray border */
|
||||
border-radius: 8px; /* Rounded corners */
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
box-sizing: border-box;
|
||||
background-color: #f9f9f9; /* Light background */
|
||||
color: #333; /* Dark text color */
|
||||
transition: border-color 0.3s ease, box-shadow 0.3s ease; /* Smooth transition for interaction */
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
body, html {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||
background-color: #4A628A;
|
||||
}
|
||||
|
||||
h4, h2, h1{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page{
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(to bottom, rgba(74, 98, 138, 0.0) 30%, rgba(186, 237, 255, 0.7));
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
height: 90%;
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
.header{
|
||||
text-align: center;
|
||||
padding-bottom: 16px;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
.title{
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.users-container{
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
height: 370px;
|
||||
max-height: 370px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.user-management-container{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: space-between; /* Align buttons next to each other */
|
||||
align-items: center;
|
||||
gap: 10px; /* Space between buttons */
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* Styling for buttons */
|
||||
.user-management-container button {
|
||||
flex: 1; /* Ensure buttons are equal width */
|
||||
padding: 16px;
|
||||
font-weight: 300;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
button[name="underline"] {
|
||||
background-color: transparent; /* No background */
|
||||
border: none; /* Remove default button border */
|
||||
color: #4A628A; /* Set text color */
|
||||
font-size: 1em; /* Set font size */
|
||||
cursor: pointer; /* Add pointer cursor for interactivity */
|
||||
text-decoration: underline; /* Underline text */
|
||||
padding: 0; /* Remove default padding */
|
||||
}
|
||||
|
||||
.user-info{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.user-card{
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
width: auto;
|
||||
margin-right: 8px;
|
||||
background-color: #ED4B4B; /* Blue background */
|
||||
color: white; /* White text color */
|
||||
}
|
||||
|
||||
.users-container::-webkit-scrollbar {
|
||||
width: 4px; /* Width of the scrollbar */
|
||||
}
|
||||
|
||||
.users-container::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1); /* Track background color */
|
||||
border-radius: 10px; /* Rounded corners for the track */
|
||||
}
|
||||
|
||||
.users-container::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(74, 98, 138, 0.8); /* Scrollbar handle color */
|
||||
border-radius: 10px; /* Rounded corners for the scrollbar handle */
|
||||
}
|
||||
|
||||
.users-container::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(74, 98, 138, 1); /* Darker color on hover */
|
||||
}
|
||||
|
||||
.users-container:empty {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.user-name{
|
||||
padding-top: 8px;
|
||||
font-size: 20px;
|
||||
letter-spacing: -0.5px;
|
||||
color: rgba(255,255,255, 1);
|
||||
}
|
||||
|
||||
.user-department{
|
||||
font-weight: lighter;
|
||||
padding-top: 8px;
|
||||
font-size: 14px;
|
||||
letter-spacing: -0.5px;
|
||||
color: rgba(255,255,255, 0.8);
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
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: 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);
|
||||
}
|
||||
|
||||
.profile-form{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
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 {
|
||||
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 */
|
||||
.profile-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;
|
||||
}
|
||||
|
||||
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"] {
|
||||
width: 100%;
|
||||
background-color: #4A628A; /* Blue background */
|
||||
color: white; /* White text color */
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
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-between;
|
||||
height: 90%;
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
.header{
|
||||
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 {
|
||||
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 */
|
||||
.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"] {
|
||||
width: 100%;
|
||||
background-color: #4A628A; /* Blue background */
|
||||
color: white; /* White text color */
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
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;
|
||||
height: 90%;
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
.header{
|
||||
text-align: center;
|
||||
padding-bottom: 16px;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.form{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
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 {
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 180px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
h5{
|
||||
font-size: 20px;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Styling for buttons */
|
||||
.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;
|
||||
}
|
||||
|
||||
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"] {
|
||||
width: 100%;
|
||||
background-color: #4A628A; /* Blue background */
|
||||
color: white; /* White text color */
|
||||
}
|
||||
|
||||
button[name="submit"]:hover {
|
||||
background-color: #3B5173; /* Darker background on hover */
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
.fade-in {
|
||||
animation: fadeInAnimation 0.5s ease-in forwards;
|
||||
}
|
||||
|
||||
.fade-out {
|
||||
animation: fadeOutAnimation 0.5s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeInAnimation {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOutAnimation {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
<!-- Reuse the same CSS files as UC Not Found -->
|
||||
<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>Welcome</title>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 class="title">Team Vault</h1>
|
||||
</div>
|
||||
<img src="../assets/refresh-data.webp" alt="Backup Fetcher" class="backup-img">
|
||||
<div class="loading-section">
|
||||
<div class="spinner"></div>
|
||||
<p>Backup is not searched, please wait...</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,43 @@
|
||||
<!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/login.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>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="page">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 class="title">TeamVault</h1>
|
||||
<h4 class="subheading">Do we know each other?</h4>
|
||||
</div>
|
||||
<form class="login-form" id="loginForm">
|
||||
<div class="login-form-title">
|
||||
<h2 class="login-title">Login</h2>
|
||||
</div>
|
||||
<div class="login-form-content">
|
||||
<input name="email" placeholder="Email" type="email">
|
||||
<input name="password" placeholder="Password" type="password">
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../js/login.js"></script>
|
||||
<script src="../js/helpers.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,65 @@
|
||||
<!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/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">
|
||||
|
||||
<script src="../js/main_menu.js"></script>
|
||||
<script src="../js/helpers.js"></script>
|
||||
|
||||
<title>Main Page</title>
|
||||
|
||||
</head>
|
||||
<body onload="initialSetup(); fadeIn()">
|
||||
<div class="page">
|
||||
<div class="container">
|
||||
<div class="left_block">
|
||||
<div class="notification-title">
|
||||
<h1 class="notif-title">Notifications</h1>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<div class="right_block">
|
||||
<div class="navbar">
|
||||
<div class="welcome">
|
||||
<img alt="" src="../assets/user_1144760.png">
|
||||
<h3 class="user">Welcome Back, </h3>
|
||||
<h3 class="user" id="username-field"></h3>
|
||||
<h3 class="user">!</h3>
|
||||
|
||||
</div>
|
||||
<h1 class="title">TeamVault</h1>
|
||||
</div>
|
||||
|
||||
<div class="right_block_content">
|
||||
<button id="change-info" name="menu_button">Change your info</button>
|
||||
<button id="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>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||
<link href="../css/normalizer.css" rel="stylesheet">
|
||||
<link href="../css/overview_departments.css" rel="stylesheet">
|
||||
<link href="../css/transition.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script src="../js/helpers.js"></script>
|
||||
<script src="../js/overview_departments.js"></script>
|
||||
|
||||
<title>Manage Departments</title>
|
||||
</head>
|
||||
<body onload="fetchDepartments(); fadeIn()">
|
||||
<div class="page">
|
||||
<div class="container">
|
||||
<div class="user-management-container">
|
||||
<h1 class="title">Manage Departments</h1>
|
||||
<div id="departments-container" class="departments-container">
|
||||
<!-- Department divs will be dynamically inserted here -->
|
||||
</div>
|
||||
<div class="footer">
|
||||
<button id="backButton" name="underline" type="button">Back</button>
|
||||
<button id="createDepartmentButton" name="submit" type="button">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Department Modal -->
|
||||
<div id="createDepartmentModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span id="closeCreateModal" class="close-button">×</span>
|
||||
<h2 class="title" style="color: black; margin-bottom: 16px;">Create Department</h2>
|
||||
<input type="text" id="newDepartmentName" placeholder="Department Name" />
|
||||
<button id="confirmCreateButton" name="submit" type="button"
|
||||
style="width: 30%;"
|
||||
>Create</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modify Department Modal -->
|
||||
<div id="modifyDepartmentModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span id="closeModifyModal" class="close-button">×</span>
|
||||
<h2 class="title" style="color: black; margin-bottom: 16px;">Modify Department</h2>
|
||||
<input type="text" id="modifiedDepartmentName" placeholder="New Department Name" />
|
||||
<button id="confirmModifyButton" name="submit" type="button"
|
||||
style="width: 30%;"
|
||||
>Modify</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||
<link href="../css/normalizer.css" rel="stylesheet">
|
||||
<link href="../css/overview_users.css" rel="stylesheet">
|
||||
<link href="../css/transition.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script src="../js/helpers.js"></script>
|
||||
<script src="../js/overview_users.js"></script>
|
||||
|
||||
<title>Manage Users</title>
|
||||
</head>
|
||||
<body onload="fetchData(); fadeIn()">
|
||||
<div class="page">
|
||||
<div class="container">
|
||||
<div class="user-management-container">
|
||||
<h1 class="title">Manage Users</h1>
|
||||
<div id="users-container" class="users-container">
|
||||
<!-- User divs will be dynamically inserted here -->
|
||||
</div>
|
||||
<div class="footer">
|
||||
<button id="backButton" name="underline" type="button">Back</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,40 @@
|
||||
<!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/profile.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/helpers.js"></script>
|
||||
|
||||
<title>Profile</title>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="page">
|
||||
<div class="container">
|
||||
<form class="profile-form" id="profileForm">
|
||||
<div class="header">
|
||||
<h1 class="title">Profile</h1>
|
||||
<h3 class="subheading">Who are you?</h3>
|
||||
</div>
|
||||
<div class="profile-form-content">
|
||||
<input name="email" placeholder="Email" type="email">
|
||||
<input name="username" placeholder="Username" type="text">
|
||||
<input name="password" placeholder="Password" type="password">
|
||||
</div>
|
||||
<div class="profile-form-footer">
|
||||
<button id="back" name="underline" type="button">Back</button>
|
||||
<button id="submit" name="submit" type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||
<link href="../css/normalizer.css" rel="stylesheet">
|
||||
<link href="../css/loading.css" rel="stylesheet">
|
||||
<link href="../css/transition.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script src="../js/helpers.js"></script>
|
||||
<script src="../js/reset_database.js"></script>
|
||||
<title>UC Not Found</title>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 class="title">TeamVault</h1>
|
||||
</div>
|
||||
<img src="../assets/2581896.png" style="margin-left: 20px" alt="Connecting to server" class="server-img">
|
||||
<div class="loading-section">
|
||||
<div class="spinner"></div>
|
||||
<p>Please wait while we reset the database.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<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="../css/reset_password.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>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="page">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 class="title">TeamVault</h1>
|
||||
<h3 class="subheading">Forgot your password? No worries, it happens to the best of us!</h3>
|
||||
</div>
|
||||
<form class="login-form" id="resetPasswordForm">
|
||||
<div class="login-form-title">
|
||||
<h2 class="login-title">Reset Password</h2>
|
||||
</div>
|
||||
<div class="login-form-content">
|
||||
<input name="email" placeholder="Email" type="email" required>
|
||||
<input name="newPassword" placeholder="New Password" type="password" required>
|
||||
</div>
|
||||
<div class="login-form-footer">
|
||||
<button id="backToLogin" name="underline" type="button">Back to Login</button>
|
||||
<button id="resetPassword" name="submit" type="submit">Reset Password</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script src="../js/reset_password.js"></script>
|
||||
<script src="../js/helpers.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,45 @@
|
||||
<!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/share_file.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/helpers.js"></script>
|
||||
|
||||
<title>Share File</title>
|
||||
</head>
|
||||
<body onload="fetchUsersAndCreateCheckboxes(); updateFileName(); fadeIn()">
|
||||
<div class="page">
|
||||
<div class="container">
|
||||
<div class="form">
|
||||
<div class="header">
|
||||
<h1 class="title">Share a File</h1>
|
||||
<h3 class="subheading">First select the file you want to share.</h3>
|
||||
<button class="filename-title" id="selectFile" name="submit" type="button">Select</button>
|
||||
<form class="users_block" id="userDestForm">
|
||||
<h5>Users</h5>
|
||||
<h3 class="subheading">Select the users you want to share the file with.</h3>
|
||||
<div id="choose_user_form_content" class="choose_user_form_content">
|
||||
<!-- Insert the users from the database -->
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="footer">
|
||||
<button id="backButton" name="underline" type="button">Back</button>
|
||||
<button id="submitButton" name="submit" type="submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||
<link href="../css/normalizer.css" rel="stylesheet">
|
||||
<link href="../css/loading.css" rel="stylesheet">
|
||||
<link href="../css/transition.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script src="../js/helpers.js"></script>
|
||||
|
||||
<title>UC Not Found</title>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 class="title">TeamVault</h1>
|
||||
</div>
|
||||
<img src="../assets/cloud-computing.webp" alt="Connecting to server" class="server-img">
|
||||
<div class="loading-section">
|
||||
<div class="spinner"></div>
|
||||
<p>Please wait while we try to connect to the server.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<!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">
|
||||
<!-- Reuse the same CSS files as UC Not Found -->
|
||||
<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>Welcome</title>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 class="title">Welcome to TeamVault</h1>
|
||||
</div>
|
||||
<div class="loading-section">
|
||||
<div class="spinner"></div>
|
||||
<p>Initializing the application, please wait...</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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.uiAPI.showAlert('Please enter a message before sending.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the announcement message via the electronAPI
|
||||
window.workersAPI.startAnnouncementWorker(message);
|
||||
fadeOut('send_announcement');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,47 @@
|
||||
function fadeIn() {
|
||||
document.querySelector('.container').classList.remove('fade-out');
|
||||
document.querySelector('.container').classList.add('fade-in');
|
||||
}
|
||||
|
||||
function fadeOut(destination) {
|
||||
const container = document.querySelector('.container');
|
||||
container.classList.remove('fade-in');
|
||||
container.classList.add('fade-out');
|
||||
|
||||
console.log(destination);
|
||||
setTimeout(() => {}, 5000);
|
||||
|
||||
container.addEventListener('animationend', async () => {
|
||||
try {
|
||||
await window.uiAPI.changeContent(destination);
|
||||
console.log('Navigated to', destination);
|
||||
} catch (error) {
|
||||
console.error('Error navigating:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForResponse() {
|
||||
return new Promise((resolve) => {
|
||||
const idResponseCheck = setInterval(async () => {
|
||||
if (await window.networkAPI.hasResponseArrived()) {
|
||||
clearInterval(idResponseCheck);
|
||||
resolve(await window.networkAPI.getLastUcResult()); // Resolve the response or null if not available
|
||||
}
|
||||
}, 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
let codeLogin = '';
|
||||
let codeFindByEmail = '';
|
||||
let codeFindKeyByUser = '';
|
||||
let codeOk = '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
const submitButton = document.getElementById('submit');
|
||||
const resetPasswordButton = document.getElementById('resetPassword');
|
||||
|
||||
// Retrieve the operation codes via IPC
|
||||
const operationCodes = await window.networkAPI.getOperationsCodes();
|
||||
if (!operationCodes) {
|
||||
await window.uiAPI.showAlert('Internal error of the application. Failed to retrieve operation codes.');
|
||||
return;
|
||||
}
|
||||
|
||||
codeLogin = operationCodes.LOGIN;
|
||||
codeFindByEmail = operationCodes.FIND_BY_EMAIL;
|
||||
codeFindKeyByUser = operationCodes.FIND_KEY_BY_USER_ID;
|
||||
codeOk = operationCodes.OK;
|
||||
|
||||
resetPasswordButton.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
window.uiAPI.changeContent('reset_password');
|
||||
});
|
||||
|
||||
// Submit button logic (handle login)
|
||||
submitButton.addEventListener('click', async function (e) {
|
||||
e.preventDefault();
|
||||
console.log('Submit button clicked');
|
||||
|
||||
const form = document.getElementById('loginForm');
|
||||
const formData = new FormData(form);
|
||||
|
||||
const email = formData.get('email');
|
||||
const password = formData.get('password');
|
||||
|
||||
// Open a TCP socket to the stored IP
|
||||
if (!await window.networkAPI.openUcSocket()) {
|
||||
await window.uiAPI.showAlert('Internal error of the application. Unable to open socket.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt login
|
||||
if (!await attemptLogin(email, password)) {
|
||||
await window.networkAPI.closeUcSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch and store user info
|
||||
if (!await fetchAndStoreUserInfo(email)) {
|
||||
await window.networkAPI.closeUcSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch user info from local storage
|
||||
const userInfo = await window.databaseAPI.getUserInfo('user_info');
|
||||
if (!userInfo) {
|
||||
await window.networkAPI.closeUcSocket();
|
||||
await window.uiAPI.showAlert('Failed to fetch user info.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch and store encryption key
|
||||
if (!await fetchAndStoreEncryptionKey(userInfo.id)) {
|
||||
await window.networkAPI.closeUcSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the socket and navigate to main menu after success
|
||||
await window.networkAPI.closeUcSocket();
|
||||
await window.workersAPI.startWorkers();
|
||||
await window.databaseAPI.setLoginStatus(true);
|
||||
await window.uiAPI.changeContent('main_menu');
|
||||
});
|
||||
});
|
||||
|
||||
async function attemptLogin(email, password) {
|
||||
const app_type = await window.databaseAPI.getAppType();
|
||||
const messageData = {email, password, app_type};
|
||||
|
||||
// Send the login message to the server
|
||||
if (!await window.networkAPI.sendUcMessage(codeLogin, messageData)) {
|
||||
await window.uiAPI.showAlert('Failed to send login request.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait for the response using waitForResponse
|
||||
const response = await waitForResponse();
|
||||
|
||||
if (!response) {
|
||||
await window.uiAPI.showAlert('No response from server.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.operationCode !== codeOk) {
|
||||
await window.uiAPI.showAlert(response.metaInfo.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function fetchAndStoreUserInfo(userEmail) {
|
||||
if (!await window.networkAPI.sendUcMessage(codeFindByEmail, {email: userEmail})) {
|
||||
await window.uiAPI.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) {
|
||||
const userInfo = {};
|
||||
userInfo.id = response.metaInfo.id;
|
||||
userInfo.email = response.metaInfo.email;
|
||||
userInfo.departmentId = response.metaInfo.departmentId;
|
||||
userInfo.name = response.metaInfo.name || 'User';
|
||||
|
||||
await window.databaseAPI.writeUserInfo(userInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
await window.uiAPI.showAlert('Failed to fetch user info from server.');
|
||||
return false;
|
||||
}
|
||||
|
||||
async function fetchAndStoreEncryptionKey(userId) {
|
||||
if (!await window.networkAPI.sendUcMessage(codeFindKeyByUser, {userId})) {
|
||||
await window.uiAPI.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.databaseAPI.writeEncryptionKey(encryptionKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
await window.uiAPI.showAlert('Failed to fetch encryption key from server.');
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
setInterval(loadReceivedFiles, 3000); // Call loadReceivedFiles every 3000 ms (3 seconds)
|
||||
|
||||
const backupButton = document.getElementById('backup-dir');
|
||||
const shareButton = document.getElementById('share-dir');
|
||||
|
||||
const changeInfoButton = document.getElementById('change-info');
|
||||
const shareFileButton = document.getElementById('share-file');
|
||||
const logoutButton = document.getElementById('logout');
|
||||
const restoreBackupButton = document.getElementById('restore-backup');
|
||||
const buttonOverviewDepartments = document.getElementById('overview-departments');
|
||||
const buttonOverviewUsers = document.getElementById('overview-users');
|
||||
const buttonResetDatabase = document.getElementById('reset-database');
|
||||
const buttonSendAnnouncement = document.getElementById('send-announcement');
|
||||
|
||||
backupButton.addEventListener('click', async function () {
|
||||
await setPath('backupDirectory');
|
||||
});
|
||||
|
||||
shareButton.addEventListener('click', async function () {
|
||||
await setPath('shareDirectory');
|
||||
});
|
||||
|
||||
buttonResetDatabase.addEventListener('click', async function () {
|
||||
// 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.uiAPI.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 () {
|
||||
fadeOut('profile');
|
||||
});
|
||||
|
||||
shareFileButton.addEventListener('click', function () {
|
||||
fadeOut('share_file');
|
||||
});
|
||||
|
||||
logoutButton.addEventListener('click', async function () {
|
||||
fadeOut('login');
|
||||
});
|
||||
|
||||
// Add event listener for the restore backup button
|
||||
restoreBackupButton.addEventListener('click', async function () {
|
||||
await restoreBackup();
|
||||
});
|
||||
});
|
||||
|
||||
async function initialSetup(){
|
||||
await checkAndSetAllDirectories();
|
||||
await loadReceivedFiles();
|
||||
await fetchUserInfo();
|
||||
}
|
||||
|
||||
async function restoreBackup() {
|
||||
const backupDirectory = await window.databaseAPI.isBackupSet();
|
||||
|
||||
if (backupDirectory) {
|
||||
await window.uiAPI.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.uiAPI.selectDirectory();
|
||||
if (!destinationPath) {
|
||||
return; // User canceled the directory selection
|
||||
}
|
||||
|
||||
// Call the IPC method to initiate the backup retrieval process
|
||||
window.workersAPI.startBackupRetrieval(destinationPath);
|
||||
|
||||
// Switch the content to the 'backup_retrieve' page
|
||||
fadeOut('backup_retrieve');
|
||||
}
|
||||
|
||||
async function checkAndSetAllDirectories() {
|
||||
const directorySchemes = await window.databaseAPI.getLocalResources();
|
||||
|
||||
backupDirId = directorySchemes.backup.id;
|
||||
shareDirId = directorySchemes.shared.id;
|
||||
departmentDirId = directorySchemes.department.id;
|
||||
|
||||
await attachNotificationButton(backupDirId, 'Set your backup directory!', 'backup_alert', 'alert');
|
||||
await attachNotificationButton(shareDirId, 'Set your share directory!', 'share_alert', 'alert');
|
||||
await attachNotificationButton(departmentDirId, 'Set your department directory!', 'department_alert', 'alert');
|
||||
}
|
||||
|
||||
async function checkPathExistence(id) {
|
||||
const dirInfo = await window.databaseAPI.getDirectoryInfo(id);
|
||||
return dirInfo.path !== ''
|
||||
}
|
||||
|
||||
async function setPath(id){
|
||||
const path = await window.uiAPI.selectDirectory();
|
||||
if (path === undefined) return false;
|
||||
|
||||
return await window.databaseAPI.writeDirectoryPath(id, path);
|
||||
}
|
||||
|
||||
async function attachNotificationButton(entryId, buttonText, buttonId, buttonName) {
|
||||
const path = await checkPathExistence(entryId);
|
||||
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 () {
|
||||
if(await setPath(entryId)) button.remove();
|
||||
});
|
||||
notificationsDiv.appendChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchUserInfo() {
|
||||
const usernameField = document.getElementById('username-field');
|
||||
|
||||
// Read the user credentials from the userConfig
|
||||
let userInfo = await window.databaseAPI.getUserInfo();
|
||||
if (userInfo && userInfo.name) {
|
||||
usernameField.textContent = userInfo.name;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the greeting with the fetched user's name
|
||||
if (usernameField) {
|
||||
usernameField.textContent = userInfo.name;
|
||||
} else {
|
||||
console.error("Username field is not available in the DOM.");
|
||||
}
|
||||
}
|
||||
|
||||
async function loadReceivedFiles() {
|
||||
// Read the shareDirectory from applicationInfo
|
||||
const shareDirData = await window.databaseAPI.getDirectoryInfo(shareDirId);
|
||||
|
||||
console.log('Loading received files:', shareDirData);
|
||||
|
||||
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(shareDirData.structure).forEach(userName => {
|
||||
const userFiles = shareDirData.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 handleFileReceivedButtonPressed(filePath, button) {
|
||||
console.log('Notification button clicked!');
|
||||
|
||||
// Open the file in the file explorer
|
||||
await window.uiAPI.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();
|
||||
}
|
||||
|
||||
async function showConfirmationDialog(message) {
|
||||
return new Promise((resolve) => {
|
||||
const userResponse = window.confirm(message); // Show native confirmation dialog
|
||||
resolve(userResponse); // Resolve with true (OK) or false (Cancel)
|
||||
});
|
||||
}
|
||||
|
||||
async function resetDatabase(){
|
||||
fadeOut('reset_database');
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
let codeOk = '';
|
||||
let codeGetDepartments = '';
|
||||
let codeCreateDepartment = '';
|
||||
let codeModifyDepartment = '';
|
||||
let codeDeleteDepartment = '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const backButton = document.getElementById('backButton');
|
||||
const createButton = document.getElementById('createDepartmentButton');
|
||||
const closeCreateModalButton = document.getElementById('closeCreateModal');
|
||||
const confirmCreateButton = document.getElementById('confirmCreateButton');
|
||||
|
||||
if (!await window.networkAPI.openUcSocket()) {
|
||||
await window.uiAPI.showAlert('Internal error of the application. Unable to open socket.');
|
||||
return;
|
||||
}
|
||||
|
||||
backButton.addEventListener('click', async () => {
|
||||
await window.networkAPI.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.uiAPI.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.networkAPI.getOperationsCodes();
|
||||
if (!operationCodes) {
|
||||
await window.uiAPI.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.networkAPI.sendUcMessage(codeGetDepartments)) {
|
||||
await window.uiAPI.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.uiAPI.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.networkAPI.sendUcMessage(codeCreateDepartment, { departmentName: departmentName })) {
|
||||
await window.uiAPI.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.uiAPI.showAlert('Could not create new department.');
|
||||
return false;
|
||||
}
|
||||
|
||||
await fetchDepartments();
|
||||
}
|
||||
|
||||
async function modifyDepartment(departmentId, newDepartmentName) {
|
||||
if (!await window.networkAPI.sendUcMessage(codeModifyDepartment, { departmentId: departmentId, newDepartmentName: newDepartmentName })) {
|
||||
await window.uiAPI.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.uiAPI.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.networkAPI.sendUcMessage(codeDeleteDepartment, { departmentId: departmentId })) {
|
||||
await window.uiAPI.showAlert('Failed to send delete request.');
|
||||
return false;
|
||||
}
|
||||
|
||||
const response = await waitForResponse();
|
||||
|
||||
if(!response || response.operationCode !== codeOk){
|
||||
await window.uiAPI.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.uiAPI.showAlert("Please enter a new department name.");
|
||||
return;
|
||||
}
|
||||
await modifyDepartment(departmentId, newDepartmentName);
|
||||
document.getElementById('modifyDepartmentModal').style.display = 'none';
|
||||
});
|
||||
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
let codeGetDepartments = '';
|
||||
let codeGetUsers = '';
|
||||
let codeDeleteUser = '';
|
||||
let codeOk = '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const backButton = document.getElementById('backButton');
|
||||
backButton.addEventListener('click', async () => {
|
||||
await window.networkAPI.closeUcSocket();
|
||||
fadeOut('main_menu');
|
||||
});
|
||||
});
|
||||
|
||||
async function fetchData() {
|
||||
const operationCodes = await window.networkAPI.getOperationsCodes();
|
||||
if (!operationCodes) {
|
||||
await window.uiAPI.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.networkAPI.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.networkAPI.sendUcMessage(codeGetDepartments)) {
|
||||
return null;
|
||||
}
|
||||
return await waitForResponse();
|
||||
}
|
||||
|
||||
async function fetchUsers() {
|
||||
// Send the request to get users
|
||||
if (!await window.networkAPI.sendUcMessage(codeGetUsers)) {
|
||||
return null;
|
||||
}
|
||||
return await waitForResponse();
|
||||
}
|
||||
|
||||
async function deleteUser(userId) {
|
||||
// Send the request to delete the user
|
||||
if (!await window.networkAPI.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.networkAPI.closeUcSocket();
|
||||
fadeOut('main_menu');
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
let id = '';
|
||||
let departmentId = '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
const {id: userId, name, email, departmentId: userDepartmentId} = await window.databaseAPI.getUserInfo();
|
||||
|
||||
const emailInput = document.querySelector('input[name="email"]');
|
||||
const usernameInput = document.querySelector('input[name="username"]');
|
||||
const passwordInput = document.querySelector('input[name="password"]');
|
||||
|
||||
// Pre-fill form fields with existing data
|
||||
id = userId
|
||||
departmentId = userDepartmentId;
|
||||
usernameInput.value = name;
|
||||
emailInput.value = email;
|
||||
passwordInput.value = '';
|
||||
|
||||
const backButton = document.getElementById('back');
|
||||
const submitButton = document.getElementById('submit');
|
||||
|
||||
// Get operation codes from the backend
|
||||
const operationCodes = await window.networkAPI.getOperationsCodes();
|
||||
if (!operationCodes) {
|
||||
await window.uiAPI.showAlert('Internal error of the application.');
|
||||
return;
|
||||
}
|
||||
|
||||
const codeModifyUser = operationCodes.MODIFY_USER;
|
||||
const codeOk = operationCodes.OK;
|
||||
|
||||
// Handle the back button click
|
||||
backButton.addEventListener('click', async function () {
|
||||
console.log('Back button clicked!');
|
||||
fadeOut('main_menu');
|
||||
});
|
||||
|
||||
// Handle the submit button click
|
||||
submitButton.addEventListener('click', async function (e) {
|
||||
e.preventDefault();
|
||||
console.log('Submit button clicked!');
|
||||
|
||||
// Fetch form values
|
||||
const email = emailInput.value;
|
||||
const name = usernameInput.value;
|
||||
const password = passwordInput.value;
|
||||
|
||||
const app_type = await window.databaseAPI.getAppType();
|
||||
|
||||
// Prepare the data to be sent via the UC socket
|
||||
const messageData = {
|
||||
id: id,
|
||||
name: name,
|
||||
email: email,
|
||||
password: password,
|
||||
departmentId: departmentId,
|
||||
app_type: app_type
|
||||
};
|
||||
|
||||
// Open UC socket
|
||||
if (!await window.networkAPI.openUcSocket()) {
|
||||
await window.uiAPI.showAlert('Failed to open socket. Internal error of the application.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the message to the server using the UC socket
|
||||
if (!await window.networkAPI.sendUcMessage(codeModifyUser, messageData)) {
|
||||
await window.uiAPI.showAlert('Failed to send message.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for the response
|
||||
const response = await waitForResponse();
|
||||
if (response && response.operationCode === codeOk) {
|
||||
console.log('User update successful.');
|
||||
|
||||
// Save updated user info to the userConfig
|
||||
await window.databaseAPI.writeUserInfo({ id: id, email: email, name: name, departmentId: departmentId });
|
||||
|
||||
// Navigate back to the main menu
|
||||
fadeOut('main_menu');
|
||||
} else {
|
||||
console.log('Error updating user:', response?.metaInfo?.message || 'Unknown error');
|
||||
await window.electronAPI.showAlert(response?.metaInfo?.message || 'Unknown error occurred');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
const operationCodes = await window.networkAPI.getOperationsCodes();
|
||||
if (!operationCodes) {
|
||||
await window.uiAPI.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.networkAPI.openUcSocket()) {
|
||||
await window.uiAPI.showAlert('Internal error of the application. Unable to open socket.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await window.networkAPI.sendUcMessage(codeResetDatabase)) {
|
||||
await window.uiAPI.showAlert('Failed to send login request.');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await waitForResponse();
|
||||
if (response && response.operationCode !== codeOk) {
|
||||
await window.uiAPI.showAlert('Database reset failed.');
|
||||
return;
|
||||
}
|
||||
|
||||
await window.networkAPI.closeUcSocket();
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
fadeOut('login');
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
let codeResetPassword = '';
|
||||
let codeOk = '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
const backToLoginButton = document.getElementById('backToLogin');
|
||||
const resetPasswordButton = document.getElementById('resetPassword');
|
||||
|
||||
// Retrieve the operation codes via IPC
|
||||
const operationCodes = await window.networkAPI.getOperationsCodes();
|
||||
if (!operationCodes) {
|
||||
await window.uiAPI.showAlert('Internal error of the application.');
|
||||
return;
|
||||
}
|
||||
|
||||
codeResetPassword = operationCodes.RESET_PASSWORD;
|
||||
codeOk = operationCodes.OK;
|
||||
|
||||
// Back to login
|
||||
backToLoginButton.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
window.uiAPI.changeContent('login');
|
||||
});
|
||||
|
||||
// Reset password logic
|
||||
resetPasswordButton.addEventListener('click', async function (e) {
|
||||
e.preventDefault();
|
||||
if (!await window.networkAPI.openUcSocket()) {
|
||||
await window.uiAPI.showAlert('Internal error of the application.');
|
||||
return;
|
||||
}
|
||||
|
||||
const form = document.getElementById('resetPasswordForm');
|
||||
const formData = new FormData(form);
|
||||
const email = formData.get('email');
|
||||
const newPassword = formData.get('newPassword');
|
||||
|
||||
// Ensure the email and new password are provided
|
||||
if (!email || !newPassword) {
|
||||
await window.uiAPI.showAlert('Please provide both email and new password.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await attemptResetPassword(email, newPassword)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await window.networkAPI.closeUcSocket();
|
||||
await window.uiAPI.showAlert('Password reset successfully.');
|
||||
await window.uiAPI.changeContent('login');
|
||||
});
|
||||
});
|
||||
|
||||
async function attemptResetPassword(email, newPassword) {
|
||||
const app_type = await window.databaseAPI.getAppType();
|
||||
const messageData = { email, newPassword, app_type };
|
||||
|
||||
if (!await window.networkAPI.sendUcMessage(codeResetPassword, messageData)) return false;
|
||||
|
||||
const response = await waitForResponse();
|
||||
if (response && response.operationCode === codeOk) {
|
||||
return true;
|
||||
} else {
|
||||
await window.uiAPI.showAlert(response?.metaInfo?.message || 'Error resetting password.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
let pathToFile = '';
|
||||
let fetchUsersInterval = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
const selectFileButton = document.getElementById('selectFile');
|
||||
const submitButton = document.getElementById('submitButton');
|
||||
const backButton = document.getElementById('backButton');
|
||||
|
||||
selectFileButton.addEventListener('click', async function (event) {
|
||||
event.preventDefault();
|
||||
try {
|
||||
pathToFile = await window.uiAPI.selectFile();
|
||||
updateFileName();
|
||||
} catch (error) {
|
||||
console.error('Error opening file dialog:', error);
|
||||
}
|
||||
});
|
||||
submitButton.addEventListener('click', async function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Check if a file was chosen
|
||||
if (!pathToFile.trim()) {
|
||||
await window.uiAPI.showAlert('File not chosen!');
|
||||
return;
|
||||
}
|
||||
|
||||
const user_info = await window.databaseAPI.getUserInfo();
|
||||
if (!user_info) {
|
||||
return;
|
||||
}
|
||||
|
||||
const form = document.getElementById('userDestForm');
|
||||
const checkboxes = form.querySelectorAll('input[name="users"]');
|
||||
const selectedUserIps = Array.from(checkboxes)
|
||||
.filter(checkbox => checkbox.checked)
|
||||
.map(checkbox => checkbox.value); // Get IP of the selected users
|
||||
|
||||
if (!selectedUserIps.length) {
|
||||
await window.uiAPI.showAlert('No user selected!');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const selectedUserIp of selectedUserIps) {
|
||||
try {
|
||||
// Add task to send file to the queue
|
||||
const task = {
|
||||
ip: selectedUserIp, // Destination IP for the file
|
||||
path: pathToFile, // File path
|
||||
userName: user_info.name // Sender's username from userConfig
|
||||
};
|
||||
await window.databaseAPI.addTaskToSendFileQueue(task);
|
||||
await window.uiAPI.showAlert('File sent to the queue.');
|
||||
console.log(`Task added to send file to IP ${selectedUserIp}`);
|
||||
} catch (error) {
|
||||
console.error(`Error processing IP ${selectedUserIp}:`, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
backButton.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
fadeOut('main_menu');
|
||||
});
|
||||
|
||||
setInterval(fetchUsersAndCreateCheckboxes, 5000);
|
||||
});
|
||||
|
||||
// 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 usersInfo = await window.databaseAPI.getActiveUsers();
|
||||
const filteredUsers = usersInfo.filter(user => user.id !== '');
|
||||
if (!filteredUsers.length === 0) {
|
||||
await window.uiAPI.showAlert('No active users found.');
|
||||
clearInterval(fetchUsersInterval);
|
||||
fetchUsersInterval = null;
|
||||
await window.uiAPI.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.name}`;
|
||||
label.insertBefore(checkbox, label.firstChild);
|
||||
|
||||
usersDiv.appendChild(label);
|
||||
});
|
||||
|
||||
toggleScroll('choose_user_form_content');
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 30 KiB |
@@ -0,0 +1,79 @@
|
||||
body, html {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: "Pridi", sans-serif;
|
||||
background-color: #4A628A;
|
||||
}
|
||||
|
||||
.container {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 48px;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.announcement-body {
|
||||
max-height: 300px; /* Limit the height to make it scrollable if content overflows */
|
||||
width: 80%;
|
||||
overflow-y: auto; /* Enable vertical scrolling */
|
||||
padding: 20px;
|
||||
color: white;
|
||||
background-color: rgba(0, 0, 0, 0.2); /* Slight background to make text stand out */
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Customize scrollbar appearance */
|
||||
.announcement-body::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.announcement-body::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.announcement-body::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.announcement-body::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgb(158, 158, 158);
|
||||
}
|
||||
|
||||
.announcement-footer {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#closeButton {
|
||||
padding: 10px 20px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 16px;
|
||||
color: #4A628A;
|
||||
background-color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
#closeButton:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
body, html {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||
background-color: #4A628A;
|
||||
}
|
||||
|
||||
h4, h2, h1{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.container{
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title{
|
||||
font-size: 48px;
|
||||
font-weight: 500;
|
||||
letter-spacing: -1px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
img{
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
p{
|
||||
color: white;
|
||||
}
|
||||
|
||||
.loading-section{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
margin-bottom: 8px;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border: 3px solid #f3f3f3;
|
||||
border-top: 3px solid #2196F3;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
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: 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 {
|
||||
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 */
|
||||
.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"] {
|
||||
width: 100%;
|
||||
background-color: #4A628A; /* Blue background */
|
||||
color: white; /* White text color */
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
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;
|
||||
}
|
||||
|
||||
.title{
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
letter-spacing: -1px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.user{
|
||||
display: inline;
|
||||
color: white;
|
||||
}
|
||||
.navbar{
|
||||
padding-top: 8px;
|
||||
padding-right: 32px;
|
||||
padding-left: 32px;
|
||||
height: 10%;
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.welcome{
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
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;
|
||||
}
|
||||
|
||||
.left_block{
|
||||
background: linear-gradient(to bottom, #131A24, #4A628A);
|
||||
width: 25%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.notification-title{
|
||||
padding-top: 16px;
|
||||
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:last-child:hover {
|
||||
background-color: #fd4c29;
|
||||
}
|
||||
|
||||
.alert, .logout-button{
|
||||
cursor: pointer;
|
||||
border-radius: 15px;
|
||||
width: 80%;
|
||||
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;
|
||||
}
|
||||
|
||||
.notification:hover{
|
||||
background: #d9efff;
|
||||
}
|
||||
|
||||
.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;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
|
||||
.right_block_content{
|
||||
height: 90%;
|
||||
}
|
||||
|
||||
.right_block{
|
||||
width: 75%;
|
||||
display:flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right_block_content {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr); /* 3 equal-width columns */
|
||||
gap: 8px; /* Spacing between buttons */
|
||||
padding: 32px; /* Optional padding */
|
||||
}
|
||||
|
||||
button[name="menu_button"] {
|
||||
padding: 10px 20px;
|
||||
font-size: 16px;
|
||||
background-color: rgba(186,229,244,0.4); /* Button background */
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
button[name="menu_button"]:hover {
|
||||
background-color: #3B5173; /* Darker background on hover */
|
||||
}
|
||||
|
||||
.notifications::-webkit-scrollbar {
|
||||
width: 4px; /* Width of the scrollbar */
|
||||
}
|
||||
|
||||
.notifications::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1); /* Track background color */
|
||||
border-radius: 10px; /* Rounded corners for the track */
|
||||
}
|
||||
|
||||
.notifications::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(74, 98, 138, 0.8); /* Scrollbar handle color */
|
||||
border-radius: 10px; /* Rounded corners for the scrollbar handle */
|
||||
}
|
||||
|
||||
.notifications::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(74, 98, 138, 1); /* Darker color on hover */
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre, hr
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
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: 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);
|
||||
}
|
||||
|
||||
.profile-form{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
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 {
|
||||
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 */
|
||||
.profile-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;
|
||||
}
|
||||
|
||||
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"] {
|
||||
width: 100%;
|
||||
background-color: #4A628A; /* Blue background */
|
||||
color: white; /* White text color */
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
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-between;
|
||||
height: 90%;
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
.header{
|
||||
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 {
|
||||
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 */
|
||||
.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"] {
|
||||
width: 100%;
|
||||
background-color: #4A628A; /* Blue background */
|
||||
color: white; /* White text color */
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
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;
|
||||
height: 90%;
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
.header{
|
||||
text-align: center;
|
||||
padding-bottom: 16px;
|
||||
padding-top: 32px;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.form{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
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 {
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 180px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
h5{
|
||||
font-size: 20px;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Styling for buttons */
|
||||
.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;
|
||||
}
|
||||
|
||||
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"] {
|
||||
width: 100%;
|
||||
background-color: #4A628A; /* Blue background */
|
||||
color: white; /* White text color */
|
||||
}
|
||||
|
||||
button[name="submit"]:hover {
|
||||
background-color: #3B5173; /* Darker background on hover */
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/* General Styles */
|
||||
body, html {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-family: "Pridi", sans-serif; /* Use sans-serif fallback */
|
||||
background-color: #4A628A; /* Background color */
|
||||
}
|
||||
|
||||
h4, h2, h1 {
|
||||
margin: 0; /* Remove default margin */
|
||||
}
|
||||
|
||||
/* Page Layout */
|
||||
.page {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.container{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
/* Active Step Styles */
|
||||
.step {
|
||||
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 {
|
||||
display: flex;
|
||||
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);
|
||||
}
|
||||
|
||||
/* Signup Form Styles */
|
||||
.signup-form {
|
||||
display: flex;
|
||||
height: 80%;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 80%; /* Width of the form */
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.signup-form-title {
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
margin-bottom: 16px; /* Space below title */
|
||||
}
|
||||
|
||||
/* Input Styles */
|
||||
.signup-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 */
|
||||
.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 {
|
||||
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between; /* Align buttons next to each other */
|
||||
align-items: center;
|
||||
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"] {
|
||||
background-color: transparent; /* No background for 'back' button */
|
||||
color: #4A628A; /* Set text color */
|
||||
text-decoration: underline; /* Underline text */
|
||||
}
|
||||
|
||||
button[name="continue"] {
|
||||
width: 100%;
|
||||
background-color: #4A628A; /* Blue background */
|
||||
color: white; /* White text color */
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
.fade-in {
|
||||
animation: fadeInAnimation 0.5s ease-in forwards;
|
||||
}
|
||||
|
||||
.fade-out {
|
||||
animation: fadeOutAnimation 0.5s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeInAnimation {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeOutAnimation {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
<!-- Reuse the same CSS files as UC Not Found -->
|
||||
<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>Welcome</title>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 class="title">Team Vault</h1>
|
||||
</div>
|
||||
<img src="../assets/refresh-data.webp" alt="Backup Fetcher" class="backup-img">
|
||||
<div class="loading-section">
|
||||
<div class="spinner"></div>
|
||||
<p>Backup is now searched, please wait...</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,44 @@
|
||||
<!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/login.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>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="page">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 class="title">TeamVault</h1>
|
||||
<h4 class="subheading">Do we know each other?</h4>
|
||||
</div>
|
||||
<form class="login-form" id="loginForm">
|
||||
<div class="login-form-title">
|
||||
<h2 class="login-title">Login</h2>
|
||||
</div>
|
||||
<div class="login-form-content">
|
||||
<input name="email" placeholder="Email" type="email">
|
||||
<input name="password" placeholder="Password" type="password">
|
||||
</div>
|
||||
<div class="login-form-footer">
|
||||
<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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="../js/login.js"></script>
|
||||
<script src="../js/helpers.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,62 @@
|
||||
<!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/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">
|
||||
|
||||
<script src="../js/main_menu.js"></script>
|
||||
<script src="../js/helpers.js"></script>
|
||||
|
||||
<title>Main Page</title>
|
||||
|
||||
</head>
|
||||
<body onload="initialSetup(); fadeIn()">
|
||||
<div class="page">
|
||||
<div class="container">
|
||||
<div class="left_block">
|
||||
<div class="notification-title">
|
||||
<h1 class="notif-title">Notifications</h1>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<div class="right_block">
|
||||
<div class="navbar">
|
||||
<div class="welcome">
|
||||
<img alt="" src="../assets/user_1144760.png">
|
||||
<h3 class="user">Welcome Back, </h3>
|
||||
<h3 class="user" id="username-field"></h3>
|
||||
<h3 class="user">!</h3>
|
||||
|
||||
</div>
|
||||
<h1 class="title">TeamVault</h1>
|
||||
</div>
|
||||
|
||||
<div class="right_block_content">
|
||||
<button id="change-info" name="menu_button">Change your info</button>
|
||||
<button id="share-file" name="menu_button">Share a file</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="share-dir" name="menu_button">Set share directory</button>
|
||||
<button id="restore-backup" name="menu_button">Restore backup</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,40 @@
|
||||
<!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/profile.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/helpers.js"></script>
|
||||
|
||||
<title>Profile</title>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="page">
|
||||
<div class="container">
|
||||
<form class="profile-form" id="profileForm">
|
||||
<div class="header">
|
||||
<h1 class="title">Profile</h1>
|
||||
<h3 class="subheading">Who are you?</h3>
|
||||
</div>
|
||||
<div class="profile-form-content">
|
||||
<input name="email" placeholder="Email" type="email">
|
||||
<input name="username" placeholder="Username" type="text">
|
||||
<input name="password" placeholder="Password" type="password">
|
||||
</div>
|
||||
<div class="profile-form-footer">
|
||||
<button id="back" name="underline" type="button">Back</button>
|
||||
<button id="submit" name="submit" type="submit">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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">
|
||||
<!-- Reuse the same CSS files as UC Not Found -->
|
||||
<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>Welcome</title>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 class="title">Welcome to 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>The CEO initialized a complete reset, please wait...</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<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="../css/reset_password.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>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="page">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 class="title">TeamVault</h1>
|
||||
<h3 class="subheading">Forgot your password? No worries, it happens to the best of us!</h3>
|
||||
</div>
|
||||
<form class="login-form" id="resetPasswordForm">
|
||||
<div class="login-form-title">
|
||||
<h2 class="login-title">Reset Password</h2>
|
||||
</div>
|
||||
<div class="login-form-content">
|
||||
<input name="email" placeholder="Email" type="email" required>
|
||||
<input name="newPassword" placeholder="New Password" type="password" required>
|
||||
</div>
|
||||
<div class="login-form-footer">
|
||||
<button id="backToLogin" name="underline" type="button">Back to Login</button>
|
||||
<button id="resetPassword" name="submit" type="submit">Reset Password</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script src="../js/reset_password.js"></script>
|
||||
<script src="../js/helpers.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,45 @@
|
||||
<!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/share_file.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/helpers.js"></script>
|
||||
|
||||
<title>Share File</title>
|
||||
</head>
|
||||
<body onload="fetchUsersAndCreateCheckboxes(); updateFileName(); fadeIn()">
|
||||
<div class="page">
|
||||
<div class="container">
|
||||
<div class="form">
|
||||
<div class="header">
|
||||
<h1 class="title">Share a File</h1>
|
||||
<h3 class="subheading">First select the file you want to share.</h3>
|
||||
<button class="filename-title" id="selectFile" name="submit" type="button">Select</button>
|
||||
<form class="users_block" id="userDestForm">
|
||||
<h5>Users</h5>
|
||||
<h3 class="subheading">Select the users you want to share the file with.</h3>
|
||||
<div id="choose_user_form_content" class="choose_user_form_content">
|
||||
<!-- Insert the users from the database -->
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="footer">
|
||||
<button id="backButton" name="underline" type="button">Back</button>
|
||||
<button id="submitButton" name="submit" type="submit">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<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="../css/transition.css" rel="stylesheet">
|
||||
<link href="../css/sign_up.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>Signup Process</title>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="page">
|
||||
<div class="container">
|
||||
<!-- Step 1: Profile Information -->
|
||||
<div class="step active" id="step1">
|
||||
<div class="header">
|
||||
<h1 class="title">TeamVault</h1>
|
||||
<h3 class="subheading">Let us meet each other</h3>
|
||||
</div>
|
||||
<form class="signup-form" id="signupForm">
|
||||
<div class="signup-form-title">
|
||||
<h2>Sign Up</h2>
|
||||
</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 class="title">TeamVault</h1>
|
||||
<h3 class="subheading">Tell me more about your work</h3>
|
||||
</div>
|
||||
<form class="signup-form" id="departmentForm">
|
||||
<div class="signup-form-title">
|
||||
<h2>Choose your department</h2>
|
||||
</div>
|
||||
<div class="signup-form-content-radio" 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>
|
||||
</div>
|
||||
<script src="../js/sign_up.js"></script>
|
||||
<script src="../js/helpers.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<link href="../assets/hard-disk.ico" rel="icon" type="image/x-icon">
|
||||
<link href="../css/normalizer.css" rel="stylesheet">
|
||||
<link href="../css/loading.css" rel="stylesheet">
|
||||
<link href="../css/transition.css" rel="stylesheet">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Pridi:wght@200;300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<script src="../js/helpers.js"></script>
|
||||
|
||||
<title>UC Not Found</title>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 class="title">TeamVault</h1>
|
||||
</div>
|
||||
<img src="../assets/cloud-computing.webp" alt="Connecting to server" class="server-img">
|
||||
<div class="loading-section">
|
||||
<div class="spinner"></div>
|
||||
<p>Please wait while we try to connect to the server.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,29 @@
|
||||
<!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">
|
||||
<!-- Reuse the same CSS files as UC Not Found -->
|
||||
<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>Welcome</title>
|
||||
</head>
|
||||
<body onload="fadeIn()">
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1 class="title">Welcome to TeamVault</h1>
|
||||
</div>
|
||||
<div class="loading-section">
|
||||
<div class="spinner"></div>
|
||||
<p>Initializing the application, please wait...</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,26 @@
|
||||
// Load announcement content from the application info when the page loads
|
||||
async function loadAnnouncement() {
|
||||
try {
|
||||
const announcementText = await window.electronAPI.readAnnouncement();
|
||||
const announcementContent = document.getElementById('announcement-content');
|
||||
if (announcementContent && announcementText) {
|
||||
announcementContent.innerHTML = formatTextForHtml(announcementText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load announcement:', error);
|
||||
document.getElementById('announcement-content').textContent = 'Failed to load announcement.';
|
||||
}
|
||||
}
|
||||
|
||||
function formatTextForHtml(text) {
|
||||
let formattedText = text.replace(/\n/g, '<br>');
|
||||
formattedText = formattedText.replace(/\t/g, ' ');
|
||||
formattedText = formattedText.replace(/ /g, ' ');
|
||||
|
||||
return formattedText;
|
||||
}
|
||||
|
||||
// Close the window when the close button is clicked
|
||||
function closeWindow() {
|
||||
window.electronAPI.closeAnnouncementWindow();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
function fadeIn() {
|
||||
document.querySelector('.container').classList.remove('fade-out');
|
||||
document.querySelector('.container').classList.add('fade-in');
|
||||
}
|
||||
|
||||
function fadeOut(destination) {
|
||||
const container = document.querySelector('.container');
|
||||
container.classList.remove('fade-in');
|
||||
container.classList.add('fade-out');
|
||||
|
||||
console.log(destination);
|
||||
setTimeout(() => {}, 5000);
|
||||
|
||||
container.addEventListener('animationend', async () => {
|
||||
try {
|
||||
await window.uiAPI.changeContent(destination);
|
||||
console.log('Navigated to', destination);
|
||||
} catch (error) {
|
||||
console.error('Error navigating:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForResponse() {
|
||||
return new Promise((resolve) => {
|
||||
const idResponseCheck = setInterval(async () => {
|
||||
if (await window.networkAPI.hasResponseArrived()) {
|
||||
clearInterval(idResponseCheck);
|
||||
resolve(await window.networkAPI.getLastUcResult()); // Resolve the response or null if not available
|
||||
}
|
||||
}, 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
let codeLogin = '';
|
||||
let codeFindByEmail = '';
|
||||
let codeFindKeyByUser = '';
|
||||
let codeOk = '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
const submitButton = document.getElementById('submit');
|
||||
const resetPasswordButton = document.getElementById('resetPassword');
|
||||
const signUpButton = document.getElementById('signup');
|
||||
|
||||
await window.databaseAPI.setLoginStatus(false);
|
||||
|
||||
// Retrieve the operation codes via IPC
|
||||
const operationCodes = await window.networkAPI.getOperationsCodes();
|
||||
if (!operationCodes) {
|
||||
await window.uiAPI.showAlert('Internal error of the application. Failed to retrieve operation codes.');
|
||||
return;
|
||||
}
|
||||
|
||||
codeLogin = operationCodes.LOGIN;
|
||||
codeFindByEmail = operationCodes.FIND_BY_EMAIL;
|
||||
codeFindKeyByUser = operationCodes.FIND_KEY_BY_USER_ID;
|
||||
codeOk = operationCodes.OK;
|
||||
|
||||
resetPasswordButton.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
window.uiAPI.changeContent('reset_password');
|
||||
});
|
||||
|
||||
signUpButton.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
window.uiAPI.changeContent('sign_up');
|
||||
});
|
||||
|
||||
// Submit button logic (handle login)
|
||||
submitButton.addEventListener('click', async function (e) {
|
||||
e.preventDefault();
|
||||
console.log('Submit button clicked');
|
||||
|
||||
const form = document.getElementById('loginForm');
|
||||
const formData = new FormData(form);
|
||||
|
||||
const email = formData.get('email');
|
||||
const password = formData.get('password');
|
||||
|
||||
// Open a TCP socket to the stored IP
|
||||
if (!await window.networkAPI.openUcSocket()) {
|
||||
await window.uiAPI.showAlert('Internal error of the application. Unable to open socket.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Attempt login
|
||||
if (!await attemptLogin(email, password)) {
|
||||
await window.networkAPI.closeUcSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch and store user info
|
||||
if (!await fetchAndStoreUserInfo(email)) {
|
||||
await window.networkAPI.closeUcSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch user info from local storage
|
||||
const userInfo = await window.databaseAPI.getUserInfo('user_info');
|
||||
if (!userInfo) {
|
||||
await window.networkAPI.closeUcSocket();
|
||||
await window.uiAPI.showAlert('Failed to fetch user info.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch and store encryption key
|
||||
if (!await fetchAndStoreEncryptionKey(userInfo.id)) {
|
||||
await window.networkAPI.closeUcSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
// Close the socket and navigate to main menu after success
|
||||
await window.networkAPI.closeUcSocket();
|
||||
await window.workersAPI.startWorkers();
|
||||
await window.databaseAPI.setLoginStatus(true);
|
||||
await window.uiAPI.changeContent('main_menu');
|
||||
});
|
||||
});
|
||||
|
||||
async function attemptLogin(email, password) {
|
||||
const app_type = await window.databaseAPI.getAppType();
|
||||
const messageData = {email, password, app_type};
|
||||
|
||||
// Send the login message to the server
|
||||
if (!await window.networkAPI.sendUcMessage(codeLogin, messageData)) {
|
||||
await window.uiAPI.showAlert('Failed to send login request.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait for the response using waitForResponse
|
||||
const response = await waitForResponse();
|
||||
|
||||
if (!response) {
|
||||
await window.uiAPI.showAlert('No response from server.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (response.operationCode !== codeOk) {
|
||||
await window.uiAPI.showAlert(response.metaInfo.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function fetchAndStoreUserInfo(userEmail) {
|
||||
if (!await window.networkAPI.sendUcMessage(codeFindByEmail, {email: userEmail})) {
|
||||
await window.uiAPI.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) {
|
||||
const userInfo = {};
|
||||
userInfo.id = response.metaInfo.id;
|
||||
userInfo.email = response.metaInfo.email;
|
||||
userInfo.departmentId = response.metaInfo.departmentId;
|
||||
userInfo.name = response.metaInfo.name || 'User';
|
||||
|
||||
await window.databaseAPI.writeUserInfo(userInfo);
|
||||
return true;
|
||||
}
|
||||
|
||||
await window.uiAPI.showAlert('Failed to fetch user info from server.');
|
||||
return false;
|
||||
}
|
||||
|
||||
async function fetchAndStoreEncryptionKey(userId) {
|
||||
if (!await window.networkAPI.sendUcMessage(codeFindKeyByUser, {userId})) {
|
||||
await window.uiAPI.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.databaseAPI.writeEncryptionKey(encryptionKey);
|
||||
return true;
|
||||
}
|
||||
|
||||
await window.uiAPI.showAlert('Failed to fetch encryption key from server.');
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
let backupDirId = '';
|
||||
let shareDirId = '';
|
||||
let departmentDirId = '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
setInterval(loadReceivedFiles, 3000); // Call loadReceivedFiles every 3000 ms (3 seconds)
|
||||
|
||||
const backupButton = document.getElementById('backup-dir');
|
||||
const shareButton = document.getElementById('share-dir');
|
||||
const departmentButton = document.getElementById('department-dir');
|
||||
|
||||
const changeInfoButton = document.getElementById('change-info');
|
||||
const shareFileButton = document.getElementById('share-file');
|
||||
const logoutButton = document.getElementById('logout');
|
||||
const restoreBackupButton = document.getElementById('restore-backup');
|
||||
|
||||
backupButton.addEventListener('click', async function () {
|
||||
await setPath('backupDirectory');
|
||||
});
|
||||
|
||||
shareButton.addEventListener('click', async function () {
|
||||
await setPath('shareDirectory');
|
||||
});
|
||||
|
||||
departmentButton.addEventListener('click', async function () {
|
||||
await setPath('departmentDirectory');
|
||||
});
|
||||
|
||||
changeInfoButton.addEventListener('click', function () {
|
||||
fadeOut('profile');
|
||||
});
|
||||
|
||||
shareFileButton.addEventListener('click', function () {
|
||||
fadeOut('share_file');
|
||||
});
|
||||
|
||||
logoutButton.addEventListener('click', async function () {
|
||||
fadeOut('login');
|
||||
});
|
||||
|
||||
// Add event listener for the restore backup button
|
||||
restoreBackupButton.addEventListener('click', async function () {
|
||||
await restoreBackup();
|
||||
});
|
||||
});
|
||||
|
||||
async function initialSetup(){
|
||||
await checkAndSetAllDirectories();
|
||||
await loadReceivedFiles();
|
||||
await fetchUserInfo();
|
||||
}
|
||||
|
||||
async function restoreBackup() {
|
||||
const backupDirectory = await window.databaseAPI.isBackupSet();
|
||||
|
||||
if (backupDirectory) {
|
||||
await window.uiAPI.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.uiAPI.selectDirectory();
|
||||
if (!destinationPath) {
|
||||
return; // User canceled the directory selection
|
||||
}
|
||||
|
||||
// Call the IPC method to initiate the backup retrieval process
|
||||
window.workersAPI.startBackupRetrieval(destinationPath);
|
||||
|
||||
// Switch the content to the 'backup_retrieve' page
|
||||
fadeOut('backup_retrieve');
|
||||
}
|
||||
|
||||
async function checkAndSetAllDirectories() {
|
||||
const directorySchemes = await window.databaseAPI.getLocalResources();
|
||||
|
||||
backupDirId = directorySchemes.backup.id;
|
||||
shareDirId = directorySchemes.shared.id;
|
||||
departmentDirId = directorySchemes.department.id;
|
||||
|
||||
await attachNotificationButton(backupDirId, 'Set your backup directory!', 'backup_alert', 'alert');
|
||||
await attachNotificationButton(shareDirId, 'Set your share directory!', 'share_alert', 'alert');
|
||||
await attachNotificationButton(departmentDirId, 'Set your department directory!', 'department_alert', 'alert');
|
||||
}
|
||||
|
||||
async function checkPathExistence(id) {
|
||||
const dirInfo = await window.databaseAPI.getDirectoryInfo(id);
|
||||
return dirInfo.path !== ''
|
||||
}
|
||||
|
||||
async function setPath(id){
|
||||
const path = await window.uiAPI.selectDirectory();
|
||||
if (path === undefined) return false;
|
||||
|
||||
return await window.databaseAPI.writeDirectoryPath(id, path);
|
||||
}
|
||||
|
||||
async function attachNotificationButton(entryId, buttonText, buttonId, buttonName) {
|
||||
const path = await checkPathExistence(entryId);
|
||||
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 () {
|
||||
if(await setPath(entryId)) button.remove();
|
||||
});
|
||||
notificationsDiv.appendChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchUserInfo() {
|
||||
const usernameField = document.getElementById('username-field');
|
||||
|
||||
// Read the user credentials from the userConfig
|
||||
let userInfo = await window.databaseAPI.getUserInfo();
|
||||
if (userInfo && userInfo.name) {
|
||||
usernameField.textContent = userInfo.name;
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the greeting with the fetched user's name
|
||||
if (usernameField) {
|
||||
usernameField.textContent = userInfo.name;
|
||||
} else {
|
||||
console.error("Username field is not available in the DOM.");
|
||||
}
|
||||
}
|
||||
|
||||
async function loadReceivedFiles() {
|
||||
// Read the shareDirectory from applicationInfo
|
||||
const shareDirData = await window.databaseAPI.getDirectoryInfo(shareDirId);
|
||||
|
||||
console.log('Loading received files:', shareDirData);
|
||||
|
||||
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(shareDirData.structure).forEach(userName => {
|
||||
const userFiles = shareDirData.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 handleFileReceivedButtonPressed(filePath, button) {
|
||||
console.log('Notification button clicked!');
|
||||
|
||||
// Open the file in the file explorer
|
||||
await window.uiAPI.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();
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
let id = '';
|
||||
let departmentId = '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
const {id: userId, name, email, departmentId: userDepartmentId} = await window.databaseAPI.getUserInfo();
|
||||
|
||||
const emailInput = document.querySelector('input[name="email"]');
|
||||
const usernameInput = document.querySelector('input[name="username"]');
|
||||
const passwordInput = document.querySelector('input[name="password"]');
|
||||
|
||||
// Pre-fill form fields with existing data
|
||||
id = userId
|
||||
departmentId = userDepartmentId;
|
||||
usernameInput.value = name;
|
||||
emailInput.value = email;
|
||||
passwordInput.value = '';
|
||||
|
||||
const backButton = document.getElementById('back');
|
||||
const submitButton = document.getElementById('submit');
|
||||
|
||||
// Get operation codes from the backend
|
||||
const operationCodes = await window.networkAPI.getOperationsCodes();
|
||||
if (!operationCodes) {
|
||||
await window.uiAPI.showAlert('Internal error of the application.');
|
||||
return;
|
||||
}
|
||||
|
||||
const codeModifyUser = operationCodes.MODIFY_USER;
|
||||
const codeOk = operationCodes.OK;
|
||||
|
||||
// Handle the back button click
|
||||
backButton.addEventListener('click', async function () {
|
||||
console.log('Back button clicked!');
|
||||
fadeOut('main_menu');
|
||||
});
|
||||
|
||||
// Handle the submit button click
|
||||
submitButton.addEventListener('click', async function (e) {
|
||||
e.preventDefault();
|
||||
console.log('Submit button clicked!');
|
||||
|
||||
// Fetch form values
|
||||
const email = emailInput.value;
|
||||
const name = usernameInput.value;
|
||||
const password = passwordInput.value;
|
||||
|
||||
const app_type = await window.databaseAPI.getAppType();
|
||||
|
||||
// Prepare the data to be sent via the UC socket
|
||||
const messageData = {
|
||||
id: id,
|
||||
name: name,
|
||||
email: email,
|
||||
password: password,
|
||||
departmentId: departmentId,
|
||||
app_type: app_type
|
||||
};
|
||||
|
||||
// Open UC socket
|
||||
if (!await window.networkAPI.openUcSocket()) {
|
||||
await window.uiAPI.showAlert('Failed to open socket. Internal error of the application.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the message to the server using the UC socket
|
||||
if (!await window.networkAPI.sendUcMessage(codeModifyUser, messageData)) {
|
||||
await window.uiAPI.showAlert('Failed to send message.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for the response
|
||||
const response = await waitForResponse();
|
||||
if (response && response.operationCode === codeOk) {
|
||||
console.log('User update successful.');
|
||||
|
||||
// Save updated user info to the userConfig
|
||||
await window.databaseAPI.writeUserInfo({ id: id, email: email, name: name, departmentId: departmentId });
|
||||
|
||||
// Navigate back to the main menu
|
||||
fadeOut('main_menu');
|
||||
} else {
|
||||
console.log('Error updating user:', response?.metaInfo?.message || 'Unknown error');
|
||||
await window.electronAPI.showAlert(response?.metaInfo?.message || 'Unknown error occurred');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
await new Promise(resolve => setTimeout(resolve, 7000));
|
||||
await window.databaseAPI.resetInternalDatabase();
|
||||
await window.workersAPI.stopWorkers();
|
||||
await window.uiAPI.changeContent('welcome');
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
let codeResetPassword = '';
|
||||
let codeOk = '';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
const backToLoginButton = document.getElementById('backToLogin');
|
||||
const resetPasswordButton = document.getElementById('resetPassword');
|
||||
|
||||
// Retrieve the operation codes via IPC
|
||||
const operationCodes = await window.networkAPI.getOperationsCodes();
|
||||
if (!operationCodes) {
|
||||
await window.uiAPI.showAlert('Internal error of the application.');
|
||||
return;
|
||||
}
|
||||
|
||||
codeResetPassword = operationCodes.RESET_PASSWORD;
|
||||
codeOk = operationCodes.OK;
|
||||
|
||||
// Back to login
|
||||
backToLoginButton.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
window.uiAPI.changeContent('login');
|
||||
});
|
||||
|
||||
// Reset password logic
|
||||
resetPasswordButton.addEventListener('click', async function (e) {
|
||||
e.preventDefault();
|
||||
if (!await window.networkAPI.openUcSocket()) {
|
||||
await window.uiAPI.showAlert('Internal error of the application.');
|
||||
return;
|
||||
}
|
||||
|
||||
const form = document.getElementById('resetPasswordForm');
|
||||
const formData = new FormData(form);
|
||||
const email = formData.get('email');
|
||||
const newPassword = formData.get('newPassword');
|
||||
|
||||
// Ensure the email and new password are provided
|
||||
if (!email || !newPassword) {
|
||||
await window.uiAPI.showAlert('Please provide both email and new password.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await attemptResetPassword(email, newPassword)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await window.networkAPI.closeUcSocket();
|
||||
await window.uiAPI.showAlert('Password reset successfully.');
|
||||
await window.uiAPI.changeContent('login');
|
||||
});
|
||||
});
|
||||
|
||||
async function attemptResetPassword(email, newPassword) {
|
||||
const app_type = await window.databaseAPI.getAppType();
|
||||
const messageData = { email, newPassword, app_type };
|
||||
|
||||
if (!await window.networkAPI.sendUcMessage(codeResetPassword, messageData)) return false;
|
||||
|
||||
const response = await waitForResponse();
|
||||
if (response && response.operationCode === codeOk) {
|
||||
return true;
|
||||
} else {
|
||||
await window.uiAPI.showAlert(response?.metaInfo?.message || 'Error resetting password.');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
let pathToFile = '';
|
||||
let fetchUsersInterval = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function () {
|
||||
const selectFileButton = document.getElementById('selectFile');
|
||||
const submitButton = document.getElementById('submitButton');
|
||||
const backButton = document.getElementById('backButton');
|
||||
|
||||
selectFileButton.addEventListener('click', async function (event) {
|
||||
event.preventDefault();
|
||||
try {
|
||||
pathToFile = await window.uiAPI.selectFile();
|
||||
updateFileName();
|
||||
} catch (error) {
|
||||
console.error('Error opening file dialog:', error);
|
||||
}
|
||||
});
|
||||
submitButton.addEventListener('click', async function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Check if a file was chosen
|
||||
if (!pathToFile.trim()) {
|
||||
await window.uiAPI.showAlert('File not chosen!');
|
||||
return;
|
||||
}
|
||||
|
||||
const user_info = await window.databaseAPI.getUserInfo();
|
||||
if (!user_info) {
|
||||
return;
|
||||
}
|
||||
|
||||
const form = document.getElementById('userDestForm');
|
||||
const checkboxes = form.querySelectorAll('input[name="users"]');
|
||||
const selectedUserIps = Array.from(checkboxes)
|
||||
.filter(checkbox => checkbox.checked)
|
||||
.map(checkbox => checkbox.value); // Get IP of the selected users
|
||||
|
||||
if (!selectedUserIps.length) {
|
||||
await window.uiAPI.showAlert('No user selected!');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const selectedUserIp of selectedUserIps) {
|
||||
try {
|
||||
// Add task to send file to the queue
|
||||
const task = {
|
||||
ip: selectedUserIp, // Destination IP for the file
|
||||
path: pathToFile, // File path
|
||||
userName: user_info.name // Sender's username from userConfig
|
||||
};
|
||||
await window.databaseAPI.addTaskToSendFileQueue(task);
|
||||
await window.uiAPI.showAlert('File sent to the queue.');
|
||||
console.log(`Task added to send file to IP ${selectedUserIp}`);
|
||||
} catch (error) {
|
||||
console.error(`Error processing IP ${selectedUserIp}:`, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
backButton.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
fadeOut('main_menu');
|
||||
});
|
||||
|
||||
setInterval(fetchUsersAndCreateCheckboxes, 5000);
|
||||
});
|
||||
|
||||
// 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 usersInfo = await window.databaseAPI.getActiveUsers();
|
||||
const filteredUsers = usersInfo.filter(user => user.id !== '');
|
||||
if (!filteredUsers.length === 0) {
|
||||
await window.uiAPI.showAlert('No active users found.');
|
||||
clearInterval(fetchUsersInterval);
|
||||
fetchUsersInterval = null;
|
||||
await window.uiAPI.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.name}`;
|
||||
label.insertBefore(checkbox, label.firstChild);
|
||||
|
||||
usersDiv.appendChild(label);
|
||||
});
|
||||
|
||||
toggleScroll('choose_user_form_content');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
let codeOk = '';
|
||||
let codeGetDepartments = '';
|
||||
let codeSignUp = '';
|
||||
let userData = {
|
||||
email: null,
|
||||
name: null,
|
||||
password: null,
|
||||
app_type: null,
|
||||
departmentId: null
|
||||
};
|
||||
|
||||
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');
|
||||
|
||||
if (!await window.networkAPI.openUcSocket()) {
|
||||
alert('Internal error of the application.');
|
||||
return;
|
||||
}
|
||||
|
||||
const operationCodes = await window.networkAPI.getOperationsCodes();
|
||||
if (!operationCodes) {
|
||||
await window.electronAPI.showAlert('Internal error of the application.');
|
||||
return;
|
||||
}
|
||||
|
||||
codeGetDepartments = operationCodes.GET_DEPARTMENTS;
|
||||
codeSignUp = operationCodes.SIGN_UP;
|
||||
codeOk = operationCodes.OK;
|
||||
|
||||
let departments = await getDepartments();
|
||||
if (departments === null) {
|
||||
await window.uiAPI.showAlert('Failed to fetch departments.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 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.databaseAPI.getAppType();
|
||||
|
||||
// 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.uiAPI.showAlert('Please select a department.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await attemptSignUp()) {
|
||||
switchStep(step2, step1);
|
||||
return;
|
||||
}
|
||||
|
||||
await window.uiAPI.showAlert('Signup successful!');
|
||||
await window.uiAPI.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.networkAPI.closeUcSocket();
|
||||
await window.uiAPI.changeContent('login');
|
||||
});
|
||||
});
|
||||
|
||||
async function getDepartments() {
|
||||
if (!await window.networkAPI.sendUcMessage('GET_DEPARTMENTS')) return null;
|
||||
|
||||
const response = await waitForResponse();
|
||||
console.log(response);
|
||||
if (response && response.operationCode === codeOk) {
|
||||
return response.metaInfo.departments;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function attemptSignUp() {
|
||||
if (!await window.networkAPI.sendUcMessage(codeSignUp, userData)) return false;
|
||||
|
||||
const response = await waitForResponse();
|
||||
if (response && response.operationCode === codeOk) {
|
||||
return true;
|
||||
}
|
||||
await window.uiAPI.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)
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
import jsonfile from 'jsonfile'
|
||||
import { promises as fs } from 'fs'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { DatabaseScheme } from './schemes/database_scheme'
|
||||
import { FileItemTask } from './schemes/local_resources_scheme'
|
||||
import dotenv from 'dotenv'
|
||||
import path from 'path'
|
||||
|
||||
dotenv.config({ path: path.join(__dirname, '..', '..', '..', '.env') })
|
||||
const IS_CLIENT = process.env.IS_CLIENT === 'true'
|
||||
|
||||
const defaultData: DatabaseScheme = {
|
||||
app_config: {
|
||||
app_type: IS_CLIENT ? 'client' : 'ceo',
|
||||
user_info: {
|
||||
id: '',
|
||||
email: '',
|
||||
departmentId: '',
|
||||
name: '',
|
||||
},
|
||||
encryption_key: {
|
||||
key: '',
|
||||
iv: '',
|
||||
},
|
||||
reset_application_preferences: false,
|
||||
logged_in: false,
|
||||
server_found: false,
|
||||
},
|
||||
network: {
|
||||
usersInLan: [],
|
||||
serverIp: '',
|
||||
announcement: '',
|
||||
},
|
||||
local_resources: {
|
||||
directory_schemes: {
|
||||
backup: { id: uuidv4(), path: '', structure: {}, totalSize: 0 },
|
||||
department: { id: uuidv4(), path: '', structure: {}, totalSize: 0 },
|
||||
shared: { id: uuidv4(), path: '', structure: {}, totalSize: 0 },
|
||||
},
|
||||
task_schemes: {
|
||||
send_file_queue: [],
|
||||
receive_file_queue: [],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export class Database {
|
||||
private readonly filePath: string
|
||||
|
||||
constructor(filePath: string) {
|
||||
this.filePath = filePath
|
||||
|
||||
// Ensure the file exists and is not empty
|
||||
this.ensureFileExists().then(() => {
|
||||
console.log('[Database] File exists and is properly initialized.')
|
||||
})
|
||||
}
|
||||
|
||||
// Ensure the JSON file exists, or create it with default content
|
||||
private async ensureFileExists(): Promise<void> {
|
||||
try {
|
||||
await fs.access(this.filePath) // Check if file exists
|
||||
|
||||
// Check if file is empty
|
||||
const fileContent = await fs.readFile(this.filePath, 'utf-8')
|
||||
if (!fileContent.trim()) {
|
||||
console.log(`[Database] ${this.filePath} is empty. Writing default data...`)
|
||||
await jsonfile.writeFile(this.filePath, defaultData, { spaces: 2 })
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`[Database] ${this.filePath} not found. Creating with default data...`)
|
||||
|
||||
// Create the file with default content
|
||||
await jsonfile.writeFile(this.filePath, defaultData, { spaces: 2 })
|
||||
console.log(`[Database] ${this.filePath} created successfully.`)
|
||||
}
|
||||
}
|
||||
|
||||
async reset(): Promise<void> {
|
||||
await jsonfile.writeFile(this.filePath, defaultData, { spaces: 2 })
|
||||
}
|
||||
|
||||
// Read JSON from file
|
||||
async read(): Promise<DatabaseScheme> {
|
||||
try {
|
||||
return await jsonfile.readFile(this.filePath)
|
||||
} catch (error: any) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return defaultData
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Update JSON file with atomic write
|
||||
async update(
|
||||
updateCallback: (
|
||||
data: Awaited<DatabaseScheme>,
|
||||
) => Awaited<DatabaseScheme> | Promise<Awaited<DatabaseScheme>>,
|
||||
): Promise<void> {
|
||||
let data = await this.read()
|
||||
data = await updateCallback(data)
|
||||
await jsonfile.writeFile(this.filePath, data, { spaces: 2 })
|
||||
}
|
||||
|
||||
// **Queue Methods Integrated Here**
|
||||
|
||||
/** Push a task to the send queue */
|
||||
async pushToSendQueue(task: FileItemTask): Promise<void> {
|
||||
await this.update((data) => {
|
||||
data.local_resources.task_schemes.send_file_queue.push(task)
|
||||
return data
|
||||
})
|
||||
}
|
||||
|
||||
/** Push a task to the receive queue */
|
||||
async pushToReceiveQueue(task: FileItemTask): Promise<void> {
|
||||
await this.update((data) => {
|
||||
data.local_resources.task_schemes.receive_file_queue.push(task)
|
||||
return data
|
||||
})
|
||||
}
|
||||
|
||||
/** Pop (remove) the first task from the send queue */
|
||||
async popFromSendQueue(): Promise<FileItemTask | undefined> {
|
||||
let poppedTask: FileItemTask | undefined
|
||||
await this.update((data) => {
|
||||
poppedTask = data.local_resources.task_schemes.send_file_queue.shift()
|
||||
return data
|
||||
})
|
||||
return poppedTask
|
||||
}
|
||||
|
||||
/** Pop (remove) the first task from the receive queue */
|
||||
async popFromReceiveQueue(): Promise<FileItemTask | undefined> {
|
||||
let poppedTask: FileItemTask | undefined
|
||||
await this.update((data) => {
|
||||
poppedTask = data.local_resources.task_schemes.receive_file_queue.shift()
|
||||
return data
|
||||
})
|
||||
return poppedTask
|
||||
}
|
||||
|
||||
/** View the first task in the send queue without removing it */
|
||||
async seekSendQueue(): Promise<FileItemTask | undefined> {
|
||||
const data = await this.read()
|
||||
return data.local_resources.task_schemes.send_file_queue[0]
|
||||
}
|
||||
|
||||
/** View the first task in the receive queue without removing it */
|
||||
async seekReceiveQueue(): Promise<FileItemTask | undefined> {
|
||||
const data = await this.read()
|
||||
return data.local_resources.task_schemes.receive_file_queue[0]
|
||||
}
|
||||
|
||||
/** Get the length of the send queue */
|
||||
async sendQueueSize(): Promise<number> {
|
||||
const data = await this.read()
|
||||
return data.local_resources.task_schemes.send_file_queue.length
|
||||
}
|
||||
|
||||
/** Get the length of the receive queue */
|
||||
async receiveQueueSize(): Promise<number> {
|
||||
const data = await this.read()
|
||||
return data.local_resources.task_schemes.receive_file_queue.length
|
||||
}
|
||||
|
||||
/** Clear all tasks from the send queue */
|
||||
async clearSendQueue(): Promise<void> {
|
||||
await this.update((data) => {
|
||||
data.local_resources.task_schemes.send_file_queue = []
|
||||
return data
|
||||
})
|
||||
}
|
||||
|
||||
/** Clear all tasks from the receive queue */
|
||||
async clearReceiveQueue(): Promise<void> {
|
||||
await this.update((data) => {
|
||||
data.local_resources.task_schemes.receive_file_queue = []
|
||||
return data
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export interface AppConfigScheme {
|
||||
app_type: 'client' | 'ceo'
|
||||
user_info: UserInfoScheme
|
||||
encryption_key: EncryptionKeyScheme
|
||||
reset_application_preferences: boolean
|
||||
logged_in: boolean
|
||||
server_found: boolean
|
||||
}
|
||||
|
||||
export interface UserInfoScheme {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
departmentId: string
|
||||
}
|
||||
|
||||
export interface EncryptionKeyScheme {
|
||||
key: string
|
||||
iv: string
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { NetworkScheme } from './network_scheme'
|
||||
import { AppConfigScheme } from './app_config_scheme'
|
||||
import { LocalResourcesScheme } from './local_resources_scheme'
|
||||
|
||||
export interface DatabaseScheme {
|
||||
app_config: AppConfigScheme
|
||||
network: NetworkScheme
|
||||
local_resources: LocalResourcesScheme
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
export interface LocalResourcesScheme {
|
||||
directory_schemes: DirectorySchemes
|
||||
task_schemes: TaskSchemes
|
||||
}
|
||||
|
||||
export interface DirectorySchemes {
|
||||
backup: DirectoryInfo
|
||||
department: DirectoryInfo
|
||||
shared: DirectoryInfo
|
||||
}
|
||||
|
||||
export interface DirectoryInfo {
|
||||
id: string
|
||||
path: string
|
||||
structure: any
|
||||
totalSize: number
|
||||
}
|
||||
|
||||
export interface TaskSchemes {
|
||||
send_file_queue: FileItemTask[]
|
||||
receive_file_queue: FileItemTask[]
|
||||
}
|
||||
|
||||
export interface FileItemTask {
|
||||
ip: string
|
||||
path: string
|
||||
userName: string
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export interface NetworkUserScheme {
|
||||
ip: string
|
||||
name: string
|
||||
departmentId: string
|
||||
}
|
||||
|
||||
export interface NetworkScheme {
|
||||
serverIp: string
|
||||
usersInLan: NetworkUserScheme[]
|
||||
announcement: string
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import { TcpCommunicator } from './tcp_communicator'
|
||||
import { operationCodes } from '../network/operation_codes'
|
||||
import { ParsedMessage } from '../network/message_handler'
|
||||
import { Database } from '../database/database'
|
||||
|
||||
export class AnnouncementSender {
|
||||
private readonly db: Database
|
||||
private readonly port: number
|
||||
private message: string = ''
|
||||
private tcpCommunicator: TcpCommunicator | null = null
|
||||
private stopRequested: boolean = false
|
||||
|
||||
constructor(pathToDatabaseFile: string, port: number) {
|
||||
this.db = new Database(pathToDatabaseFile)
|
||||
this.port = port
|
||||
}
|
||||
|
||||
async start(message: string): Promise<void> {
|
||||
console.log('AnnouncementWorker started.')
|
||||
this.message = message
|
||||
this.stopRequested = false
|
||||
|
||||
try {
|
||||
const data = await this.db.read()
|
||||
|
||||
const activeUsersIp = data.network.usersInLan.map((user) => user.ip)
|
||||
if (!activeUsersIp || !activeUsersIp.length) {
|
||||
throw new Error('No active users found.')
|
||||
}
|
||||
|
||||
for (const ip of activeUsersIp) {
|
||||
if (this.stopRequested) {
|
||||
console.log('AnnouncementWorker stopped.')
|
||||
break
|
||||
}
|
||||
|
||||
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}`)
|
||||
}
|
||||
|
||||
process.send?.({
|
||||
type: 'showAlert',
|
||||
message: 'Announcement sent to all active users successfully.',
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error('Error in AnnouncementWorker:', error)
|
||||
process.send?.({ type: 'shotAlert', message: `A problem occurred: ${error.message}` })
|
||||
}
|
||||
|
||||
console.log('AnnouncementWorker finished.')
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
console.log('Stopping AnnouncementWorker...')
|
||||
this.stopRequested = true
|
||||
if (this.tcpCommunicator) {
|
||||
await this.tcpCommunicator.disconnect()
|
||||
}
|
||||
console.log('AnnouncementWorker stopped.')
|
||||
}
|
||||
|
||||
private async sendAnnouncementToIp(ip: string): Promise<boolean> {
|
||||
this.tcpCommunicator = new TcpCommunicator(ip, this.port)
|
||||
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.stopRequested || !this.tcpCommunicator) {
|
||||
clearInterval(idResponseCheck)
|
||||
resolve(null)
|
||||
return
|
||||
}
|
||||
|
||||
if (this.tcpCommunicator.hasResponseArrived()) {
|
||||
clearInterval(idResponseCheck)
|
||||
resolve(this.tcpCommunicator.getLastResult())
|
||||
}
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { FileEncryptor } from './file_encryptor'
|
||||
import { TcpCommunicator } from './tcp_communicator'
|
||||
import { operationCodes } from '../network/operation_codes'
|
||||
import { ParsedMessage } from '../network/message_handler'
|
||||
import { Database } from '../database/database'
|
||||
import { NetworkUserScheme } from '../database/schemes/network_scheme'
|
||||
import { UserInfoScheme } from '../database/schemes/app_config_scheme'
|
||||
|
||||
const backupDirectoryPath = path.join(__dirname, '..', 'backups')
|
||||
|
||||
export class BackupManager {
|
||||
private fileEncryptor: FileEncryptor | null = null
|
||||
private readonly db: Database
|
||||
private readonly port: number
|
||||
private serverIp: string = ''
|
||||
private isBusy: boolean = false
|
||||
private intervalId: NodeJS.Timeout | null = null
|
||||
private stopRequested: boolean = false
|
||||
|
||||
constructor(pathToDatabaseFile: string, port: number) {
|
||||
this.db = new Database(pathToDatabaseFile)
|
||||
this.port = port
|
||||
this.db.read().then((data) => {
|
||||
this.serverIp = data.network.serverIp
|
||||
})
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
this.intervalId = setInterval(async () => {
|
||||
if (!this.isBusy || !this.stopRequested) {
|
||||
this.isBusy = true
|
||||
this.log('Start successfully. Backup files to users.')
|
||||
await this.initialize()
|
||||
}
|
||||
|
||||
if (global.gc) {
|
||||
global.gc()
|
||||
}
|
||||
}, 10000) // 10-second interval for testing
|
||||
}
|
||||
|
||||
private async initialize(): Promise<void> {
|
||||
this.isBusy = true
|
||||
try {
|
||||
const data = await this.db.read()
|
||||
const userInfo = data.app_config.user_info
|
||||
const encryptionKey = data.app_config.encryption_key
|
||||
const usersIp = data.network.usersInLan.map((user: NetworkUserScheme) => user.ip)
|
||||
const backupDirectoryData = data.local_resources.directory_schemes.backup
|
||||
|
||||
this.fileEncryptor = new FileEncryptor(encryptionKey.key, encryptionKey.iv)
|
||||
|
||||
await this.sendFilesToUsers(
|
||||
userInfo,
|
||||
usersIp,
|
||||
backupDirectoryData.structure,
|
||||
backupDirectoryData.path,
|
||||
)
|
||||
|
||||
await this.validateBackupDirectories(backupDirectoryPath)
|
||||
} catch (error: any) {
|
||||
this.log(`Error in initialize process: ${error.message}`, 'error')
|
||||
} finally {
|
||||
this.log('Backup process completed.')
|
||||
this.isBusy = false
|
||||
}
|
||||
}
|
||||
|
||||
private encryptFile(filePath: string): string {
|
||||
if (!this.fileEncryptor) {
|
||||
return filePath
|
||||
}
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
this.log(`File not found: ${filePath}`, 'error')
|
||||
return ''
|
||||
}
|
||||
|
||||
return this.fileEncryptor.encryptFileToBase64(filePath)
|
||||
}
|
||||
|
||||
private async fetchUsersFromServer(): Promise<Set<string>> {
|
||||
const tcpCommunicator = new TcpCommunicator(this.serverIp, this.port)
|
||||
|
||||
try {
|
||||
await tcpCommunicator.connect()
|
||||
this.log(`Connected to server at ${this.serverIp}`)
|
||||
|
||||
await tcpCommunicator.sendMessage(operationCodes.GET_USERS, {})
|
||||
|
||||
const response = await this.waitForResponse(tcpCommunicator)
|
||||
if (!response || !response.metaInfo || !response.metaInfo.users) {
|
||||
throw new Error('Invalid or missing user data from server.')
|
||||
}
|
||||
|
||||
const validUserDirectories = new Set<string>()
|
||||
for (const user of response.metaInfo.users) {
|
||||
const { name, departmentId } = user
|
||||
validUserDirectories.add(`${name}-${departmentId}`)
|
||||
}
|
||||
|
||||
return validUserDirectories
|
||||
} catch (error) {
|
||||
this.log(`Failed to fetch user list from server. Error: ${error}`, 'error')
|
||||
return new Set<string>()
|
||||
} finally {
|
||||
await tcpCommunicator.disconnect()
|
||||
this.log(`Disconnected from server at ${this.serverIp}`)
|
||||
}
|
||||
}
|
||||
|
||||
private async validateBackupDirectories(backupDirectoryPath: string): Promise<void> {
|
||||
try {
|
||||
const validUserDirectories = await this.fetchUsersFromServer()
|
||||
|
||||
if (!fs.existsSync(backupDirectoryPath)) {
|
||||
this.log('Backup directory does not exist. No cleanup needed.')
|
||||
return
|
||||
}
|
||||
|
||||
const existingDirectories = fs
|
||||
.readdirSync(backupDirectoryPath)
|
||||
.filter((dir) => fs.statSync(path.join(backupDirectoryPath, dir)).isDirectory())
|
||||
|
||||
for (const directory of existingDirectories) {
|
||||
if (!validUserDirectories.has(directory)) {
|
||||
this.log(`Deleting unrecognized backup directory: ${directory}`, 'warn')
|
||||
fs.rmSync(path.join(backupDirectoryPath, directory), { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
this.log('Backup directory validation and cleanup complete.')
|
||||
} catch (error) {
|
||||
// @ts-ignore
|
||||
this.log(`Error validating backup directories: ${error.message}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
private async sendFilesToUsers(
|
||||
userInfo: UserInfoScheme,
|
||||
usersIp: string[],
|
||||
fileStructure: { [key: string]: string },
|
||||
backupDirectoryPath: string,
|
||||
): Promise<void> {
|
||||
let unsentFiles = Object.keys(fileStructure)
|
||||
let remainingUsers = [...usersIp]
|
||||
|
||||
while (unsentFiles.length > 0 && remainingUsers.length > 0) {
|
||||
for (const ip of remainingUsers) {
|
||||
const tcpCommunicator = new TcpCommunicator(ip, this.port)
|
||||
|
||||
try {
|
||||
await tcpCommunicator.connect()
|
||||
this.log(`Connected to ${ip}`)
|
||||
|
||||
const clearBackupMeta = { name: userInfo.name, departmentId: userInfo.departmentId }
|
||||
await tcpCommunicator.sendMessage(operationCodes.CLEAR_BACKUP, clearBackupMeta)
|
||||
|
||||
for (const fileName of [...unsentFiles]) {
|
||||
const filePath = fileStructure[fileName]
|
||||
const encryptedFileContent = this.encryptFile(filePath)
|
||||
|
||||
if (!encryptedFileContent) {
|
||||
this.log(`Failed to encrypt file: ${fileName}`, 'error')
|
||||
continue
|
||||
}
|
||||
|
||||
const relativeFilePath = path.relative(backupDirectoryPath, filePath)
|
||||
const metaInfo = {
|
||||
name: userInfo.name,
|
||||
departmentId: userInfo.departmentId,
|
||||
relativeFilePath,
|
||||
}
|
||||
|
||||
const sendSuccess = await tcpCommunicator.sendMessage(
|
||||
operationCodes.BACKUP_FILE,
|
||||
metaInfo,
|
||||
Buffer.from(encryptedFileContent, 'base64'),
|
||||
)
|
||||
if (!sendSuccess) {
|
||||
throw new Error('Failed to send file content.')
|
||||
}
|
||||
|
||||
const responseReceived = await this.waitForResponse(tcpCommunicator)
|
||||
if (!responseReceived) {
|
||||
throw new Error('Timeout waiting for the message response.')
|
||||
}
|
||||
|
||||
this.log(`Successfully sent file: ${fileName} to ${ip}`)
|
||||
unsentFiles = unsentFiles.filter((f) => f !== fileName)
|
||||
}
|
||||
} catch (error) {
|
||||
this.log(`Failed to send backup to ${ip}. Error: ${error}`, 'error')
|
||||
} finally {
|
||||
await tcpCommunicator.disconnect()
|
||||
this.log(`Disconnected from ${ip}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Update remaining users to retry
|
||||
remainingUsers = usersIp.filter((ip) => !this.isBackupCompleteForIp(ip, unsentFiles))
|
||||
|
||||
if (remainingUsers.length === 0) {
|
||||
this.log('Backup process retried for all users. Exiting retry loop.')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (unsentFiles.length > 0) {
|
||||
process.send?.({ type: 'log', message: 'Backup could not be completed for all files' })
|
||||
} else {
|
||||
process.send?.({ type: 'log', message: 'Backup completed successfully' })
|
||||
}
|
||||
}
|
||||
|
||||
private isBackupCompleteForIp(ip: string, unsentFiles: string[]): boolean {
|
||||
return unsentFiles.length === 0
|
||||
}
|
||||
|
||||
private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise<ParsedMessage | null> {
|
||||
return new Promise((resolve) => {
|
||||
const idResponseCheck = setInterval(() => {
|
||||
if (!tcpCommunicator) return null
|
||||
if (tcpCommunicator.hasResponseArrived()) {
|
||||
clearInterval(idResponseCheck)
|
||||
resolve(tcpCommunicator.getLastResult())
|
||||
}
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
this.stopRequested = true // Signal that stop is requested
|
||||
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId)
|
||||
this.intervalId = null
|
||||
}
|
||||
|
||||
while (this.isBusy) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
}
|
||||
|
||||
console.log('[BackupManager] Stopped successfully.')
|
||||
}
|
||||
|
||||
private log(message: string, level: 'log' | 'error' | 'warn' = 'log'): void {
|
||||
const prefix = '[BackupManager]'
|
||||
if (level === 'error') {
|
||||
console.error(`${prefix} ${message}`)
|
||||
} else if (level === 'warn') {
|
||||
console.warn(`${prefix} ${message}`)
|
||||
} else {
|
||||
console.log(`${prefix} ${message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
import { TcpCommunicator } from './tcp_communicator'
|
||||
import { operationCodes } from '../network/operation_codes'
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import crypto from 'crypto'
|
||||
import { ParsedMessage } from '../network/message_handler'
|
||||
import { Database } from '../database/database'
|
||||
import { UserInfoScheme } from '../database/schemes/app_config_scheme'
|
||||
|
||||
export class BackupRetrievalWorker {
|
||||
private db: Database
|
||||
private readonly clientPort: number
|
||||
private readonly destinationPath: string
|
||||
private encryptionKey: Buffer | null = null
|
||||
private iv: Buffer | null = null
|
||||
private tcpCommunicator: TcpCommunicator | null = null
|
||||
private stopRequested: boolean = false
|
||||
private isBusy: boolean = false
|
||||
|
||||
constructor(pathToDatabaseFile: string, clientPort: number, destinationPath: string) {
|
||||
this.db = new Database(pathToDatabaseFile)
|
||||
this.clientPort = clientPort
|
||||
this.destinationPath = destinationPath
|
||||
}
|
||||
|
||||
// Logging helper function
|
||||
private log(message: string, level: 'log' | 'error' = 'log'): void {
|
||||
const prefix = '[BackupRetrievalWorker]'
|
||||
if (level === 'error') {
|
||||
console.error(`${prefix} ${message}`)
|
||||
} else {
|
||||
console.log(`${prefix} ${message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
if (this.stopRequested) return
|
||||
this.isBusy = true
|
||||
try {
|
||||
this.log('Start successfully. Retrieving backup.')
|
||||
const data = await this.db.read()
|
||||
const userInfo = data.app_config.user_info
|
||||
const encryptionKey = data.app_config.encryption_key
|
||||
|
||||
this.encryptionKey = Buffer.from(encryptionKey.key, 'base64')
|
||||
this.iv = Buffer.from(encryptionKey.iv, 'base64')
|
||||
|
||||
const activeUsers = data.network.usersInLan
|
||||
if (!activeUsers.length) {
|
||||
throw new Error('No active users found.')
|
||||
}
|
||||
|
||||
for (const lanUser of activeUsers) {
|
||||
const success = await this.processBackupForIp(lanUser.ip, userInfo)
|
||||
if (!success) {
|
||||
throw new Error(`Failed to retrieve backup from ${lanUser.ip}`)
|
||||
}
|
||||
this.log(`Backup retrieved successfully from ${lanUser.ip}`)
|
||||
}
|
||||
|
||||
process.send?.({ type: 'showAlert', message: 'Backup retrieval completed successfully.' })
|
||||
process.send?.({ type: 'changeContent', page: 'main_menu' })
|
||||
} catch (error: any) {
|
||||
this.log(`Error in BackupRetrievalWorker: ${error.message}`, 'error')
|
||||
process.send?.({ type: 'showAlert', message: `A problem occurred: ${error.message}` })
|
||||
process.send?.({ type: 'changeContent', page: 'main_menu' })
|
||||
} finally {
|
||||
this.isBusy = false
|
||||
}
|
||||
|
||||
if (global.gc) {
|
||||
global.gc()
|
||||
}
|
||||
}
|
||||
|
||||
private async processBackupForIp(ip: string, userInfo: UserInfoScheme): Promise<boolean> {
|
||||
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort)
|
||||
if (!(await this.tcpCommunicator.connect())) {
|
||||
this.log(`Failed to connect to ${ip}`, 'error')
|
||||
return true
|
||||
}
|
||||
|
||||
const backupExists = await this.checkIfBackupExists(userInfo.name, userInfo.departmentId)
|
||||
if (!backupExists) {
|
||||
this.log(`No backup found for user ${userInfo.name} on IP ${ip}`)
|
||||
await this.tcpCommunicator.disconnect()
|
||||
return true
|
||||
}
|
||||
|
||||
const backupStructure = await this.requestBackupStructure(userInfo.name, userInfo.departmentId)
|
||||
if (!backupStructure || Object.keys(backupStructure).length === 0) {
|
||||
this.log(`No files found in backup structure for user ${userInfo.name} on IP ${ip}`)
|
||||
await this.tcpCommunicator.disconnect()
|
||||
return true
|
||||
}
|
||||
|
||||
for (const relativeFilePath of Object.keys(backupStructure)) {
|
||||
const fileRequestSuccess = await this.requestBackupFile(
|
||||
userInfo.name,
|
||||
userInfo.departmentId,
|
||||
relativeFilePath,
|
||||
)
|
||||
if (!fileRequestSuccess) {
|
||||
await this.tcpCommunicator.disconnect()
|
||||
throw new Error(
|
||||
`Failed to retrieve file ${relativeFilePath} from backup for user ${userInfo.name} on IP ${ip}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await this.tcpCommunicator.disconnect()
|
||||
return true
|
||||
}
|
||||
|
||||
private async checkIfBackupExists(name: string, departmentId: string): Promise<boolean> {
|
||||
if (!this.tcpCommunicator) return false
|
||||
|
||||
const metaInfo = { name, departmentId }
|
||||
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(name: string, departmentId: string): Promise<any> {
|
||||
if (!this.tcpCommunicator) return false
|
||||
|
||||
const metaInfo = { name, departmentId }
|
||||
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(
|
||||
name: string,
|
||||
departmentId: string,
|
||||
relativeFilePath: string,
|
||||
): Promise<boolean> {
|
||||
if (!this.tcpCommunicator) return false
|
||||
|
||||
const metaInfo = { name, departmentId, 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) {
|
||||
throw new Error('Encryption key or IV is not set.')
|
||||
}
|
||||
|
||||
let encryptedBuffer: Buffer
|
||||
try {
|
||||
encryptedBuffer = Buffer.from(fileContent, 'base64')
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error decoding base64 file content: ${error.message}`)
|
||||
}
|
||||
|
||||
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: any) {
|
||||
throw new Error(`Error decrypting file: ${error.message}`)
|
||||
}
|
||||
|
||||
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)
|
||||
this.log(`File saved successfully: ${fullFilePath}`)
|
||||
return true
|
||||
} catch (error: any) {
|
||||
throw new Error(`Error saving file ${relativeFilePath}: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { TcpCommunicator } from './tcp_communicator'
|
||||
import { operationCodes } from '../network/operation_codes'
|
||||
import { ParsedMessage } from '../network/message_handler'
|
||||
import { Database } from '../database/database'
|
||||
import { NetworkUserScheme } from '../database/schemes/network_scheme'
|
||||
|
||||
export class DepartmentSharer {
|
||||
private readonly db: Database
|
||||
private departmentDirectory: string | null = null
|
||||
private readonly clientPort: number
|
||||
private isBusy: boolean = false
|
||||
private tcpCommunicator: TcpCommunicator | null = null
|
||||
private stopRequested: boolean = false
|
||||
private intervalId: NodeJS.Timeout | null = null
|
||||
|
||||
constructor(pathToDatabaseFile: string, clientPort: number) {
|
||||
this.db = new Database(pathToDatabaseFile)
|
||||
this.clientPort = clientPort
|
||||
}
|
||||
|
||||
// Start sharing files with the department every minute
|
||||
async start(): Promise<void> {
|
||||
this.intervalId = setInterval(async () => {
|
||||
if (!this.isBusy || !this.stopRequested) {
|
||||
this.isBusy = true
|
||||
this.log('Start successfully. Sharing files with the department.')
|
||||
await this.shareFilesWithDepartment()
|
||||
}
|
||||
|
||||
if (global.gc) {
|
||||
global.gc()
|
||||
}
|
||||
}, 10000) // 10-second interval for testing
|
||||
}
|
||||
|
||||
// Share files with users in the same department
|
||||
private async shareFilesWithDepartment(): Promise<void> {
|
||||
try {
|
||||
const data = await this.db.read()
|
||||
const userInfo = data.app_config.user_info
|
||||
const departmentStructure = data.local_resources.directory_schemes.department
|
||||
|
||||
const departmentId = userInfo.departmentId
|
||||
const userName = userInfo.name
|
||||
const activeUsers = data.network.usersInLan
|
||||
this.departmentDirectory = departmentStructure.path
|
||||
|
||||
if (departmentStructure.path === '') {
|
||||
throw new Error('Department directory not set.')
|
||||
}
|
||||
|
||||
// Filter users who belong to the same department
|
||||
const departmentUsers = activeUsers.filter(
|
||||
(user: NetworkUserScheme) => user.departmentId === departmentId,
|
||||
)
|
||||
if (departmentUsers.length === 0) {
|
||||
throw new Error('No users found in the same department.')
|
||||
}
|
||||
|
||||
// Iterate over all department users and perform the operations
|
||||
for (const user of departmentUsers) {
|
||||
const userIp = user.ip
|
||||
this.tcpCommunicator = new TcpCommunicator(userIp, this.clientPort)
|
||||
|
||||
if (!(await this.tcpCommunicator.connect())) continue
|
||||
|
||||
// First clear the department directory;
|
||||
if (await this.clearDepartmentDirectory(userName)) {
|
||||
await this.sendFilesToUser(departmentStructure.structure, userName)
|
||||
}
|
||||
|
||||
await this.tcpCommunicator.disconnect()
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.log(error.message, 'error')
|
||||
} finally {
|
||||
this.log('Department sharing completed.')
|
||||
this.isBusy = false
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the department directory for a user
|
||||
private async clearDepartmentDirectory(userName: string): Promise<boolean> {
|
||||
if (!this.tcpCommunicator) return false
|
||||
|
||||
if (
|
||||
!(await this.tcpCommunicator.sendMessage(operationCodes.CLEAR_DEPARTMENT, {
|
||||
userName: userName,
|
||||
}))
|
||||
)
|
||||
return false
|
||||
const response = await this.waitForResponse()
|
||||
|
||||
if (!response || response.operationCode !== operationCodes.OK) {
|
||||
this.log('Failed to clear the department directory.', 'error')
|
||||
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)
|
||||
|
||||
for (const fileName of unsentFiles) {
|
||||
const filePath = files[fileName]
|
||||
|
||||
// Ensure the file exists before attempting to send
|
||||
if (!fs.existsSync(filePath)) {
|
||||
this.log(`File not found: ${filePath}`, 'error')
|
||||
continue
|
||||
}
|
||||
|
||||
// Read the file content
|
||||
const fileContent = fs.readFileSync(filePath)
|
||||
|
||||
// Get the relative path of the file (used in the meta info)
|
||||
if (!this.departmentDirectory) return
|
||||
const relativeFilePath = path.relative(this.departmentDirectory, filePath)
|
||||
|
||||
// Prepare the metaInfo (same structure as FileSharer)
|
||||
const metaInfo = {
|
||||
userName,
|
||||
relativeFilePath,
|
||||
}
|
||||
|
||||
// Send the file
|
||||
if (
|
||||
!(await this.tcpCommunicator.sendMessage(
|
||||
operationCodes.DEPARTMENT_FILE,
|
||||
metaInfo,
|
||||
Buffer.from(fileContent),
|
||||
))
|
||||
)
|
||||
return
|
||||
|
||||
const response = await this.waitForResponse()
|
||||
if (!response || response.operationCode !== operationCodes.OK) {
|
||||
this.log(`Failed to send file: ${fileName}`, 'error')
|
||||
return
|
||||
}
|
||||
|
||||
this.log(`File sent successfully: ${fileName} to ${userName}`)
|
||||
|
||||
unsentFiles.splice(unsentFiles.indexOf(fileName), 1)
|
||||
await this.tcpCommunicator.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
this.stopRequested = true // Signal that stop is requested
|
||||
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId)
|
||||
this.intervalId = null
|
||||
}
|
||||
|
||||
// Wait for any ongoing process to complete if busy
|
||||
while (this.isBusy) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
// Unified logging function
|
||||
private log(message: string, level: 'log' | 'error' = 'log'): void {
|
||||
const prefix = '[DepartmentSharer]'
|
||||
if (level === 'error') {
|
||||
console.error(`${prefix} ${message}`)
|
||||
} else {
|
||||
console.log(`${prefix} ${message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
import { Database } from '../database/database'
|
||||
|
||||
export class DirectoryWatcher {
|
||||
private readonly db: Database
|
||||
private intervalId: NodeJS.Timeout | null = null
|
||||
|
||||
constructor(pathToDatabaseFile: string) {
|
||||
this.db = new Database(pathToDatabaseFile)
|
||||
}
|
||||
|
||||
// Start scanning directories at a fixed interval
|
||||
async start(scanInterval: number = 10000): Promise<void> {
|
||||
if (this.intervalId) {
|
||||
this.log('Directory watcher is already running.', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
this.intervalId = setInterval(async () => {
|
||||
await this.scanDirectories()
|
||||
}, scanInterval)
|
||||
|
||||
this.log('Directory scanning started successfully.')
|
||||
}
|
||||
|
||||
// Scan all directories from the database
|
||||
private async scanDirectories(): Promise<void> {
|
||||
const dbData = await this.db.read()
|
||||
const directorySchemes = dbData.local_resources?.directory_schemes
|
||||
|
||||
if (!directorySchemes) {
|
||||
this.log('Error: directory_schemes not found in database.', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
// Scan each directory if it exists
|
||||
await this.scanDirectory('backup', directorySchemes.backup?.path)
|
||||
await this.scanDirectory('department', directorySchemes.department?.path)
|
||||
await this.scanDirectory('shared', directorySchemes.shared?.path)
|
||||
}
|
||||
|
||||
// Scan a specific directory and update the database
|
||||
private async scanDirectory(id: string, directoryPath: string | undefined): Promise<void> {
|
||||
if (!directoryPath) {
|
||||
this.log(`Skipping scan: No path provided for ${id}.`, 'error')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if directory exists
|
||||
await fs.access(directoryPath)
|
||||
} catch {
|
||||
this.log(`Skipping scan: Directory ${directoryPath} does not exist.`, 'error')
|
||||
return
|
||||
}
|
||||
|
||||
// If exists, build its structure
|
||||
const result = await this.buildDirectoryScheme(directoryPath)
|
||||
|
||||
await this.db.update((data) => {
|
||||
switch (id) {
|
||||
case 'backup':
|
||||
data.local_resources.directory_schemes.backup.structure = result.structure
|
||||
data.local_resources.directory_schemes.backup.totalSize = result.size
|
||||
break
|
||||
case 'department':
|
||||
data.local_resources.directory_schemes.department.structure = result.structure
|
||||
data.local_resources.directory_schemes.department.totalSize = result.size
|
||||
break
|
||||
case 'shared':
|
||||
data.local_resources.directory_schemes.shared.structure = result.structure
|
||||
data.local_resources.directory_schemes.shared.totalSize = result.size
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return data
|
||||
})
|
||||
|
||||
this.log(`Scanned and updated directory: ${directoryPath} (ID: ${id})`)
|
||||
}
|
||||
|
||||
// Recursively build the directory structure and calculate total size
|
||||
private async buildDirectoryScheme(dirPath: string): Promise<{ structure: any; size: number }> {
|
||||
const directoryScheme: any = {}
|
||||
let totalSize = 0
|
||||
|
||||
const items = await fs.readdir(dirPath, { withFileTypes: true })
|
||||
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(dirPath, item.name)
|
||||
const stats = await fs.stat(fullPath)
|
||||
|
||||
if (item.isDirectory()) {
|
||||
const { structure, size } = await this.buildDirectoryScheme(fullPath)
|
||||
directoryScheme[item.name] = structure
|
||||
totalSize += size
|
||||
} else if (item.isFile()) {
|
||||
directoryScheme[item.name] = fullPath
|
||||
totalSize += stats.size
|
||||
}
|
||||
}
|
||||
|
||||
return { structure: directoryScheme, size: totalSize }
|
||||
}
|
||||
|
||||
// Stop directory scanning
|
||||
public stop(): void {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId)
|
||||
this.intervalId = null
|
||||
this.log('Stopped directory scanning.')
|
||||
}
|
||||
}
|
||||
|
||||
// Unified logging function
|
||||
private log(message: string, level: 'log' | 'error' = 'log'): void {
|
||||
const sourcePrefix = `[DirectoryWatcher]`
|
||||
if (level === 'error') {
|
||||
console.error(`${sourcePrefix} ${message}`)
|
||||
} else {
|
||||
console.log(`${sourcePrefix} ${message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import fs from 'fs'
|
||||
import crypto from 'crypto'
|
||||
|
||||
export class FileEncryptor {
|
||||
private readonly encryptionKey: Buffer
|
||||
private readonly iv: Buffer
|
||||
|
||||
constructor(base64Key: string, base64Iv: string) {
|
||||
// Decode the base64-encoded key and IV
|
||||
this.encryptionKey = Buffer.from(base64Key, 'base64')
|
||||
this.iv = Buffer.from(base64Iv, 'base64')
|
||||
}
|
||||
|
||||
// Method to read a file, encrypt it, and return the encrypted content as a base64 string
|
||||
public encryptFileToBase64(filePath: string): string {
|
||||
try {
|
||||
// Read the file contents
|
||||
const fileBuffer = fs.readFileSync(filePath)
|
||||
|
||||
// Create the cipher using AES-256-CBC (or another algorithm you prefer)
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', this.encryptionKey, this.iv)
|
||||
|
||||
// Encrypt the file data
|
||||
let encryptedData = cipher.update(fileBuffer)
|
||||
encryptedData = Buffer.concat([encryptedData, cipher.final()])
|
||||
|
||||
// Return the encrypted data as a base64 string
|
||||
return encryptedData.toString('base64')
|
||||
} catch (err) {
|
||||
console.error(`Error encrypting file at path ${filePath}:`, err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Method to decrypt base64-encoded encrypted content and return the decrypted buffer
|
||||
public decryptBase64(encryptedBase64: string): Buffer {
|
||||
try {
|
||||
// Decode the base64-encoded encrypted data
|
||||
const encryptedData = Buffer.from(encryptedBase64, 'base64')
|
||||
|
||||
// Create the decipher using AES-256-CBC
|
||||
const decipher = crypto.createDecipheriv('aes-256-cbc', this.encryptionKey, this.iv)
|
||||
|
||||
// Decrypt the data
|
||||
let decryptedData = decipher.update(encryptedData)
|
||||
decryptedData = Buffer.concat([decryptedData, decipher.final()])
|
||||
|
||||
// Return the decrypted buffer
|
||||
return decryptedData
|
||||
} catch (err) {
|
||||
console.error('Error decrypting data:', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { TcpCommunicator } from './tcp_communicator'
|
||||
import { operationCodes } from '../network/operation_codes'
|
||||
import { ParsedMessage } from '../network/message_handler'
|
||||
import { Database } from '../database/database'
|
||||
|
||||
interface FileSendTask {
|
||||
ip: string
|
||||
path: string
|
||||
userName: string
|
||||
}
|
||||
|
||||
export class FileSharer {
|
||||
private readonly db: Database
|
||||
private readonly clientPort: number
|
||||
private isBusy: boolean = false
|
||||
private tcpCommunicator: TcpCommunicator | null = null
|
||||
private stopRequested: boolean = false
|
||||
private intervalId: NodeJS.Timeout | null = null
|
||||
|
||||
constructor(pathToDatabaseFile: string, clientPort: number) {
|
||||
this.db = new Database(pathToDatabaseFile)
|
||||
this.clientPort = clientPort
|
||||
}
|
||||
|
||||
// Start processing the file queue
|
||||
async start(): Promise<void> {
|
||||
this.intervalId = setInterval(async () => {
|
||||
if (!this.isBusy || !this.stopRequested) {
|
||||
// Check if the queue is already being processed
|
||||
this.isBusy = true // Set busy flag to true before starting
|
||||
this.log('Start successfully. Processing the queue.')
|
||||
await this.processQueue()
|
||||
}
|
||||
|
||||
if (global.gc) {
|
||||
global.gc()
|
||||
}
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
// Method to process the queue
|
||||
private async processQueue(): Promise<void> {
|
||||
const queueSize = await this.db.sendQueueSize()
|
||||
for (let i = 0; i < queueSize; i++) {
|
||||
const task = await this.db.popFromSendQueue()
|
||||
if (task) {
|
||||
this.log(`Processing task for file: ${task.path} to IP: ${task.ip}`)
|
||||
const success = await this.sendFile(task)
|
||||
|
||||
if (!success) {
|
||||
this.log(
|
||||
`Failed to send file: ${task.path} to IP: ${task.ip}. Re-adding to queue.`,
|
||||
'error',
|
||||
)
|
||||
await this.db.pushToSendQueue(task)
|
||||
} else {
|
||||
this.log(`File sent successfully: ${task.path} to IP: ${task.ip}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.log('Queue processed successfully.')
|
||||
this.isBusy = false // Reset busy flag after the queue is processed
|
||||
}
|
||||
|
||||
// Method to send the file to a specific IP using TcpCommunicator
|
||||
private async sendFile(task: FileSendTask): Promise<boolean> {
|
||||
const { ip, path: filePath, userName } = task
|
||||
|
||||
// Ensure the file exists before attempting to send
|
||||
if (!fs.existsSync(filePath)) {
|
||||
this.log(`File not found: ${filePath}`, 'error')
|
||||
return false
|
||||
}
|
||||
|
||||
// Read the file contents
|
||||
const fileContent = fs.readFileSync(filePath)
|
||||
|
||||
// Extract the file name from the file path using path.basename
|
||||
const fileName = path.basename(filePath)
|
||||
|
||||
const metaInfo = {
|
||||
userName, // Sender's username
|
||||
relativeFilePath: fileName, // Use the file name instead of the full path
|
||||
}
|
||||
|
||||
this.tcpCommunicator = new TcpCommunicator(ip, this.clientPort)
|
||||
|
||||
if (!(await this.tcpCommunicator.connect())) {
|
||||
this.log(`Failed to connect to IP: ${ip}`, 'error')
|
||||
return false
|
||||
}
|
||||
|
||||
this.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) {
|
||||
this.log(`Failed to send file: ${filePath} to IP: ${ip}`, 'error')
|
||||
return false
|
||||
}
|
||||
|
||||
await this.tcpCommunicator.disconnect()
|
||||
return true
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
this.stopRequested = true // Signal that stop is requested
|
||||
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId)
|
||||
this.intervalId = null
|
||||
}
|
||||
|
||||
// Wait for any ongoing process to complete if busy
|
||||
while (this.isBusy) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
}
|
||||
|
||||
console.log('[BackupManager] Stopped successfully.')
|
||||
}
|
||||
|
||||
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()) // Resolve the response or null if not available
|
||||
}
|
||||
}, 100) // Check every 100 milliseconds if the response has arrived
|
||||
})
|
||||
}
|
||||
|
||||
// Unified logging function
|
||||
private log(message: string, level: 'log' | 'error' = 'log'): void {
|
||||
const prefix = '[FileSharer]'
|
||||
if (level === 'error') {
|
||||
console.error(`${prefix} ${message}`)
|
||||
} else {
|
||||
console.log(`${prefix} ${message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
import { UdpClient } from '../network/udp/udp_client'
|
||||
import { TcpCommunicator } from './tcp_communicator'
|
||||
import { operationCodes } from '../network/operation_codes'
|
||||
import { ParsedMessage } from '../network/message_handler'
|
||||
import { Database } from '../database/database'
|
||||
|
||||
export class NetworkScanner {
|
||||
private db: Database
|
||||
private readonly udpPort: number
|
||||
private readonly tcpPort: number
|
||||
private readonly okPage: string
|
||||
private readonly errorPage: string
|
||||
private readonly databaseResetPage: string
|
||||
private intervalIds: NodeJS.Timeout[] = []
|
||||
|
||||
// Flags to prevent overlapping executions
|
||||
private ucCheckBusy = false
|
||||
private ipLookupBusy = false
|
||||
private sendLoginBusy = false
|
||||
|
||||
constructor(
|
||||
pathToDatabaseFile: string,
|
||||
udpPort: number,
|
||||
tcpPort: number,
|
||||
okPage: string,
|
||||
errorPage: string,
|
||||
databaseResetPage: string,
|
||||
) {
|
||||
this.db = new Database(pathToDatabaseFile)
|
||||
this.udpPort = udpPort
|
||||
this.tcpPort = tcpPort
|
||||
this.okPage = okPage
|
||||
this.errorPage = errorPage
|
||||
this.databaseResetPage = databaseResetPage
|
||||
|
||||
// Start tasks
|
||||
this.startUCCheck()
|
||||
this.startUserIPLookup()
|
||||
this.sendAccountCheckRequest()
|
||||
}
|
||||
|
||||
// Log helper function for consistent logging format
|
||||
private log(message: string, level: 'log' | 'error' = 'log', methodName: string = ''): void {
|
||||
const prefix = `[NetworkScanner${methodName ? `.${methodName}` : ''}]`
|
||||
if (level === 'error') {
|
||||
console.error(`${prefix} ${message}`)
|
||||
} else {
|
||||
console.log(`${prefix} ${message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// UC Check Task
|
||||
private startUCCheck(interval: number = 5000): void {
|
||||
const intervalId = setInterval(async () => {
|
||||
if (this.ucCheckBusy) return
|
||||
this.ucCheckBusy = true
|
||||
|
||||
try {
|
||||
this.log('UC Check running...', 'log', 'startUCCheck')
|
||||
const udpClient = new UdpClient(this.udpPort)
|
||||
const aliveClients = await udpClient.getTargetClients(operationCodes.ARE_YOU_UC)
|
||||
const data = await this.db.read()
|
||||
const foundClient = aliveClients.length > 0
|
||||
const serverFound = data.app_config.server_found
|
||||
|
||||
if (foundClient) {
|
||||
const ipAddress = aliveClients[0].ipAddress
|
||||
|
||||
if (data.network.serverIp !== ipAddress) {
|
||||
await this.db.update((data) => {
|
||||
data.network.serverIp = ipAddress
|
||||
return data
|
||||
})
|
||||
process.send?.({ type: 'changeContent', page: this.okPage })
|
||||
}
|
||||
|
||||
if (!serverFound) {
|
||||
process.send?.({ type: 'changeContent', page: this.okPage })
|
||||
await this.db.update((data) => {
|
||||
data.app_config.server_found = true
|
||||
return data
|
||||
})
|
||||
}
|
||||
} else {
|
||||
process.send?.({ type: 'changeContent', page: this.errorPage })
|
||||
await this.db.update((data) => {
|
||||
data.app_config.server_found = false
|
||||
return data
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
this.log(`Error checking UC: ${err}`, 'error', 'startUCCheck')
|
||||
process.send?.({ type: 'changeContent', page: this.errorPage })
|
||||
} finally {
|
||||
this.ucCheckBusy = false
|
||||
}
|
||||
}, interval)
|
||||
|
||||
this.intervalIds.push(intervalId)
|
||||
}
|
||||
|
||||
// IP Lookup Task
|
||||
private startUserIPLookup(interval: number = 10000): void {
|
||||
const intervalId = setInterval(async () => {
|
||||
if (this.ipLookupBusy) return
|
||||
this.ipLookupBusy = true
|
||||
|
||||
try {
|
||||
this.log('IP Lookup running...', 'log', 'startUserIPLookup')
|
||||
const udpClient = new UdpClient(this.udpPort)
|
||||
const activeClients = await udpClient.getTargetClients(operationCodes.ARE_YOU_HUMAN)
|
||||
|
||||
// Save the filtered IPs to 'users_ip'
|
||||
await this.db.update((data) => {
|
||||
data.network.usersInLan = activeClients.map((client) => ({
|
||||
ip: client.ip,
|
||||
name: client.name,
|
||||
departmentId: client.departmentId,
|
||||
}))
|
||||
return data
|
||||
})
|
||||
} catch (err) {
|
||||
this.log(`Error during user IP lookup: ${err}`, 'error', 'startUserIPLookup')
|
||||
} finally {
|
||||
this.ipLookupBusy = false
|
||||
}
|
||||
}, interval)
|
||||
|
||||
this.intervalIds.push(intervalId)
|
||||
}
|
||||
|
||||
// Login Request Task
|
||||
private sendAccountCheckRequest(interval: number = 5000): void {
|
||||
const intervalId = setInterval(async () => {
|
||||
if (this.sendLoginBusy) return
|
||||
this.sendLoginBusy = true
|
||||
|
||||
try {
|
||||
const data = await this.db.read()
|
||||
const loggedIn = data.app_config.logged_in
|
||||
if (!loggedIn) {
|
||||
this.log('User is not logged in.', 'log', 'sendAccountCheckRequest')
|
||||
return
|
||||
}
|
||||
|
||||
const userInfo = data.app_config.user_info
|
||||
if (!userInfo || !userInfo.email) {
|
||||
this.log(
|
||||
'Email or password not found in user config.',
|
||||
'error',
|
||||
'sendAccountCheckRequest',
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const email = userInfo.email
|
||||
const serverIp = data.network.serverIp
|
||||
|
||||
if (!serverIp) {
|
||||
this.log('Server IP not found in application info.', 'error', 'sendAccountCheckRequest')
|
||||
return
|
||||
}
|
||||
|
||||
const tcpCommunicator = new TcpCommunicator(serverIp, this.tcpPort)
|
||||
|
||||
if (!(await tcpCommunicator.connect())) {
|
||||
this.log('Failed to connect to the server.', 'error', 'sendAccountCheckRequest')
|
||||
return
|
||||
}
|
||||
|
||||
const metaInfo = { email }
|
||||
if (!(await tcpCommunicator.sendMessage(operationCodes.EMAIL_VERIFICATION, metaInfo))) {
|
||||
this.log('Failed to send login request.', 'error', 'sendAccountCheckRequest')
|
||||
await tcpCommunicator.disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
const response = await this.waitForResponse(tcpCommunicator)
|
||||
if (response?.operationCode !== operationCodes.OK) {
|
||||
process.send?.({ type: 'changeContent', page: this.databaseResetPage })
|
||||
}
|
||||
} catch (err) {
|
||||
this.log(`Error during login request: ${err}`, 'error', 'sendAccountCheckRequest')
|
||||
} finally {
|
||||
this.sendLoginBusy = false
|
||||
}
|
||||
}, interval)
|
||||
|
||||
this.intervalIds.push(intervalId)
|
||||
}
|
||||
|
||||
// Helper function to wait for a response from the TCP communicator
|
||||
private async waitForResponse(tcpCommunicator: TcpCommunicator): Promise<ParsedMessage | null> {
|
||||
return new Promise((resolve) => {
|
||||
const checkInterval = setInterval(() => {
|
||||
if (tcpCommunicator.hasResponseArrived()) {
|
||||
clearInterval(checkInterval)
|
||||
resolve(tcpCommunicator.getLastResult())
|
||||
}
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
// Method to stop all intervals (for cleanup if needed)
|
||||
public stopAllIntervals(): void {
|
||||
for (const id of this.intervalIds) {
|
||||
clearInterval(id)
|
||||
}
|
||||
this.log('All intervals have been stopped.', 'log', 'stopAllIntervals')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { TcpClient } from '../network/tcp/tcp_client'
|
||||
import { ParsedMessage } from '../network/message_handler'
|
||||
|
||||
export class TcpCommunicator {
|
||||
private readonly ip: string
|
||||
private readonly port: number
|
||||
private tcpClient: TcpClient | null = null
|
||||
private lastResult: ParsedMessage | null = null
|
||||
|
||||
constructor(ip: string, port: number) {
|
||||
this.ip = ip
|
||||
this.port = port
|
||||
}
|
||||
|
||||
async connect(): Promise<boolean> {
|
||||
this.tcpClient = new TcpClient(this.port)
|
||||
this.tcpClient.openSocket(this.ip)
|
||||
return this.tcpClient.isSocketConnected()
|
||||
}
|
||||
|
||||
async sendMessage(
|
||||
operationCode: string,
|
||||
metaInfo?: { [key: string]: any },
|
||||
fileContent?: Buffer,
|
||||
): Promise<boolean> {
|
||||
if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return false
|
||||
|
||||
// Wait until the AES key is set before sending the message
|
||||
return new Promise((resolve) => {
|
||||
const idWaitForAes = setInterval(async () => {
|
||||
if (this.tcpClient?.isAesKeySet()) {
|
||||
clearInterval(idWaitForAes)
|
||||
|
||||
// Send the message once AES key is set
|
||||
const status = await this.tcpClient!.sendMessage(operationCode, metaInfo, fileContent)
|
||||
|
||||
if (status) {
|
||||
await this.waitForResponse()
|
||||
}
|
||||
|
||||
resolve(status)
|
||||
}
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
getLastResult(): ParsedMessage | null {
|
||||
const message = this.lastResult
|
||||
this.lastResult = null
|
||||
|
||||
if (global.gc) {
|
||||
global.gc()
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
|
||||
hasResponseArrived(): boolean {
|
||||
if (!this.tcpClient) return false
|
||||
return this.lastResult !== null
|
||||
}
|
||||
|
||||
private waitForResponse(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.tcpClient) {
|
||||
reject()
|
||||
}
|
||||
|
||||
// Start interval for waiting for the response
|
||||
const responseInterval = setInterval(() => {
|
||||
if (!this.tcpClient?.isSocketConnected()) {
|
||||
clearInterval(responseInterval)
|
||||
resolve()
|
||||
}
|
||||
|
||||
if (this.tcpClient?.isMessageReceived()) {
|
||||
this.lastResult = this.tcpClient.getLastResult()
|
||||
clearInterval(responseInterval) // Stop checking once we have a response
|
||||
resolve()
|
||||
}
|
||||
}, 100) // Check every 100 milliseconds
|
||||
})
|
||||
}
|
||||
|
||||
async disconnect(): Promise<boolean> {
|
||||
if (!this.tcpClient || !this.tcpClient.isSocketConnected()) return true
|
||||
this.tcpClient.closeSocket()
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import { app, BrowserWindow, dialog, shell } from 'electron'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
export class WindowManager {
|
||||
private readonly mainWindow: BrowserWindow
|
||||
private readonly pathToPagesDir: string
|
||||
private announcementWindow: BrowserWindow | null = null
|
||||
|
||||
constructor(mainWindow: BrowserWindow, pathToPagesDir: string) {
|
||||
this.pathToPagesDir = pathToPagesDir
|
||||
this.mainWindow = mainWindow
|
||||
this.log('WindowManager initialized.')
|
||||
}
|
||||
|
||||
// Logging helper function
|
||||
private log(message: string, level: 'log' | 'error' = 'log'): void {
|
||||
const prefix = '[WindowManager]'
|
||||
if (level === 'error') {
|
||||
console.error(`${prefix} ${message}`)
|
||||
} else {
|
||||
console.log(`${prefix} ${message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Show an alert dialog
|
||||
async showAlert(message: string): Promise<void> {
|
||||
if (this.mainWindow) {
|
||||
await dialog.showMessageBox(this.mainWindow, {
|
||||
type: 'info',
|
||||
title: 'Alert',
|
||||
message: message,
|
||||
buttons: ['OK'],
|
||||
})
|
||||
this.log(`Alert displayed with message: "${message}"`)
|
||||
} else {
|
||||
this.log('Main window is not available.', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// Change the content of the current window to load a new HTML file
|
||||
async changeContent(destination: string): Promise<void> {
|
||||
if (this.mainWindow) {
|
||||
try {
|
||||
const destinationPath = path.join(this.pathToPagesDir, `${destination}.html`)
|
||||
this.log(`Navigating to: ${destinationPath}`)
|
||||
|
||||
// Load the destination HTML file into the main window
|
||||
await this.mainWindow.loadFile(destinationPath)
|
||||
this.log(`Navigated to ${destination}`)
|
||||
} catch (error) {
|
||||
this.log(`Error changing content: ${error}`, 'error')
|
||||
throw error // Pass the error back to the render process
|
||||
}
|
||||
} else {
|
||||
this.log('Main window is not available.', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// New method to select a directory
|
||||
async selectDirectory(): Promise<string | undefined> {
|
||||
const result = await dialog.showOpenDialog(this.mainWindow, {
|
||||
properties: ['openDirectory'], // Only allow selecting directories
|
||||
defaultPath: app.getPath('home'),
|
||||
})
|
||||
|
||||
if (result.filePaths && result.filePaths.length > 0) {
|
||||
this.log(`Directory selected: ${result.filePaths[0]}`)
|
||||
return result.filePaths[0] // Return the selected directory path
|
||||
} else {
|
||||
this.log('No directory selected.')
|
||||
return undefined // Return undefined if no directory was selected
|
||||
}
|
||||
}
|
||||
|
||||
// Show a file in the explorer
|
||||
async showFileInExplorer(filePath: string): Promise<void> {
|
||||
if (filePath && fs.existsSync(filePath)) {
|
||||
try {
|
||||
shell.showItemInFolder(filePath)
|
||||
this.log(`Opened file explorer for: ${filePath}`)
|
||||
} catch (error: any) {
|
||||
this.log(`Error showing file in explorer: ${error.message}`, 'error')
|
||||
}
|
||||
} else {
|
||||
this.log('File path is undefined or does not exist.', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// New method to open the file explorer and choose a file
|
||||
async selectFile(): Promise<string | undefined> {
|
||||
const result = await dialog.showOpenDialog(this.mainWindow, {
|
||||
properties: ['openFile'], // Allow selecting a file
|
||||
defaultPath: app.getPath('desktop'),
|
||||
filters: [{ name: 'All Files', extensions: ['*'] }],
|
||||
})
|
||||
|
||||
if (result.filePaths && result.filePaths.length > 0) {
|
||||
this.log(`File selected: ${result.filePaths[0]}`)
|
||||
return result.filePaths[0] // Return the selected file path
|
||||
} else {
|
||||
this.log('No file selected.')
|
||||
return undefined // Return undefined if no file was selected
|
||||
}
|
||||
}
|
||||
|
||||
// Method to display an announcement in a new window
|
||||
async displayAnnouncement(): Promise<void> {
|
||||
if (this.announcementWindow) {
|
||||
this.announcementWindow.focus()
|
||||
this.log('Announcement window focused.')
|
||||
return
|
||||
}
|
||||
|
||||
const mainScreen = require('electron').screen.getPrimaryDisplay()
|
||||
const { width, height } = mainScreen.size
|
||||
|
||||
this.announcementWindow = new BrowserWindow({
|
||||
width: width / 3,
|
||||
height: height / 2,
|
||||
resizable: false,
|
||||
title: 'Announcement',
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, '..', 'main', 'preload.js'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
},
|
||||
})
|
||||
|
||||
this.announcementWindow.removeMenu()
|
||||
const announcementPath = path.join(this.pathToPagesDir, 'announcement.html')
|
||||
await this.announcementWindow.loadFile(announcementPath)
|
||||
|
||||
this.log(`Announcement window opened at: ${announcementPath}`)
|
||||
|
||||
// Handle window close
|
||||
this.announcementWindow.on('closed', () => {
|
||||
this.announcementWindow = null
|
||||
this.log('Announcement window closed.')
|
||||
})
|
||||
}
|
||||
|
||||
async closeAnnouncementWindow(): Promise<void> {
|
||||
if (this.announcementWindow) {
|
||||
this.announcementWindow.close()
|
||||
this.log('Announcement window closed by user.')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
import { fork, ChildProcess } from 'child_process'
|
||||
import path from 'path'
|
||||
import { WindowManager } from './window_manager'
|
||||
|
||||
export class WorkerManager {
|
||||
private readonly pathToWorkerDir: string
|
||||
private windowManager: WindowManager
|
||||
private workers: ChildProcess[]
|
||||
private cleanupInProgress: boolean = false
|
||||
|
||||
constructor(pathToWorkerDir: string, windowManager: WindowManager) {
|
||||
this.pathToWorkerDir = pathToWorkerDir
|
||||
this.windowManager = windowManager
|
||||
this.workers = [] // Initialize the array to store child processes
|
||||
}
|
||||
|
||||
async startNetworkScannerWorker(
|
||||
udpPort: number,
|
||||
tcpPort: number,
|
||||
okPage: string,
|
||||
errorPage: string,
|
||||
databaseResetPage: string,
|
||||
pathToDatabaseFile: string,
|
||||
): Promise<void> {
|
||||
return this.startForkedWorker('network_scanner_worker.js', {
|
||||
UDP_PORT: udpPort.toString(),
|
||||
TCP_PORT: tcpPort.toString(),
|
||||
OK_PAGE: okPage,
|
||||
ERROR_PAGE: errorPage,
|
||||
DATABASE_RESET_PAGE: databaseResetPage,
|
||||
DATABASE_FILE_PATH: pathToDatabaseFile,
|
||||
})
|
||||
}
|
||||
|
||||
async startDirectoriesWatchersWorker(pathToDatabaseFile: string): Promise<void> {
|
||||
return this.startForkedWorker('directories_watcher_worker.js', {
|
||||
DATABASE_FILE_PATH: pathToDatabaseFile,
|
||||
})
|
||||
}
|
||||
|
||||
async startServersWorker(host: string, udpPort: number, tcpPort: number): Promise<void> {
|
||||
return this.startForkedWorker('servers_worker.js', {
|
||||
HOST: host,
|
||||
USER_UDP_PORT: udpPort.toString(),
|
||||
USER_TCP_PORT: tcpPort.toString(),
|
||||
})
|
||||
}
|
||||
|
||||
async startResourceCoordinatorWorker(
|
||||
pathToDatabaseFiles: string,
|
||||
tcpPort: number,
|
||||
): Promise<void> {
|
||||
return this.startForkedWorker('resource_coordinator_worker.js', {
|
||||
DATABASE_FILE_PATH: pathToDatabaseFiles,
|
||||
TCP_PORT: tcpPort.toString(),
|
||||
})
|
||||
}
|
||||
|
||||
async startBackupRetrievalWorker(
|
||||
clientPort: number,
|
||||
destinationPath: string,
|
||||
pathToDatabaseFile: string,
|
||||
): Promise<void> {
|
||||
return this.startForkedWorker('backup_retrieval_worker.js', {
|
||||
CLIENT_PORT: clientPort.toString(),
|
||||
DESTINATION_PATH: destinationPath,
|
||||
DATABASE_FILE_PATH: pathToDatabaseFile,
|
||||
})
|
||||
}
|
||||
|
||||
async startAnnouncementWorker(
|
||||
pathToDatabaseFile: string,
|
||||
clientPort: number,
|
||||
message: string,
|
||||
): Promise<void> {
|
||||
return this.startForkedWorker('send_announcement_worker.js', {
|
||||
DATABASE_FILE_PATH: pathToDatabaseFile,
|
||||
CLIENT_PORT: clientPort.toString(),
|
||||
MESSAGE: message,
|
||||
})
|
||||
}
|
||||
|
||||
private async startForkedWorker(
|
||||
scriptName: string,
|
||||
envData: { [key: string]: string },
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const worker = fork(path.join(this.pathToWorkerDir, scriptName), {
|
||||
execArgv: ['--max-old-space-size=4096'], // Set memory limit for the forked process
|
||||
env: { ...process.env, ...envData }, // Merge environment variables
|
||||
})
|
||||
|
||||
this.workers.push(worker) // Store the worker reference
|
||||
|
||||
worker.on('message', (data: unknown) => {
|
||||
const message = data as { type: string; page?: string; message?: string } // Type casting for message
|
||||
|
||||
if (message.type === 'changeContent' && message.page) {
|
||||
this.windowManager.changeContent(message.page)
|
||||
} else if (message.type === 'showAlert' && message.message) {
|
||||
this.windowManager.showAlert(message.message)
|
||||
} else {
|
||||
console.log(`${scriptName} message:`, message)
|
||||
}
|
||||
})
|
||||
|
||||
worker.on('error', (err) => {
|
||||
console.error(`${scriptName} error:`, err)
|
||||
worker.kill()
|
||||
this.removeWorker(worker)
|
||||
reject(err)
|
||||
})
|
||||
|
||||
worker.on('exit', (code, signal) => {
|
||||
this.removeWorker(worker)
|
||||
if (code === 0) {
|
||||
console.log(`${scriptName} exited successfully`)
|
||||
resolve()
|
||||
} else if (signal) {
|
||||
console.log(`${scriptName} was killed with signal: ${signal}`)
|
||||
} else {
|
||||
console.error(`${scriptName} exited with code: ${code}`)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
closeAllWorkers(): void {
|
||||
if (this.cleanupInProgress) return // Prevent duplicate cleanup
|
||||
this.cleanupInProgress = true
|
||||
|
||||
console.log('Terminating all running workers...')
|
||||
this.workers.forEach((worker) => worker.kill())
|
||||
this.workers = []
|
||||
}
|
||||
|
||||
private removeWorker(worker: ChildProcess): void {
|
||||
const index = this.workers.indexOf(worker)
|
||||
if (index > -1) {
|
||||
this.workers.splice(index, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { Database } from '../database/database'
|
||||
import { DatabaseScheme } from '../database/schemes/database_scheme'
|
||||
import path from 'path'
|
||||
import { EncryptionKeyScheme, UserInfoScheme } from '../database/schemes/app_config_scheme'
|
||||
import {
|
||||
DirectoryInfo,
|
||||
DirectorySchemes,
|
||||
FileItemTask,
|
||||
} from '../database/schemes/local_resources_scheme'
|
||||
|
||||
const pathToDatabaseFile = path.join(__dirname, '..', 'database.json')
|
||||
|
||||
class IpcDatabaseHandler {
|
||||
private readonly db: Database
|
||||
|
||||
constructor(pathToDatabaseFile: string) {
|
||||
this.db = new Database(pathToDatabaseFile)
|
||||
}
|
||||
|
||||
async getAppType(): Promise<string> {
|
||||
const data = await this.db.read()
|
||||
return data.app_config.app_type
|
||||
}
|
||||
|
||||
async getUserInfo(): Promise<UserInfoScheme> {
|
||||
const data = await this.db.read()
|
||||
return data.app_config.user_info
|
||||
}
|
||||
|
||||
async getActiveUsers(): Promise<any> {
|
||||
const data = await this.db.read()
|
||||
return data.network.usersInLan
|
||||
}
|
||||
|
||||
async getLocalResources(): Promise<DirectorySchemes> {
|
||||
const data = await this.db.read()
|
||||
return data.local_resources.directory_schemes
|
||||
}
|
||||
|
||||
async getDirectoryInfo(id: string): Promise<DirectoryInfo> {
|
||||
const data = await this.db.read()
|
||||
const directorySchemes = data.local_resources.directory_schemes
|
||||
|
||||
switch (id) {
|
||||
case directorySchemes.backup.id: {
|
||||
return directorySchemes.backup
|
||||
}
|
||||
|
||||
case directorySchemes.department.id: {
|
||||
return directorySchemes.department
|
||||
}
|
||||
|
||||
case directorySchemes.shared.id: {
|
||||
return directorySchemes.shared
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error(`Directory with id ${id} not found`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async writeDirectoryPath(id: string, path: string): Promise<boolean> {
|
||||
try {
|
||||
await this.db.update((data) => {
|
||||
switch (id) {
|
||||
case data.local_resources.directory_schemes.backup.id: {
|
||||
data.local_resources.directory_schemes.backup.path = path
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case data.local_resources.directory_schemes.department.id: {
|
||||
data.local_resources.directory_schemes.department.path = path
|
||||
break
|
||||
}
|
||||
|
||||
case data.local_resources.directory_schemes.shared.id: {
|
||||
data.local_resources.directory_schemes.shared.path = path
|
||||
break
|
||||
}
|
||||
|
||||
default: {
|
||||
throw new Error(`Directory with id ${id} not found`)
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
})
|
||||
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async isBackupSet(): Promise<boolean> {
|
||||
const data = await this.db.read()
|
||||
return data.local_resources.directory_schemes.backup.path !== ''
|
||||
}
|
||||
|
||||
async writeUserInfo(userInfo: UserInfoScheme): Promise<boolean> {
|
||||
await this.db.update((data) => {
|
||||
data.app_config.user_info = userInfo
|
||||
return data
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async writeEncryptionKey(encryptionKey: EncryptionKeyScheme): Promise<boolean> {
|
||||
await this.db.update((data) => {
|
||||
data.app_config.encryption_key = encryptionKey
|
||||
return data
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async setLoginStatus(status: boolean): Promise<boolean> {
|
||||
await this.db.update((data) => {
|
||||
data.app_config.logged_in = status
|
||||
return data
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async addTaskToSendFileQueue(task: FileItemTask) {
|
||||
await this.db.pushToSendQueue(task)
|
||||
}
|
||||
|
||||
async resetInternalDatabase(): Promise<boolean> {
|
||||
await this.db.reset()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export const ipcDatabaseHandler = new IpcDatabaseHandler(pathToDatabaseFile)
|
||||