Merge pull request #47 from fossyy/staging
Display user storage usage on the dashboard
This commit is contained in:
102
db/database.go
102
db/database.go
@ -5,12 +5,11 @@ import (
|
||||
"fmt"
|
||||
"github.com/fossyy/filekeeper/types"
|
||||
"github.com/fossyy/filekeeper/types/models"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
gormLogger "gorm.io/gorm/logger"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type mySQLdb struct {
|
||||
@ -71,21 +70,20 @@ func NewMYSQLdb(username, password, host, port, dbName string) types.Database {
|
||||
panic("failed to connect database: " + err.Error())
|
||||
}
|
||||
|
||||
file, err := os.ReadFile("schema.sql")
|
||||
err = DB.AutoMigrate(&models.User{})
|
||||
if err != nil {
|
||||
panic("Error opening file: " + err.Error())
|
||||
panic(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
queries := strings.Split(string(file), ";")
|
||||
for _, query := range queries {
|
||||
query = strings.TrimSpace(query)
|
||||
if query == "" {
|
||||
continue
|
||||
}
|
||||
err := DB.Exec(query).Error
|
||||
err = DB.AutoMigrate(&models.File{})
|
||||
if err != nil {
|
||||
panic("Error executing query: " + err.Error())
|
||||
panic(err.Error())
|
||||
return nil
|
||||
}
|
||||
err = DB.AutoMigrate(&models.Allowance{})
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
return nil
|
||||
}
|
||||
return &mySQLdb{DB}
|
||||
}
|
||||
@ -101,6 +99,10 @@ func NewPostgresDB(username, password, host, port, dbName string, mode SSLMode)
|
||||
Logger: gormLogger.Default.LogMode(gormLogger.Silent),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic("failed to connect database: " + err.Error())
|
||||
}
|
||||
|
||||
initDB.Raw("SELECT count(*) FROM pg_database WHERE datname = ?", dbName).Scan(&count)
|
||||
if count <= 0 {
|
||||
if err := initDB.Exec("CREATE DATABASE " + dbName).Error; err != nil {
|
||||
@ -119,23 +121,21 @@ func NewPostgresDB(username, password, host, port, dbName string, mode SSLMode)
|
||||
panic("failed to connect database: " + err.Error())
|
||||
}
|
||||
|
||||
file, err := os.ReadFile("schema.sql")
|
||||
err = DB.AutoMigrate(&models.User{})
|
||||
if err != nil {
|
||||
panic("Error opening file: " + err.Error())
|
||||
panic(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
queries := strings.Split(string(file), ";")
|
||||
for _, query := range queries {
|
||||
query = strings.TrimSpace(query)
|
||||
if query == "" {
|
||||
continue
|
||||
}
|
||||
err := DB.Exec(query).Error
|
||||
err = DB.AutoMigrate(&models.File{})
|
||||
if err != nil {
|
||||
panic("Error executing query: " + err.Error())
|
||||
panic(err.Error())
|
||||
return nil
|
||||
}
|
||||
err = DB.AutoMigrate(&models.Allowance{})
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
return nil
|
||||
}
|
||||
|
||||
return &postgresDB{DB}
|
||||
}
|
||||
|
||||
@ -168,6 +168,10 @@ func (db *mySQLdb) CreateUser(user *models.User) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = db.CreateAllowance(user.UserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -200,6 +204,28 @@ func (db *mySQLdb) UpdateUserPassword(email string, password string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *mySQLdb) CreateAllowance(userID uuid.UUID) error {
|
||||
userAllowance := &models.Allowance{
|
||||
UserID: userID,
|
||||
AllowanceByte: 1024 * 1024 * 1024 * 10,
|
||||
AllowanceFile: 10,
|
||||
}
|
||||
err := db.DB.Create(userAllowance).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *mySQLdb) GetAllowance(userID uuid.UUID) (*models.Allowance, error) {
|
||||
var allowance models.Allowance
|
||||
err := db.DB.Table("allowances").Where("user_id = ?", userID).First(&allowance).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &allowance, nil
|
||||
}
|
||||
|
||||
func (db *mySQLdb) CreateFile(file *models.File) error {
|
||||
err := db.DB.Create(file).Error
|
||||
if err != nil {
|
||||
@ -279,6 +305,10 @@ func (db *postgresDB) CreateUser(user *models.User) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = db.CreateAllowance(user.UserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -311,6 +341,28 @@ func (db *postgresDB) UpdateUserPassword(email string, password string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *postgresDB) CreateAllowance(userID uuid.UUID) error {
|
||||
userAllowance := &models.Allowance{
|
||||
UserID: userID,
|
||||
AllowanceByte: 1024 * 1024 * 1024 * 10,
|
||||
AllowanceFile: 10,
|
||||
}
|
||||
err := db.DB.Create(userAllowance).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *postgresDB) GetAllowance(userID uuid.UUID) (*models.Allowance, error) {
|
||||
var allowance models.Allowance
|
||||
err := db.DB.Table("allowances").Where("user_id = $1", userID).First(&allowance).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &allowance, nil
|
||||
}
|
||||
|
||||
func (db *postgresDB) CreateFile(file *models.File) error {
|
||||
err := db.DB.Create(file).Error
|
||||
if err != nil {
|
||||
|
6
go.mod
6
go.mod
@ -3,7 +3,7 @@ module github.com/fossyy/filekeeper
|
||||
go 1.22.2
|
||||
|
||||
require (
|
||||
github.com/a-h/templ v0.2.707
|
||||
github.com/a-h/templ v0.2.778
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/joho/godotenv v1.5.1
|
||||
@ -36,8 +36,8 @@ require (
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.23.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
)
|
||||
|
12
go.sum
12
go.sum
@ -1,7 +1,7 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/a-h/templ v0.2.707 h1:T1Gkd2ugbRglZ9rYw/VBchWOSZVKmetDbBkm4YubM7U=
|
||||
github.com/a-h/templ v0.2.707/go.mod h1:5cqsugkq9IerRNucNsI4DEamdHPsoGMQy99DzydLhM8=
|
||||
github.com/a-h/templ v0.2.778 h1:VzhOuvWECrwOec4790lcLlZpP4Iptt5Q4K9aFxQmtaM=
|
||||
github.com/a-h/templ v0.2.778/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
@ -70,14 +70,14 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -2,14 +2,14 @@ package userSessionTerminateHandler
|
||||
|
||||
import (
|
||||
"github.com/fossyy/filekeeper/session"
|
||||
"github.com/fossyy/filekeeper/types"
|
||||
"github.com/fossyy/filekeeper/view/client/user"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func DELETE(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
|
||||
_, mySession, _ := session.GetSession(r)
|
||||
mySession := r.Context().Value("user").(types.User)
|
||||
otherSession := session.Get(id)
|
||||
if _, err := session.GetSessionInfo(mySession.Email, otherSession.ID); err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/fossyy/filekeeper/session"
|
||||
"github.com/fossyy/filekeeper/types"
|
||||
"github.com/fossyy/filekeeper/types/models"
|
||||
"github.com/fossyy/filekeeper/utils"
|
||||
"github.com/fossyy/filekeeper/view/client/user"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/websocket"
|
||||
@ -57,24 +58,44 @@ func GET(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
handlerWS(upgrade, userSession)
|
||||
}
|
||||
|
||||
var component templ.Component
|
||||
sessions, err := session.GetSessions(userSession.Email)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
allowance, err := app.Server.Database.GetAllowance(userSession.UserID)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
usage, err := app.Server.Service.GetUserStorageUsage(userSession.UserID.String())
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
allowanceStats := &types.Allowance{
|
||||
AllowanceByte: utils.ConvertFileSize(allowance.AllowanceByte),
|
||||
AllowanceUsedByte: utils.ConvertFileSize(usage),
|
||||
AllowanceUsedPercent: fmt.Sprintf("%.2f", float64(usage)/float64(allowance.AllowanceByte)*100),
|
||||
}
|
||||
|
||||
if err := r.URL.Query().Get("error"); err != "" {
|
||||
message, ok := errorMessages[err]
|
||||
if !ok {
|
||||
message = "Unknown error occurred. Please contact support at bagas@fossy.my.id for assistance."
|
||||
}
|
||||
|
||||
component = userView.Main("Filekeeper - User Page", userSession, sessions, types.Message{
|
||||
component = userView.Main("Filekeeper - User Page", userSession, allowanceStats, sessions, types.Message{
|
||||
Code: 0,
|
||||
Message: message,
|
||||
})
|
||||
} else {
|
||||
component = userView.Main("Filekeeper - User Page", userSession, sessions, types.Message{
|
||||
component = userView.Main("Filekeeper - User Page", userSession, allowanceStats, sessions, types.Message{
|
||||
Code: 1,
|
||||
Message: "",
|
||||
})
|
||||
|
@ -1,2 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS users (user_id UUID PRIMARY KEY NOT NULL, username VARCHAR(255) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, password TEXT NOT NULL, totp VARCHAR(255) NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS files (id UUID PRIMARY KEY NOT NULL, owner_id UUID NOT NULL, name TEXT NOT NULL, size BIGINT NOT NULL, total_chunk BIGINT NOT NULL, downloaded BIGINT NOT NULL DEFAULT 0, FOREIGN KEY (owner_id) REFERENCES users(user_id));
|
@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/fossyy/filekeeper/app"
|
||||
"github.com/fossyy/filekeeper/types"
|
||||
"github.com/fossyy/filekeeper/types/models"
|
||||
@ -26,6 +27,7 @@ func (r *Service) GetUser(ctx context.Context, email string) (*models.User, erro
|
||||
userJSON, err := app.Server.Cache.GetCache(ctx, "UserCache:"+email)
|
||||
if err == redis.Nil {
|
||||
userData, err := r.db.GetUser(email)
|
||||
fmt.Println(userData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -66,6 +68,18 @@ func (r *Service) DeleteUser(email string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Service) GetUserStorageUsage(ownerID string) (uint64, error) {
|
||||
files, err := app.Server.Database.GetFiles(ownerID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var total uint64 = 0
|
||||
for _, file := range files {
|
||||
total += file.Size
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (r *Service) GetFile(id string) (*models.File, error) {
|
||||
fileJSON, err := r.cache.GetCache(context.Background(), "FileCache:"+id)
|
||||
if err == redis.Nil {
|
||||
|
@ -3,18 +3,26 @@ package models
|
||||
import "github.com/google/uuid"
|
||||
|
||||
type User struct {
|
||||
UserID uuid.UUID `gorm:"primaryKey;not null;unique"`
|
||||
Username string `gorm:"unique;not null"`
|
||||
Email string `gorm:"unique;not null"`
|
||||
Password string `gorm:"not null"`
|
||||
Totp string `gorm:"not null"`
|
||||
UserID uuid.UUID `gorm:"type:uuid;primaryKey"`
|
||||
Username string `gorm:"type:varchar(255);unique;not null"`
|
||||
Email string `gorm:"type:varchar(255);unique;not null"`
|
||||
Password string `gorm:"type:text;not null"`
|
||||
Totp string `gorm:"type:varchar(255);not null"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
ID uuid.UUID `gorm:"primaryKey;not null;unique"`
|
||||
OwnerID uuid.UUID `gorm:"not null"`
|
||||
Name string `gorm:"not null"`
|
||||
ID uuid.UUID `gorm:"type:uuid;primaryKey"`
|
||||
OwnerID uuid.UUID `gorm:"type:uuid;not null"`
|
||||
Name string `gorm:"type:text;not null"`
|
||||
Size uint64 `gorm:"not null"`
|
||||
TotalChunk uint64 `gorm:"not null"`
|
||||
Downloaded uint64 `gorm:"not null;default=0"`
|
||||
Downloaded uint64 `gorm:"not null;default:0"`
|
||||
Owner *User `gorm:"foreignKey:OwnerID;constraint:OnDelete:CASCADE;"`
|
||||
}
|
||||
|
||||
type Allowance struct {
|
||||
UserID uuid.UUID `gorm:"type:uuid;primaryKey"`
|
||||
AllowanceByte uint64 `gorm:"not null"`
|
||||
AllowanceFile uint64 `gorm:"not null"`
|
||||
User *User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE;"`
|
||||
}
|
||||
|
@ -20,10 +20,10 @@ type User struct {
|
||||
Authenticated bool
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
Name string `json:"name"`
|
||||
Size uint64 `json:"size"`
|
||||
Chunk uint64 `json:"chunk"`
|
||||
type Allowance struct {
|
||||
AllowanceByte string
|
||||
AllowanceUsedByte string
|
||||
AllowanceUsedPercent string
|
||||
}
|
||||
|
||||
type FileData struct {
|
||||
@ -52,6 +52,9 @@ type Database interface {
|
||||
GetAllUsers() ([]models.User, error)
|
||||
UpdateUserPassword(email string, password string) error
|
||||
|
||||
CreateAllowance(userID uuid.UUID) error
|
||||
GetAllowance(userID uuid.UUID) (*models.Allowance, error)
|
||||
|
||||
CreateFile(file *models.File) error
|
||||
GetFile(fileID string) (*models.File, error)
|
||||
GetUserFile(name string, ownerID string) (*models.File, error)
|
||||
@ -72,4 +75,5 @@ type Services interface {
|
||||
DeleteUser(email string)
|
||||
GetFile(id string) (*models.File, error)
|
||||
GetUserFile(name, ownerID string) (*FileWithDetail, error)
|
||||
GetUserStorageUsage(ownerID string) (uint64, error)
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"github.com/fossyy/filekeeper/session"
|
||||
)
|
||||
|
||||
templ content(message types.Message, title string, user types.User, ListSession []*session.SessionInfo) {
|
||||
templ content(message types.Message, title string, user types.User, allowance *types.Allowance, ListSession []*session.SessionInfo) {
|
||||
@layout.BaseAuth(title){
|
||||
@layout.Navbar(user)
|
||||
<main class="container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10">
|
||||
@ -231,24 +231,19 @@ templ content(message types.Message, title string, user types.User, ListSession
|
||||
|
||||
<div class="space-y-8">
|
||||
<div class="rounded-lg border bg-card text-card-foreground shadow-sm" data-v0-t="card">
|
||||
<div class="flex flex-col space-y-1.5 p-6">
|
||||
<h3 class="whitespace-nowrap text-2xl font-semibold leading-none tracking-tight">Storage Usage
|
||||
</h3>
|
||||
<div class="flex flex-row justify-between items-center p-6">
|
||||
<h3 class="whitespace-nowrap text-2xl font-semibold leading-none tracking-tight">Storage Usage</h3>
|
||||
<svg class="w-4 h-4 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="22" x2="2" y1="12" y2="12"></line>
|
||||
<path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path>
|
||||
<line x1="6" x2="6.01" y1="16" y2="16"></line>
|
||||
<line x1="10" x2="10.01" y1="16" y2="16"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="p-6 grid gap-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<span>Used</span>
|
||||
<span>42.0GB</span>
|
||||
</div>
|
||||
<div class="text-2xl font-bold">{allowance.AllowanceUsedByte} / {allowance.AllowanceByte}</div>
|
||||
<div class="w-full bg-gray-300 rounded-full h-2.5">
|
||||
<div class="bg-gray-800 h-2.5 rounded-full" style="width: 45%"></div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span>Available</span>
|
||||
<span>6.9GB</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-300 rounded-full h-2.5">
|
||||
<div class="bg-gray-800 h-2.5 rounded-full" style="width: 100%"></div>
|
||||
<div class="bg-gray-800 h-2.5 rounded-full" id="allowanceProgress" style="width: 0%;"></div>
|
||||
</div>
|
||||
<a
|
||||
class="hover:bg-gray-200 inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-4 py-2"
|
||||
@ -291,6 +286,7 @@ templ content(message types.Message, title string, user types.User, ListSession
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
@templ.JSONScript("AllowanceUsedPercent", allowance.AllowanceUsedPercent)
|
||||
<script type="text/javascript">
|
||||
document.getElementById('currentPassword').addEventListener('input', function() {
|
||||
var validationBox = document.getElementById('validationBox');
|
||||
@ -300,6 +296,11 @@ templ content(message types.Message, title string, user types.User, ListSession
|
||||
validationBox.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
let allowanceProgress = document.getElementById(`allowanceProgress`);
|
||||
const AllowanceUsedPercent = JSON.parse(document.getElementById('AllowanceUsedPercent').textContent);
|
||||
allowanceProgress.style.width = `${AllowanceUsedPercent}%`;
|
||||
console.log(AllowanceUsedPercent)
|
||||
</script>
|
||||
<script src="/public/validatePassword.js" />
|
||||
@layout.Footer()
|
||||
@ -331,6 +332,6 @@ templ SessionTable(ListSession []*session.SessionInfo){
|
||||
</tbody>
|
||||
}
|
||||
|
||||
templ Main(title string, user types.User, ListSession []*session.SessionInfo, message types.Message) {
|
||||
@content(message, title, user, ListSession)
|
||||
templ Main(title string, user types.User, allowance *types.Allowance, ListSession []*session.SessionInfo, message types.Message) {
|
||||
@content(message, title, user, allowance, ListSession)
|
||||
}
|
Reference in New Issue
Block a user