Finished SignUp and SignIn frontend and backend

This commit is contained in:
Donald Zou 2025-06-02 19:23:04 +08:00
parent 243071d4cc
commit 0300c26952
19 changed files with 725 additions and 158 deletions

38
package-lock.json generated
View File

@ -5,6 +5,7 @@
"packages": {
"": {
"dependencies": {
"axios": "^1.9.0",
"marked": "^15.0.7",
"openai": "^4.89.0",
"pinia-plugin-persistedstate": "^4.2.0"
@ -163,6 +164,17 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.9.0",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
@ -444,6 +456,26 @@
"node": ">=8"
}
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz",
@ -998,6 +1030,12 @@
"integrity": "sha512-hkT3yDPFbs95mNCy1+7qNKC6Pro+/ibzYxtM2iqEigpf0sVw+bg4Zh9/snjsBcf990vfIsg5+1U7VyiyBb3etg==",
"license": "MIT"
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/quansync": {
"version": "0.2.10",
"resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.10.tgz",

View File

@ -1,5 +1,6 @@
{
"dependencies": {
"axios": "^1.9.0",
"marked": "^15.0.7",
"openai": "^4.89.0",
"pinia-plugin-persistedstate": "^4.2.0"

View File

@ -36,19 +36,23 @@ def createClientBlueprint(wireguardConfigurations: dict[WireguardConfiguration],
if request.method.lower() == 'options':
return ResponseObject(True)
@client.get(prefix)
def ClientIndex():
print(wireguardConfigurations.keys())
return render_template('client.html')
@client.post(f'{prefix}/api/signup')
def ClientAPI_SignUp():
data = request.json
status, msg = DashboardClients.SignUp(**data)
return ResponseObject(status, msg)
@client.post(f'{prefix}/api/signin')
def ClientAPI_SignIn():
data = request.json
status, msg = DashboardClients.SignIn(**data)
return ResponseObject(status, msg)
@client.get(prefix)
@login_required
def ClientIndex():
print(wireguardConfigurations.keys())
return render_template('client.html')
return client

View File

@ -1264,7 +1264,7 @@ WireguardConfigurations: dict[str, WireguardConfiguration] = {}
AllPeerShareLinks: PeerShareLinks = PeerShareLinks(DashboardConfig)
AllPeerJobs: PeerJobs = PeerJobs(DashboardConfig, WireguardConfigurations)
DashboardLogger: DashboardLogger = DashboardLogger(DashboardConfig)
DashboardLogger: DashboardLogger = DashboardLogger()
InitWireguardConfigurationsList(startup=True)

View File

@ -1,14 +1,23 @@
import hashlib
import uuid
import bcrypt
import pyotp
import sqlalchemy as db
from sqlalchemy.orm import DeclarativeBase, Mapped
from sqlalchemy.testing.schema import mapped_column
from .ConnectionString import ConnectionString
from .DashboardClientsTOTP import DashboardClientsTOTP
from .Utilities import ValidatePasswordStrength
from .DashboardLogger import DashboardLogger
class DashboardClients:
def __init__(self):
self.logger = DashboardLogger()
self.engine = db.create_engine(ConnectionString("wgdashboard"))
self.metadata = db.MetaData()
self.DashboardClientsTOTP = DashboardClientsTOTP()
self.dashboardClientsTable = db.Table(
'DashboardClients', self.metadata,
@ -36,8 +45,6 @@ class DashboardClients:
self.metadata.create_all(self.engine)
self.Clients = []
self.__getClients()
print('hi')
print(self.Clients)
def __getClients(self):
with self.engine.connect() as conn:
@ -49,10 +56,63 @@ class DashboardClients:
).where(
self.dashboardClientsTable.c.DeletedDate is None)
).mappings().fetchall()
def SignIn(self, Email, Password) -> tuple[bool, str]:
if not all([Email, Password]):
return False, "Please fill in all fields"
with self.engine.connect() as conn:
existingClient = conn.execute(
self.dashboardClientsTable.select().where(
self.dashboardClientsTable.c.Email == Email
)
).mappings().fetchone()
if existingClient:
checkPwd = bcrypt.checkpw(Password.encode("utf-8"), existingClient.get("Password").encode("utf-8"))
if checkPwd:
return True, self.DashboardClientsTOTP.GenerateToken(existingClient.get("ClientID"))
return False, "Email or Password is incorrect"
def SignUp(self, Email, Password, ConfirmPassword) -> tuple[bool, str] or tuple[bool, None]:
try:
if not all([Email, Password, ConfirmPassword]):
return False, "Please fill in all fields"
if Password != ConfirmPassword:
return False, "Passwords does not match"
with self.engine.connect() as conn:
existingClient = conn.execute(
self.dashboardClientsTable.select().where(
self.dashboardClientsTable.c.Email == Email
)
).mappings().fetchone()
if existingClient:
return False, "Email already signed up"
pwStrength, msg = ValidatePasswordStrength(Password)
if not pwStrength:
return pwStrength, msg
with self.engine.begin() as conn:
newClientUUID = str(uuid.uuid4())
totpKey = pyotp.random_base32()
encodePassword = Password.encode('utf-8')
conn.execute(
self.dashboardClientsTable.insert().values({
"ClientID": newClientUUID,
"Email": Email,
"Password": bcrypt.hashpw(encodePassword, bcrypt.gensalt()).decode("utf-8"),
"TotpKey": totpKey
})
)
conn.execute(
self.dashboardClientsInfoTable.insert().values({
"ClientID": newClientUUID
})
)
except Exception as e:
self.logger.log(Status="false", Message=f"Signed up failed, reason: {str(e)}")
return False, "Signed up failed."
def SignUp(self, Email, Password, ConfirmPassword) -> tuple[bool, str]:
pass
return True, None

View File

@ -0,0 +1,44 @@
import datetime
import hashlib
import uuid
import sqlalchemy as db
from .ConnectionString import ConnectionString
class DashboardClientsTOTP:
def __init__(self):
self.engine = db.create_engine(ConnectionString("wgdashboard"))
self.metadata = db.MetaData()
self.dashboardClientsTOTPTable = db.Table(
'DashboardClientsTOTPTokens', self.metadata,
db.Column("Token", db.String(500), primary_key=True, index=True),
db.Column("ClientID", db.String(500), index=True),
db.Column(
"ExpireTime", (db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP)
)
)
self.metadata.create_all(self.engine)
def GenerateToken(self, ClientID) -> str:
token = hashlib.sha512(f"{ClientID}_{datetime.datetime.now()}_{uuid.uuid4()}".encode()).hexdigest()
with self.engine.begin() as conn:
conn.execute(
self.dashboardClientsTOTPTable.update().values({
"ExpireTime": datetime.datetime.now()
}).where(
db.and_(self.dashboardClientsTOTPTable.c.ClientID == ClientID, self.dashboardClientsTOTPTable.c.ExpireTime > datetime.datetime.now())
)
)
conn.execute(
self.dashboardClientsTOTPTable.insert().values({
"Token": token,
"ClientID": ClientID,
"ExpireTime": datetime.datetime.now() + datetime.timedelta(minutes=10)
})
)
return token

View File

@ -8,13 +8,14 @@ from .ConnectionString import ConnectionString
class DashboardLogger:
def __init__(self, DashboardConfig):
def __init__(self):
self.engine = db.create_engine(ConnectionString("wgdashboard_log"))
self.metadata = db.MetaData()
self.dashboardLoggerTable = db.Table('DashboardLog', self.metadata,
db.Column('LogID', db.String(255), nullable=False, primary_key=True),
db.Column('LogDate', (db.DATETIME if DashboardConfig.GetConfig("Database", "type")[1] == 'sqlite' else db.TIMESTAMP),
db.Column('LogDate',
(db.DATETIME if 'sqlite:///' in ConnectionString("wgdashboard") else db.TIMESTAMP),
server_default=db.func.now()),
db.Column('URL', db.String(255)),
db.Column('IP', db.String(255)),

View File

@ -8,6 +8,7 @@
"name": "client",
"version": "0.0.0",
"dependencies": {
"axios": "^1.9.0",
"bootstrap": "^5.3.6",
"bootstrap-icons": "^1.13.1",
"dayjs": "^1.11.13",
@ -1553,6 +1554,23 @@
"integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==",
"license": "MIT"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.9.0",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/birpc": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.3.0.tgz",
@ -1646,6 +1664,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001720",
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz",
@ -1667,6 +1698,18 @@
],
"license": "CC-BY-4.0"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz",
@ -1777,6 +1820,29 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.161",
"resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz",
@ -1806,6 +1872,51 @@
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/esbuild": {
"version": "0.25.5",
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.5.tgz",
@ -1921,6 +2032,41 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.2.tgz",
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fs-extra": {
"version": "11.3.0",
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.0.tgz",
@ -1951,6 +2097,15 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz",
@ -1961,6 +2116,43 @@
"node": ">=6.9.0"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/get-stream": {
"version": "9.0.1",
"resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-9.0.1.tgz",
@ -1988,6 +2180,18 @@
"node": ">=4"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
@ -1995,6 +2199,45 @@
"dev": true,
"license": "ISC"
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hookable": {
"version": "5.5.3",
"resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz",
@ -2192,6 +2435,36 @@
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz",
@ -2409,6 +2682,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/rfdc": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz",

View File

@ -9,6 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.9.0",
"bootstrap": "^5.3.6",
"bootstrap-icons": "^1.13.1",
"dayjs": "^1.11.13",

View File

@ -5,13 +5,19 @@ import NotificationList from "@/components/notification/notificationList.vue";
<template>
<div data-bs-theme="dark" class="text-body bg-body w-100 h-100">
<Suspense>
<RouterView v-slot="{ Component }">
<Transition name="app" mode="out-in" type="transition" appear>
<Component :is="Component"></Component>
</Transition>
</RouterView>
</Suspense>
<div class="d-flex vh-100 vw-100 p-4 overflow-y-scroll">
<div class="m-auto p-5 bg-body-tertiary rounded-4 shadow-lg border" style="width: 700px">
<Suspense>
<RouterView v-slot="{ Component }">
<Transition name="app" mode="out-in" type="transition" appear>
<Component :is="Component"></Component>
</Transition>
</RouterView>
</Suspense>
</div>
</div>
<NotificationList></NotificationList>
</div>
</template>
@ -19,12 +25,12 @@ import NotificationList from "@/components/notification/notificationList.vue";
<style scoped>
.app-enter-active,
.app-leave-active {
transition: all 0.5s cubic-bezier(0.82, 0.58, 0.17, 1);
transition: all 0.3s cubic-bezier(0.82, 0.58, 0.17, 1);
}
.app-enter-from,
.app-leave-to{
opacity: 0;
filter: blur(5px);
transform: scale(0.95);
transform: scale(0.97);
}
</style>

View File

@ -56,4 +56,20 @@
::-webkit-scrollbar {
display: none;
}
.slide-right-enter-active,
.slide-right-leave-active {
transition: all 0.3s cubic-bezier(0.82, 0.58, 0.17, 1);
}
.slide-right-enter-from,
.slide-right-leave-to{
opacity: 0;
}
.slide-right-enter-from{
transform: translateX(-20px);
}
.slide-right-leave-to{
transform: translateX(20px);
}

View File

@ -16,7 +16,7 @@ const show = () => {
props.notificationData.show = true;
timeout = setTimeout(() => {
dismiss()
}, 50000)
}, 5000)
}
const clearTime = () => clearTimeout(timeout)
const dismiss = () => props.notificationData.show = false;
@ -57,7 +57,7 @@ onMounted(() => {
<style scoped>
.notification{
width: 100%;
word-break: break-all;
word-break: break-word;
}
@media screen and (min-width: 576px) {

View File

@ -6,10 +6,6 @@ const store = clientStore()
const notifications = computed(() => {
return store.notifications.filter(x => x.show).slice().reverse()
})
onMounted(() => {
store.newNotification("Hi!!lskadjlkasjdlkasjkldjaslkdjklasjdlkjaslkdjlkasjdlkjsalkdjlkasjdlk", "warning")
})
</script>
<template>

View File

@ -1,7 +1,8 @@
import {createWebHashHistory, createRouter} from "vue-router";
import Index from "@/views/index.vue";
import Signin from "@/views/signin.vue";
import Signup from "@/views/signup.vue";
import SignIn from "@/views/signin.vue";
import SignUp from "@/views/signup.vue";
import Totp from "@/views/totp.vue";
const router = createRouter({
history: createWebHashHistory(),
@ -13,13 +14,18 @@ const router = createRouter({
},
{
path: '/signin',
component: Signin,
component: SignIn,
name: "Sign In"
},
{
path: '/signup',
component: Signup,
component: SignUp,
name: "Sign Up"
},
{
path: '/totp',
component: Totp,
name: "Verify TOTP"
}
]
})

View File

@ -0,0 +1,5 @@
export const requestURl = (url) => {
return import.meta.env.MODE === 'development' ? url
: `${window.location.protocol}//${(window.location.host + window.location.pathname + url).replace(/\/\//g, '/')}`
}

View File

@ -1,74 +1,105 @@
<script setup>
import {reactive} from "vue";
import {computed, reactive, ref} from "vue";
import {clientStore} from "@/stores/clientStore.js";
import axios from "axios";
import {requestURl} from "@/utilities/request.js";
import {useRoute, useRouter} from "vue-router";
const loading = ref(false)
const formData = reactive({
email: "",
password: ""
Email: "",
Password: ""
});
const submit = (e) => {
const store = clientStore()
const signIn = async (e) => {
e.preventDefault();
for (let key in formData){
if (formData[key].length === 0){
break
}
if (!formFilled){
store.newNotification("Please fill in all fields", "warning")
return;
}
loading.value = true;
await axios.post(requestURl("/client/api/signin"), formData).then(res => {
let data = res.data;
if (!data.status){
store.newNotification(data.message, "danger")
loading.value = false;
}else{
}
})
}
const formFilled = computed(() => {
return Object.values(formData).find(x => !x) === undefined
})
// const router = useRouter()
const route = useRoute()
if (route.query.Email){
formData.Email = route.query.Email
}
</script>
<template>
<div class="d-flex vh-100 vw-100 p-4 overflow-y-scroll">
<div class="m-auto" style="width: 600px">
<h1>Sign In</h1>
<p>to your WGDashboard Client account</p>
<form class="mt-4 d-flex flex-column gap-3" @submit="e => submit(e)">
<div class="form-floating">
<input type="text"
required
v-model="formData.email"
name="email"
autocomplete="email"
autofocus
class="form-control rounded-3" id="email" placeholder="email">
<label for="email" class="d-flex">
<i class="bi bi-person-circle me-2"></i>
Email
</label>
</div>
<div class="form-floating">
<input type="password"
required
v-model="formData.password"
name="password"
autocomplete="current-password"
class="form-control rounded-3" id="password" placeholder="Password">
<label for="password" class="d-flex">
<i class="bi bi-key me-2"></i>
Password
</label>
</div>
<a href="#" class="text-body text-decoration-none">
<div>
<h1>Sign In</h1>
<p>to your WGDashboard Client account</p>
<form class="mt-4 d-flex flex-column gap-3" @submit="e => signIn(e)">
<div class="form-floating">
<input type="text"
required
:disabled="loading"
v-model="formData.Email"
name="email"
autocomplete="email"
autofocus
class="form-control rounded-3" id="email" placeholder="email">
<label for="email" class="d-flex">
<i class="bi bi-person-circle me-2"></i>
Email
</label>
</div>
<div class="form-floating">
<input type="password"
required
:disabled="loading"
v-model="formData.Password"
name="password"
autocomplete="current-password"
class="form-control rounded-3" id="password" placeholder="Password">
<label for="password" class="d-flex">
<i class="bi bi-key me-2"></i>
Password
</label>
</div>
<div>
<a href="#" class="text-body text-decoration-none ms-0">
Forgot Password?
</a>
<div class="d-flex">
<button class="ms-auto btn btn-primary rounded-3 btn-brand px-3 py-2">
Continue
<i class="ms-2 bi bi-arrow-right"></i>
</button>
</div>
</form>
<div>
<hr class="my-4">
<div class="d-flex align-items-center">
</div>
<button
:disabled="!formFilled"
class="btn btn-primary rounded-3 btn-brand px-3 py-2">
<Transition name="slide-right" mode="out-in">
<span v-if="!loading" class="d-block">
Continue <i class="ms-2 bi bi-arrow-right"></i>
</span>
<span v-else class="d-block">
<i class="spinner-border spinner-border-sm"></i>
</span>
</Transition>
</button>
</form>
<div>
<hr class="my-4">
<div class="d-flex align-items-center">
<span class="text-muted">
Don't have an account yet?
</span>
<RouterLink to="/signup" class="text-body text-decoration-none ms-auto fw-bold">
Sign Up
</RouterLink>
</div>
<RouterLink to="/signup" class="text-body text-decoration-none ms-auto fw-bold">
Sign Up
</RouterLink>
</div>
</div>
</div>

View File

@ -1,77 +1,146 @@
<script setup>
import {reactive} from "vue";
import {computed, onMounted, reactive, ref} from "vue";
import axios from "axios";
import {clientStore} from "@/stores/clientStore.js";
import {requestURl} from "@/utilities/request.js";
import {useRouter} from "vue-router";
const store = clientStore()
const formData = reactive({
Email: "",
Password: "",
ConfirmPassword: ""
})
const loading = ref(false)
const router = useRouter()
const signUp = async (e) => {
e.preventDefault()
if (!formFilled){
store.newNotification("Please fill in all fields", "warning")
return;
}
if (validatePassword){
loading.value = true
await axios.post(requestURl('/client/api/signup'), formData).then((res) => {
let data = res.data
if (!data.status){
store.newNotification(data.message, "danger")
loading.value = false
}else{
store.newNotification("Sign up successfully!", "success")
router.push({
path: '/signin',
query: {
Email: formData.Email
}
})
}
})
}
}
const validatePassword = computed(() => {
if (formData.Password && formData.ConfirmPassword){
return formData.Password === formData.ConfirmPassword;
}
return false
})
const formFilled = computed(() => {
return Object.values(formData).find(x => !x) === undefined
})
onMounted(() => {
document.querySelectorAll("input[type=password]").forEach(e => e.addEventListener('blur', () => {
if (formData.Password && formData.ConfirmPassword){
document.querySelectorAll("input[type=password]").forEach(x => {
if (!validatePassword.value){
x.classList.add("is-invalid")
}else {
x.classList.remove("is-invalid")
}
})
}
}))
})
</script>
<template>
<div class="d-flex vh-100 vw-100 p-4 overflow-y-scroll">
<div class="m-auto" style="width: 600px">
<h1>Sign Up</h1>
<p>to use WGDashboard Client</p>
<div class="mt-4 d-flex flex-column gap-3">
<div class="form-floating">
<input type="text"
required
v-model="formData.email"
name="email"
autocomplete="email"
autofocus
class="form-control rounded-3" id="email" placeholder="email">
<label for="email" class="d-flex">
<i class="bi bi-person-circle me-2"></i>
Email
</label>
</div>
<div class="form-floating">
<input type="password"
required
v-model="formData.password"
name="password"
autocomplete="new-password"
autofocus
class="form-control rounded-3" id="password" placeholder="password">
<label for="password" class="d-flex">
<i class="bi bi-key me-2"></i>
Password
</label>
</div>
<div class="form-floating">
<input type="password"
required
v-model="formData.password"
name="confirm_password"
autocomplete="new-password"
autofocus
class="form-control rounded-3" id="confirm_password" placeholder="confirm_password">
<label for="confirm_password" class="d-flex">
<i class="bi bi-key me-2"></i>
Confirm Password
</label>
</div>
<div class="d-flex">
<button class="ms-auto btn btn-primary rounded-3 btn-brand px-3 py-2">
Continue
<i class="ms-2 bi bi-arrow-right"></i>
</button>
<div>
<h1>Sign Up</h1>
<p>to use WGDashboard Client</p>
<form class="mt-4 d-flex flex-column gap-3" @submit="e => signUp(e)">
<div class="form-floating">
<input type="text"
:disabled="loading"
required
v-model="formData.Email"
name="email"
autocomplete="email"
autofocus
class="form-control rounded-3" id="email" placeholder="email">
<label for="email" class="d-flex">
<i class="bi bi-person-circle me-2"></i>
Email
</label>
</div>
<div class="form-floating">
<input type="password"
required
:disabled="loading"
v-model="formData.Password"
name="password"
autocomplete="new-password"
autofocus
class="form-control rounded-3" id="password" placeholder="password">
<label for="password" class="d-flex">
<i class="bi bi-key me-2"></i>
Password
</label>
</div>
<div class="form-floating">
<input type="password"
required
:disabled="loading"
v-model="formData.ConfirmPassword"
name="confirm_password"
autocomplete="new-password"
autofocus
class="form-control rounded-3" id="confirm_password" placeholder="confirm_password">
<label for="confirm_password" class="d-flex">
<i class="bi bi-key me-2"></i>
Confirm Password
</label>
<div id="validationServer03Feedback" class="invalid-feedback">
Passwords does not match
</div>
</div>
<div>
<hr class="my-4">
<div class="d-flex align-items-center">
<button
:disabled="!formFilled || !validatePassword || loading"
class=" btn btn-primary rounded-3 btn-brand px-3 py-2">
<Transition name="slide-right" mode="out-in">
<span v-if="!loading" class="d-block">
Continue <i class="ms-2 bi bi-arrow-right"></i>
</span>
<span v-else class="d-block">
<i class="spinner-border spinner-border-sm"></i>
</span>
</Transition>
</button>
</form>
<div>
<hr class="my-4">
<div class="d-flex align-items-center">
<span class="text-muted">
Already have an account?
</span>
<RouterLink to="/signin" class="text-body text-decoration-none ms-auto fw-bold">
Sign In
</RouterLink>
</div>
<RouterLink to="/signin" class="text-body text-decoration-none ms-auto fw-bold">
Sign In
</RouterLink>
</div>
</div>
</div>

View File

@ -0,0 +1,11 @@
<script setup>
</script>
<template>
</template>
<style scoped>
</style>

View File

@ -25,8 +25,7 @@ export default defineConfig({
},
server:{
proxy: {
'/api': proxy,
'/fileDownload':proxy
'/client': proxy,
},
host: '0.0.0.0'
},