B: Add search and greatly clean up UI
This commit is contained in:
parent
cf1c32208e
commit
d632f714d0
|
@ -319,6 +319,7 @@ func ScheduleBroadcastFromWebhook(c *gin.Context, body *webhooks.EventDelivery)
|
|||
UserId: *uid,
|
||||
TriggeringEvent: eventRecievedAudit.MongoId(),
|
||||
Result: result,
|
||||
CorrelationId: payload.Id,
|
||||
VendorName: models.YOUTUBE_VENDOR_NAME,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
BASE_URL="us-central1-docker.pkg.dev/pbaxter-infra/capstone-repo"
|
||||
|
||||
local-build:
|
||||
rm **/*_templ.go; templ generate -path ./templates
|
||||
rm -f templates/*.go; templ generate -path ./templates
|
||||
npx tailwindcss -i static/index.css -o dist/output.css
|
||||
GOEXPERIMENT=loopvar go build -o ./tmp/main .
|
||||
|
||||
|
|
|
@ -87,18 +87,36 @@ func eventsRecievedMetricFunction(c *gin.Context) *DashboardMetric {
|
|||
if events[biggestVendor].Count < event.Count {
|
||||
biggestVendor = index
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
p := message.NewPrinter(language.English)
|
||||
return &DashboardMetric{
|
||||
Title: "Events Recieved",
|
||||
PrimaryValue: p.Sprintf("%d", totalEvents),
|
||||
SecondaryValue: p.Sprintf("Most events came from: %s", events[biggestVendor].Name),
|
||||
Subtitle: "thats a lot of events",
|
||||
SecondaryValue: "",
|
||||
Subtitle: p.Sprintf("Most events came from: %s", events[biggestVendor].Name),
|
||||
}
|
||||
}
|
||||
|
||||
func streamsScheduledMetricFunction(c *gin.Context) *DashboardMetric {
|
||||
return defaultMetricFunction(c)
|
||||
user := getUserFromContext(c)
|
||||
|
||||
events, err := mongo.AggregateBroadcastReport(user.Id)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Failed to find broadcast report for user: %s", user.Id.Hex())
|
||||
return defaultMetricFunction(c)
|
||||
}
|
||||
|
||||
totalEvents := 0
|
||||
for _, event := range events {
|
||||
totalEvents += event.Count
|
||||
}
|
||||
|
||||
p := message.NewPrinter(language.English)
|
||||
return &DashboardMetric{
|
||||
Title: "Broadcasts scheduled",
|
||||
PrimaryValue: p.Sprintf("%d", totalEvents),
|
||||
SecondaryValue: "",
|
||||
Subtitle: "Scheduled to youtube",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ func BuildRouter(r *gin.Engine) {
|
|||
|
||||
r.Use(cors.Default())
|
||||
|
||||
r.Static("/static", "/var/capstone/dist")
|
||||
r.Static("/static", "./dist")
|
||||
//mainpage
|
||||
r.GET("/", AuthMiddleware(false), LandingPage)
|
||||
|
||||
|
@ -51,6 +51,11 @@ func BuildRouter(r *gin.Engine) {
|
|||
//Dashboard Components
|
||||
dashboardComponents := dashboard.Group("/components")
|
||||
dashboardComponents.GET("/metric_card", GetMetricCard)
|
||||
//Dashboard Events
|
||||
dashboardEvents := dashboard.Group("/events")
|
||||
dashboardEvents.GET("", EventsPage)
|
||||
dashboardEventComponents := dashboardEvents.Group("/components")
|
||||
dashboardEventComponents.GET("/table_data", GetTableComponent)
|
||||
|
||||
//Vendor stuff
|
||||
vendor := r.Group("/vendor")
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
package controllers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/templates"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var (
|
||||
eventsTableMap = map[string]eventsTableFunc{"default": defaultTableData, "events_for_user": eventsForUserTableData, "actions_for_user": actionsForUserTableData}
|
||||
)
|
||||
|
||||
type eventsTableFunc func(c *gin.Context) templates.TableData
|
||||
|
||||
func GetTableComponent(c *gin.Context) {
|
||||
user := getUserFromContext(c)
|
||||
if user == nil {
|
||||
log.Warnf("Could not find user in context. Trying to redner Action form")
|
||||
badRequest(c, "No user available in context")
|
||||
return
|
||||
}
|
||||
|
||||
if table, ok := c.GetQuery("table_name"); ok {
|
||||
if tableFunc, mok := eventsTableMap[table]; mok {
|
||||
renderEventTable(c, table, &tableFunc)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
//send default metric function
|
||||
log.Warn("Failed to find eventsTableFunc")
|
||||
defaultFunc := eventsTableMap["default"]
|
||||
renderEventTable(c, "default", &defaultFunc)
|
||||
}
|
||||
|
||||
func defaultTableData(c *gin.Context) templates.TableData {
|
||||
return [][]string{{"id", "col 1", "col 2"}, {"row data", "item data", "stuff"}}
|
||||
}
|
||||
|
||||
func renderEventTable(c *gin.Context, title string, tableFunc *eventsTableFunc) {
|
||||
tableData := (*tableFunc)(c)
|
||||
renderTempl(c, templates.EventTableData(tableData, title))
|
||||
}
|
||||
|
||||
func eventsForUserTableData(c *gin.Context) templates.TableData {
|
||||
//User can't be nil because we check before we get here
|
||||
user := getUserFromContext(c)
|
||||
|
||||
events, err := mongo.FindEventsRecievedByUserId(user.Id)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Failed to find events for user: %s to load table data", user.Id.Hex())
|
||||
return defaultTableData(c)
|
||||
}
|
||||
|
||||
//check for filter
|
||||
filter, filter_exists := c.GetQuery("filter")
|
||||
|
||||
table := make([][]string, len(events)+1)
|
||||
index := 1
|
||||
for _, event := range events {
|
||||
arr := []string{event.CreatedAt.Format(time.Stamp), strings.ToUpper(event.VendorName), event.VendorId, event.Type}
|
||||
|
||||
if filter_exists {
|
||||
//if the filter exists loop through the row. Check if anything meets the filter
|
||||
pass := false
|
||||
for _, item := range arr {
|
||||
//if we already have a match short circuit. If we don't we can potentially flip from false -> true
|
||||
pass = pass || strings.Contains(item, filter)
|
||||
}
|
||||
//If we did not find a matching item continue
|
||||
if !pass {
|
||||
continue
|
||||
}
|
||||
}
|
||||
//We either had no filter or passed the filter check. Add to the pool
|
||||
table[index] = arr
|
||||
index += 1
|
||||
table[0] = []string{"Timestamp", "Vendor", "Id", "Event Type"}
|
||||
}
|
||||
|
||||
return table[0:index]
|
||||
}
|
||||
|
||||
func actionsForUserTableData(c *gin.Context) templates.TableData {
|
||||
//User can't be nil because we check before we get here
|
||||
user := getUserFromContext(c)
|
||||
|
||||
actions, err := mongo.FindActionTakenByUserId(user.Id)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("Failed to find actions for user: %s to load table data", user.Id.Hex())
|
||||
return defaultTableData(c)
|
||||
}
|
||||
|
||||
|
||||
//check for filter
|
||||
filter, filter_exists := c.GetQuery("filter")
|
||||
index := 1
|
||||
table := make([][]string, len(actions)+1)
|
||||
for _, action := range actions {
|
||||
arr := []string{action.CreatedAt.Format(time.RFC1123), action.VendorName, action.CorrelationId, action.Result}
|
||||
if filter_exists {
|
||||
//if the filter exists loop through the row. Check if anything meets the filter
|
||||
pass := false
|
||||
for _, item := range arr {
|
||||
//if we already have a match short circuit. If we don't we can potentially flip from false -> true
|
||||
pass = pass || strings.Contains(item, filter)
|
||||
}
|
||||
//If we did not find a matching item continue
|
||||
if !pass {
|
||||
continue
|
||||
}
|
||||
}
|
||||
table[index] = []string{action.CreatedAt.Format(time.RFC1123), action.VendorName, action.CorrelationId, action.Result}
|
||||
index += 1
|
||||
}
|
||||
table[0] = []string{"Timestamp", "Vendor", "Id", "Result"}
|
||||
|
||||
return table[0:index]
|
||||
}
|
|
@ -70,3 +70,15 @@ func DashboardPage(c *gin.Context) {
|
|||
|
||||
renderTempl(c, templates.DashboardPage(user, vendors, actions))
|
||||
}
|
||||
|
||||
func EventsPage(c *gin.Context) {
|
||||
user := getUserFromContext(c)
|
||||
|
||||
if user == nil {
|
||||
log.Error("No user found in context")
|
||||
serverError(c, "No user found in context")
|
||||
return
|
||||
}
|
||||
|
||||
renderTempl(c, templates.EventsPage(user))
|
||||
}
|
||||
|
|
|
@ -96,17 +96,71 @@ func (db *DB) FindEventsRecievedByUserId(userId primitive.ObjectID) ([]models.Ev
|
|||
return events, nil
|
||||
}
|
||||
|
||||
func (db *DB) FindActionTakenByUserId(userId primitive.ObjectID) ([]models.ActionTaken, error) {
|
||||
conf := config.Config()
|
||||
opts := options.Find()
|
||||
res, err := db.client.Database(conf.Mongo.EntDb).Collection(conf.Mongo.EntCol).Find(context.Background(), bson.M{"user_id": userId, "obj_info.ent": models.ACTION_TAKEN_TYPE}, opts)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := []models.ActionTaken{}
|
||||
err = res.All(context.Background(), &events)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
type VendorEventReport struct {
|
||||
Count int `bson:"count"`
|
||||
Name string `bson:"_id"`
|
||||
}
|
||||
|
||||
func (db *DB) AggregateBroadcastReport(userId primitive.ObjectID) ([]VendorEventReport, error) {
|
||||
conf := config.Config()
|
||||
opts := options.Aggregate().SetAllowDiskUse(false)
|
||||
|
||||
aggregation := bson.A{
|
||||
bson.D{{Key: "$match", Value: bson.D{{Key: "obj_info.ent", Value: models.ACTION_TAKEN_TYPE}, {Key: "result", Value: "Updated Broadcast"}}}},
|
||||
bson.D{
|
||||
{Key: "$group",
|
||||
Value: bson.D{
|
||||
{Key: "_id", Value: nil},
|
||||
{Key: "count", Value: bson.D{{Key: "$sum", Value: 1}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
res, err := db.client.Database(conf.Mongo.EntDb).Collection(conf.Mongo.EntCol).Aggregate(context.Background(), aggregation, opts)
|
||||
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := []VendorEventReport{}
|
||||
err = res.All(context.Background(), &events)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func (db *DB) AggregateVendorEventReport(userId primitive.ObjectID) ([]VendorEventReport, error) {
|
||||
conf := config.Config()
|
||||
opts := options.Aggregate().SetAllowDiskUse(false)
|
||||
|
||||
aggregation := bson.A{
|
||||
bson.D{{Key: "$match", Value: bson.D{{Key: "obj_info.ent", Value: "audit_event_recieved"}}}},
|
||||
bson.D{{Key: "$match", Value: bson.D{{Key: "obj_info.ent", Value: models.EVENT_RECIEVED_TYPE}}}},
|
||||
bson.D{
|
||||
{Key: "$group",
|
||||
Value: bson.D{
|
||||
|
|
|
@ -17,7 +17,7 @@ type EventRecieved struct {
|
|||
Id primitive.ObjectID `bson:"_id,omitempty"`
|
||||
UserId primitive.ObjectID `bson:"user_id,omitempty"` //what user is this associated too
|
||||
VendorName string `bson:"vendor_name,omitempty"` //Vendor name of who sent us the event
|
||||
VendorId string `bson:"vendor_id:omitempty"`
|
||||
VendorId string `bson:"vendor_id,omitempty"`
|
||||
Type string `bson:"type,omitempty"` //type of event
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ func (obj *EventRecieved) UpdateObjectInfo() {
|
|||
now := time.Now()
|
||||
if obj.CommonFields == nil {
|
||||
obj.CommonFields = new(CommonFields)
|
||||
obj.EntityType = EVENT_RECIEVED_TYPE
|
||||
obj.EntityType = ACTION_TAKEN_TYPE
|
||||
obj.CreatedAt = now
|
||||
}
|
||||
obj.UpdatedAt = now
|
||||
|
@ -47,6 +47,7 @@ type ActionTaken struct {
|
|||
UserId primitive.ObjectID `bson:"user_id,omitempty"` //what user is this associated too
|
||||
TriggeringEvent primitive.ObjectID `bson:"triggering_event,omitempty"` //what triggered this action to be taken
|
||||
Result string `bson:"result,omitempty"` //list of entities effected or created from action
|
||||
CorrelationId string `bson:"correlation_id,omitempty"` //list of entities effected or created from action
|
||||
VendorName string `bson:"vendor_name,omitempty"` //Vendor name that the action was taken against
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
|
@ -36,11 +36,11 @@ templ DashboardPage(user *models.User, vendorAccounts []models.VendorAccount, ac
|
|||
tabindex="-1"
|
||||
class="transition-all hidden"
|
||||
></div>
|
||||
<div id="root">
|
||||
<div id="root" class="h-screen overflow-scroll">
|
||||
@DashboardNav(user)
|
||||
@DashboardContent(user, vendorAccounts, actionMappings)
|
||||
@Footer()
|
||||
</div>
|
||||
@DashboardFooter()
|
||||
</body>
|
||||
@DashboardScript()
|
||||
@toggleDropdown()
|
||||
|
@ -104,7 +104,9 @@ templ DashboardNav(user *models.User) {
|
|||
</div>
|
||||
</form>
|
||||
<ul class="md:flex-col md:min-w-full flex flex-col list-none">
|
||||
@DashboardNavItem("fa-tv", "Dashboard", "#", true)
|
||||
@DashboardNavItem("fa-tv", "Dashboard", "/dashboard", true)
|
||||
@DashboardNavItem("fa-chart-bar", "Events", "/dashboard/events", true)
|
||||
<hr/>
|
||||
@DashboardNavItem("fa-newspaper", "Home Page", "/", true)
|
||||
@DashboardNavItem("fa-user-circle", "Profile (SOON)", "#", false)
|
||||
</ul>
|
||||
|
@ -117,7 +119,7 @@ templ DashboardNavItem(icon, name, link string, enabled bool) {
|
|||
<li class="items-center">
|
||||
<a
|
||||
if enabled {
|
||||
class="text-pink-500 hover:text-pink-600 text-xs uppercase py-3 font-bold block"
|
||||
class="text-blue-500 hover:text-blue-600 text-xs uppercase py-3 font-bold block"
|
||||
href={ templ.URL(link) }
|
||||
} else {
|
||||
class="text-blueGray-300 text-xs uppercase py-3 font-bold block"
|
||||
|
@ -131,8 +133,7 @@ templ DashboardNavItem(icon, name, link string, enabled bool) {
|
|||
} else {
|
||||
class={ fmt.Sprintf("fas %s text-blueGray-300 mr-2 text-sm", icon) }
|
||||
}
|
||||
></i>
|
||||
{ name }
|
||||
></i>{ name }
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
|
@ -151,7 +152,7 @@ templ DashboardContentNav(user *models.User) {
|
|||
href="./index.html"
|
||||
>Capstone</a>
|
||||
<form
|
||||
class="md:flex hidden flex-row flex-wrap items-center lg:ml-auto mr-3"
|
||||
class="hidden flex-row flex-wrap items-center lg:ml-auto mr-3"
|
||||
>
|
||||
<div class="relative flex w-full flex-wrap items-stretch">
|
||||
<span
|
||||
|
@ -165,9 +166,9 @@ templ DashboardContentNav(user *models.User) {
|
|||
</div>
|
||||
</form>
|
||||
<ul
|
||||
class="flex-col md:flex-row list-none items-center hidden md:flex"
|
||||
class="hidden flex-col md:flex-row list-none items-center hidden md:flex"
|
||||
>
|
||||
<a class="text-blueGray-500 block" href="#pablo" onclick="openDropdown(event,'user-dropdown')">
|
||||
<a class="hidden text-blueGray-500 block" href="#pablo" onclick="openDropdown(event,'user-dropdown')">
|
||||
<div class="items-center flex">
|
||||
<span
|
||||
class="w-12 h-12 text-sm text-white bg-blueGray-200 inline-flex items-center justify-center rounded-full"
|
||||
|
@ -207,7 +208,36 @@ templ DashboardContentNav(user *models.User) {
|
|||
}
|
||||
|
||||
templ DashboardCardLoader(kind string) {
|
||||
<div class="w-full lg:w-6/12 xl:w-6/12 px-4" hx-get={ fmt.Sprintf("/dashboard/components/metric_card?metric=%s", kind) } hx-trigger="load" hx-swap="outerHTML"></div>
|
||||
<div class="w-full lg:w-6/12 xl:w-6/12 px-4" hx-get={ fmt.Sprintf("/dashboard/components/metric_card?metric=%s", kind) } hx-trigger="load" hx-swap="outerHTML">
|
||||
<div class="relative flex flex-col min-w-0 break-words bg-white rounded mb-6 xl:mb-0 shadow-lg opacity-50">
|
||||
<div class="flex-auto p-4">
|
||||
<div class="flex flex-wrap">
|
||||
<div class="relative w-full pr-4 max-w-full flex-grow flex-1">
|
||||
<h5 class="text-blueGray-400 uppercase font-bold text-xs">
|
||||
---
|
||||
</h5>
|
||||
<span class="font-semibold text-xl text-blueGray-700">
|
||||
---
|
||||
</span>
|
||||
</div>
|
||||
<div class="relative w-auto pl-4 flex-initial">
|
||||
<div class="text-white p-3 text-center inline-flex items-center justify-center w-12 h-12 shadow-lg rounded-full bg-blue-300">
|
||||
<i class="far fa-chart-bar"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-blueGray-400 mt-4">
|
||||
<span class="text-emerald-500 mr-2">
|
||||
---
|
||||
</span>
|
||||
<span class="whitespace-nowrap">
|
||||
---
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
@spinnerCentered()
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ DashboardCard(title, primaryVal, secondaryVal, subtitle string) {
|
||||
|
@ -224,14 +254,14 @@ templ DashboardCard(title, primaryVal, secondaryVal, subtitle string) {
|
|||
</span>
|
||||
</div>
|
||||
<div class="relative w-auto pl-4 flex-initial">
|
||||
<div class="text-white p-3 text-center inline-flex items-center justify-center w-12 h-12 shadow-lg rounded-full bg-red-500">
|
||||
<div class="text-white p-3 text-center inline-flex items-center justify-center w-12 h-12 shadow-lg rounded-full bg-blue-300">
|
||||
<i class="far fa-chart-bar"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-blueGray-400 mt-4">
|
||||
<span class="text-emerald-500 mr-2">
|
||||
<i class="fas fa-arrow-up"></i> { secondaryVal }
|
||||
{ secondaryVal }
|
||||
</span>
|
||||
<span class="whitespace-nowrap">
|
||||
{ subtitle }
|
||||
|
@ -397,7 +427,7 @@ templ DashboardActionDropDown() {
|
|||
hx-get="/dashboard/forms/addAction"
|
||||
hx-target="#add-action-modal"
|
||||
hx-swap="outerHTML"
|
||||
class="bg-pink-500 text-white active:bg-pink-600 font-bold uppercase text-sm px-6 py-3 rounded shadow hover:shadow-lg outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150"
|
||||
class="bg-blue-500 text-white active:bg-blue-600 font-bold uppercase text-sm px-6 py-3 rounded shadow hover:shadow-lg outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150"
|
||||
type="button"
|
||||
>
|
||||
Add Action
|
||||
|
@ -475,10 +505,10 @@ templ DashboardActionsWidget(actions []models.ActionMapping) {
|
|||
|
||||
templ DashboardContent(user *models.User, vendorAccounts []models.VendorAccount, actions []models.ActionMapping) {
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div class="relative md:ml-64 bg-blueGray-50">
|
||||
<div class="relative md:ml-64 bg-blueGray-50 h-full">
|
||||
@DashboardContentNav(user)
|
||||
<!-- Header -->
|
||||
<div class="relative bg-pink-600 md:pt-32 pb-32 pt-12">
|
||||
<div class="relative bg-blue-600 md:pt-32 pb-32 pt-12">
|
||||
<div class="px-4 md:px-10 mx-auto w-full">
|
||||
<div>
|
||||
<!-- Card stats -->
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
package templates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.preston-baxter.com/Preston_PLB/capstone/frontend-service/db/models"
|
||||
)
|
||||
|
||||
type TableData [][]string
|
||||
|
||||
templ EventsPage(user *models.User) {
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
@Head("Events")
|
||||
<body class="text-blueGray-700 antialiased">
|
||||
<div
|
||||
id="add-action-modal"
|
||||
aria-hidden="false"
|
||||
tabindex="-1"
|
||||
class="transition-all hidden"
|
||||
></div>
|
||||
<div id="root" class="h-screen overflow-scroll">
|
||||
@DashboardNav(user)
|
||||
@EventContent(user)
|
||||
</div>
|
||||
@DashboardFooter()
|
||||
</body>
|
||||
@DashboardScript()
|
||||
@toggleDropdown()
|
||||
@updateSearchScript()
|
||||
</html>
|
||||
}
|
||||
|
||||
var sampleData = [][]string{{"head 1", "head 2"}, {"row 1", "row 1"}, {"row 2", "row 2"}}
|
||||
var blankData = [][]string{{"head 1", "head 2"}}
|
||||
|
||||
templ EventContent(user *models.User) {
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div class="relative md:ml-64 bg-blueGray-50">
|
||||
@DashboardContentNav(user)
|
||||
<!-- Header -->
|
||||
<div class="relative bg-blue-600 md:pt-32 pb-32 pt-12">
|
||||
<div class="px-4 md:px-10 mx-auto w-1/4 h-3/4"></div>
|
||||
<div class="px-4 md:px-10 mx-auto w-2/4 h-3/4">
|
||||
<div class="relative flex flex-col min-w-0 break-words bg-white rounded mb-6 xl:mb-0 shadow-lg">
|
||||
<div class="flex-auto p-4">
|
||||
<div
|
||||
class="flex-row flex-wrap items-center lg:ml-auto mr-3"
|
||||
>
|
||||
<div class="relative flex w-full flex-wrap items-stretch">
|
||||
<span
|
||||
class="z-10 h-full leading-snug font-normal text-center text-blueGray-300 absolute bg-transparent rounded text-base items-center justify-center w-8 pl-3 py-3"
|
||||
><i class="fas fa-search"></i></span>
|
||||
<input
|
||||
id="search_bar"
|
||||
type="text"
|
||||
onkeyup="onSearchKeyUp()"
|
||||
placeholder="Search here..."
|
||||
class="border-0 px-3 py-3 placeholder-blueGray-300 text-blueGray-600 relative bg-white rounded text-sm shadow outline-none focus:outline-none focus:ring w-full pl-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 md:px-10 mx-auto w-1/4 h-3/4"></div>
|
||||
</div>
|
||||
<div class="px-4 md:px-10 mx-auto w-full -m-24">
|
||||
<div class="flex flex-wrap">
|
||||
<div class="w-full xl:w-6/12 mb-12 xl:mb-0 px-4">
|
||||
@EventTableWidget("Events", "events_for_user")
|
||||
</div>
|
||||
<div class="w-full xl:w-6/12 mb-12 xl:mb-0 px-4">
|
||||
@EventTableWidget("Actions", "actions_for_user")
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ EventTableWidget(title, table_name string) {
|
||||
<div class="relative flex flex-col min-w-0 break-words bg-white w-full mb-6 shadow-lg rounded">
|
||||
<div class="rounded-t mb-0 px-4 py-3 border-0">
|
||||
<div class="flex flex-wrap items-center">
|
||||
<div class="relative w-full px-4 max-w-full flex-grow flex-1">
|
||||
<h3 class="font-semibold text-base text-blueGray-700">
|
||||
{ title }
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="block w-full overflow-x-auto">
|
||||
<!-- Projects table -->
|
||||
@EventTableDataLoader(table_name)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ EventTableDataLoader(table_name string) {
|
||||
<div class="relative" hx-get={ fmt.Sprintf("/dashboard/events/components/table_data?table_name=%s", table_name) } hx-swap="outerHTML" hx-trigger="load">
|
||||
@EventTableDataLazy(table_name)
|
||||
@spinnerCentered()
|
||||
</div>
|
||||
}
|
||||
|
||||
//hx-get={ fmt.Sprintf("/dashboard/events/components/table_data?table_name=%s", table_name) }
|
||||
|
||||
templ EventTableDataLazy(table_name string) {
|
||||
<table class="items-center opacity-50 w-full bg-transparent border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 bg-blueGray-50 opacity-50 text-blueGray-500 align-middle border border-solid border-blueGray-100 py-3 text-xs uppercase border-l-0 border-r-0 whitespace-nowrap font-semibold text-left">
|
||||
Lorem Ipsum
|
||||
</th>
|
||||
<th class="px-6 bg-blueGray-50 opacity-50 text-blueGray-500 align-middle border border-solid border-blueGray-100 py-3 text-xs uppercase border-l-0 border-r-0 whitespace-nowrap font-semibold text-left">
|
||||
Lorem Ipsum
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th class="border-t-0 px-6 align-middle opacity-50 border-l-0 border-r-0 text-xs whitespace-nowrap p-4 text-left">
|
||||
No Data Available
|
||||
</th>
|
||||
<th class="border-t-0 px-6 align-middle opacity-50 border-l-0 border-r-0 text-xs whitespace-nowrap p-4 text-left">
|
||||
No Data Available
|
||||
</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
templ EventTableData(data TableData, table_name string) {
|
||||
<table class="items-center w-full bg-transparent border-collapse" hx-get={ fmt.Sprintf("/dashboard/events/components/table_data?table_name=%s", table_name) } hx-params="*" hx-trigger="search delay:200ms from:body">
|
||||
<thead>
|
||||
<tr>
|
||||
for _, header := range data[0] {
|
||||
<th class="px-6 bg-blueGray-50 text-blueGray-500 align-middle border border-solid border-blueGray-100 py-3 text-xs uppercase border-l-0 border-r-0 whitespace-nowrap font-semibold text-left">
|
||||
{ header }
|
||||
</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
if len(data) <= 1 {
|
||||
<tr>
|
||||
<th class="border-t-0 px-6 align-middle border-l-0 border-r-0 text-xs whitespace-nowrap p-4 text-left">
|
||||
No Data Available
|
||||
</th>
|
||||
</tr>
|
||||
} else {
|
||||
for _, row := range data[1:] {
|
||||
<tr>
|
||||
for _, item := range row {
|
||||
<th class="border-t-0 px-6 align-middle border-l-0 border-r-0 text-xs whitespace-nowrap p-4 text-left">
|
||||
{ item }
|
||||
</th>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
|
||||
templ updateSearchScript() {
|
||||
<script>
|
||||
const searchEvent = new Event("search");
|
||||
|
||||
function onSearchKeyUp() {
|
||||
console.log("keyup")
|
||||
document.body.dispatchEvent(searchEvent);
|
||||
}
|
||||
|
||||
document.body.addEventListener('htmx:configRequest', function(evt) {
|
||||
var query = document.getElementById("search_bar").value;
|
||||
evt.detail.parameters['filter'] = query // add a new parameter into the request
|
||||
});
|
||||
</script>
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package templates
|
||||
|
||||
templ Footer() {
|
||||
<footer class="absolute w-full bottom-0 bg-gray-900 pb-6">
|
||||
templ LandingFooter() {
|
||||
<footer class="relative w-full bottom-0 bg-gray-900 pb-6">
|
||||
<div class="container mx-auto px-4">
|
||||
<hr class="mb-6 border-b-1 border-gray-700"/>
|
||||
<div
|
||||
|
@ -32,11 +32,83 @@ templ Footer() {
|
|||
class="text-white hover:text-gray-400 text-sm font-semibold block py-1 px-3"
|
||||
>About Us</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
}
|
||||
|
||||
templ DashboardFooter() {
|
||||
<footer class="absolute w-full bottom-0 bg-gray-900 pb-6">
|
||||
<div class="md:ml-64 md:mr-16 px-4">
|
||||
<hr class="mb-6 border-b-1 border-gray-700"/>
|
||||
<div
|
||||
class="flex flex-wrap items-center md:justify-end justify-center"
|
||||
>
|
||||
<div class="w-full md:w-6/12 px-4">
|
||||
<div class="text-sm text-white font-semibold py-1">
|
||||
Copyright © 2023
|
||||
<a
|
||||
href="https://git.preston-baxter.com"
|
||||
class="text-white hover:text-gray-400 text-sm font-semibold py-1"
|
||||
>Preston Baxter</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full md:w-6/12 px-4">
|
||||
<ul
|
||||
class="flex flex-wrap list-none md:justify-end justify-center"
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/creativetimofficial/argon-design-system/blob/master/LICENSE.md"
|
||||
href="https://git.preston-baxter.com"
|
||||
class="text-white hover:text-gray-400 text-sm font-semibold block py-1 px-3"
|
||||
>MIT License</a>
|
||||
>Preston Baxter</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/about-us"
|
||||
class="text-white hover:text-gray-400 text-sm font-semibold block py-1 px-3"
|
||||
>About Us</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
}
|
||||
|
||||
templ LoginFooter() {
|
||||
<footer class="absolute w-full bottom-0 bg-gray-900 pb-6">
|
||||
<div class="md:mx-16 px-4">
|
||||
<hr class="mb-6 border-b-1 border-gray-700"/>
|
||||
<div
|
||||
class="flex flex-wrap items-center md:justify-end justify-center"
|
||||
>
|
||||
<div class="w-full md:w-6/12 px-4">
|
||||
<div class="text-sm text-white font-semibold py-1">
|
||||
Copyright © 2023
|
||||
<a
|
||||
href="https://git.preston-baxter.com"
|
||||
class="text-white hover:text-gray-400 text-sm font-semibold py-1"
|
||||
>Preston Baxter</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full md:w-6/12 px-4">
|
||||
<ul
|
||||
class="flex flex-wrap list-none md:justify-end justify-center"
|
||||
>
|
||||
<li>
|
||||
<a
|
||||
href="https://git.preston-baxter.com"
|
||||
class="text-white hover:text-gray-400 text-sm font-semibold block py-1 px-3"
|
||||
>Preston Baxter</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/about-us"
|
||||
class="text-white hover:text-gray-400 text-sm font-semibold block py-1 px-3"
|
||||
>About Us</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -127,7 +127,7 @@ templ LandingContent() {
|
|||
</div>
|
||||
<div class="w-full md:w-4/12 px-4 mr-auto ml-auto">
|
||||
<div
|
||||
class="relative flex flex-col min-w-0 break-words bg-white w-full mb-6 shadow-lg rounded-lg bg-pink-600"
|
||||
class="relative flex flex-col min-w-0 break-words bg-white w-full mb-6 shadow-lg rounded-lg bg-blue-600"
|
||||
>
|
||||
<img
|
||||
alt="..."
|
||||
|
@ -142,7 +142,7 @@ templ LandingContent() {
|
|||
class="absolute left-0 w-full block"
|
||||
style="height: 95px; top: -94px;"
|
||||
>
|
||||
<polygon points="-30,95 583,95 583,65" class="text-pink-600 fill-current"></polygon>
|
||||
<polygon points="-30,95 583,95 583,65" class="text-blue-600 fill-current"></polygon>
|
||||
</svg>
|
||||
<h4 class="text-xl font-bold text-white">
|
||||
Top Notch Services
|
||||
|
@ -187,7 +187,7 @@ templ LandingContent() {
|
|||
<div class="w-full md:w-5/12 ml-auto mr-auto px-4">
|
||||
<div class="md:pr-12">
|
||||
<div
|
||||
class="text-pink-600 p-3 text-center inline-flex items-center justify-center w-16 h-16 mb-6 shadow-lg rounded-full bg-pink-300"
|
||||
class="text-blue-600 p-3 text-center inline-flex items-center justify-center w-16 h-16 mb-6 shadow-lg rounded-full bg-pink-300"
|
||||
>
|
||||
<i class="fas fa-rocket text-xl"></i>
|
||||
</div>
|
||||
|
@ -202,7 +202,7 @@ templ LandingContent() {
|
|||
<div class="flex items-center">
|
||||
<div>
|
||||
<span
|
||||
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded-full text-pink-600 bg-pink-200 mr-3"
|
||||
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded-full text-blue-600 bg-pink-200 mr-3"
|
||||
>
|
||||
<i
|
||||
class="fas fa-fingerprint"
|
||||
|
@ -220,7 +220,7 @@ templ LandingContent() {
|
|||
<div class="flex items-center">
|
||||
<div>
|
||||
<span
|
||||
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded-full text-pink-600 bg-pink-200 mr-3"
|
||||
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded-full text-blue-600 bg-pink-200 mr-3"
|
||||
>
|
||||
<i
|
||||
class="fab fa-html5"
|
||||
|
@ -236,7 +236,7 @@ templ LandingContent() {
|
|||
<div class="flex items-center">
|
||||
<div>
|
||||
<span
|
||||
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded-full text-pink-600 bg-pink-200 mr-3"
|
||||
class="text-xs font-semibold inline-block py-1 px-2 uppercase rounded-full text-blue-600 bg-pink-200 mr-3"
|
||||
>
|
||||
<i
|
||||
class="far fa-paper-plane"
|
||||
|
|
|
@ -10,6 +10,7 @@ templ Head(msg string) {
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<meta name="theme-color" content="#000000"/>
|
||||
<meta name="view-transition" content="same-origin"/>
|
||||
<meta name="htmx-config" content='{"globalViewTransistions": true}'/>
|
||||
<link rel="shortcut icon" href="./assets/img/favicon.ico"/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
|
@ -41,7 +42,7 @@ templ LandingPage(user *models.User) {
|
|||
@Nav(user)
|
||||
@LandingContent()
|
||||
</body>
|
||||
@Footer()
|
||||
@LandingFooter()
|
||||
@toggleNavBar()
|
||||
</html>
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ templ LoginPage(errorMsg string) {
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
@Head("Log in")
|
||||
<body class="text-gray-800 antialiased">
|
||||
<body class="text-gray-800 antialiased h-full overflow-hidden">
|
||||
@Nav(nil)
|
||||
@LoginContent(false, errorMsg)
|
||||
</body>
|
||||
@Footer()
|
||||
@LoginFooter()
|
||||
@toggleNavBar()
|
||||
</html>
|
||||
}
|
||||
|
@ -17,11 +17,11 @@ templ SignupPage(errorMsg string) {
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
@Head("Sign up")
|
||||
<body class="text-gray-800 antialiased">
|
||||
<body class="text-gray-800 antialiased h-full overflow-hidden">
|
||||
@Nav(nil)
|
||||
@LoginContent(true, errorMsg)
|
||||
</body>
|
||||
@Footer()
|
||||
@LoginFooter()
|
||||
@toggleNavBar()
|
||||
</html>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package templates
|
||||
|
||||
templ spinnerCentered() {
|
||||
<div role="status" class="absolute -translate-x-1/2 -translate-y-1/2 top-2/4 left-1/2">
|
||||
<svg aria-hidden="true" class="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"></path>
|
||||
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"></path>
|
||||
</svg>
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
}
|
Loading…
Reference in New Issue