Skip to content

Local admin console

Mount a quota panel in a loopback Echo application. Inspect usage through a Viewer principal and identify the authentication callback you must replace for deployment. The example uses a process-local session binding and disposable memory quota state.

Install Echo Pro and make port 8080 available. You need a browser and Go 1.26 or later. No database, Redis or frontend toolchain is required. The example has fixed local authentication, so keep its listener on loopback.

Find the installed source directory:

Terminal window
cd "$(go list -m -f '{{.Dir}}' echo.labstack.com/pro)"

Start the example:

Terminal window
go run ./examples/admin

The process listens on 127.0.0.1:8080. It remains running until Ctrl-C. It prints nothing when it starts. Open http://127.0.0.1:8080/admin in your browser.

The principal is local-reader, restricted to tenant demo, with the default Viewer role. Select the team plan in quota usage. The plan has a 100-unit daily calls counter and a credits balance on the tokens meter. The program registers no business routes that consume them, so this is an inspection fixture.

The panel shows no write actions. Login alone does not enable credit changes or reset. The view reads the same limiter supplied to quotaadmin.New.

The outer CORS middleware skips the console prefix and its descendants for every method. Console requests reach the console’s own origin and CSRF wrapper. Config.Mount validates the panel and authentication callback, and VerifyRoutes checks the reserved prefix before serving.

Read the complete runnable source:

// Command admin demonstrates local-only, read-only customer authentication.
package main
import (
"context"
"crypto/rand"
"errors"
"fmt"
"net/http"
"os"
"os/signal"
"strings"
"time"
"github.com/labstack/echo/v5"
"github.com/labstack/echo/v5/middleware"
"echo.labstack.com/pro/admin"
"echo.labstack.com/pro/quota"
"echo.labstack.com/pro/quota/quotaadmin"
)
func runWithContext(ctx context.Context) error {
l, err := quota.New(quota.Config{Store: quota.NewMemoryStore(
quota.MemoryConfig{},
), Plans: []quota.Plan{{ID: "team", Limits: []quota.Limit{
quota.PerDay("calls", 100),
quota.Credits("credits").Meter("tokens"),
}}}})
if err != nil {
return err
}
defer func() {
closeCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), time.Second)
defer cancel()
if closeErr := l.Close(closeCtx); closeErr != nil {
fmt.Fprintln(os.Stderr, "limiter close failed")
}
}()
p, err := quotaadmin.New(quotaadmin.Config{Limiter: l})
if err != nil {
return err
}
// One process-local fixture login; use the validated login's session binding
// for each request in a deployed application, never a shared fixed value.
session := rand.Text()
e := echo.New()
prefix := "/admin"
// The outer CORS middleware must let every console method enter its wrapper.
cors, err := (middleware.CORSConfig{AllowOrigins: []string{
"http://127.0.0.1:8080",
}, Skipper: func(c *echo.Context) bool {
path := c.Request().URL.Path
return path == prefix || strings.HasPrefix(path, prefix+"/")
}}).ToMiddleware()
if err != nil {
return err
}
e.Use(cors)
cfg := admin.Config{Panels: []admin.Panel{
p,
}, Instance: "local-demo", Theme: admin.Theme{
Environment: "Local demonstration",
}, Authenticate: func(*echo.Context) (admin.Principal, error) {
// Replace with validated customer session/bearer claims in a deployed app.
// Fixed auth is confined to this loopback-only demonstration.
return admin.Principal{
ID: "local-reader",
Session: session,
Tenants: []string{"demo"},
}, nil
}}
if err := cfg.Mount(e, prefix); err != nil {
return err
}
if err := admin.VerifyRoutes(e, prefix); err != nil {
return err
}
server := &http.Server{
Addr: "127.0.0.1:8080",
Handler: e,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 15 * time.Second,
MaxHeaderBytes: 16 << 10,
}
done := make(chan error, 1)
go func() { done <- server.ListenAndServe() }()
select {
case err := <-done:
if !errors.Is(err, http.ErrServerClosed) {
return err
}
return nil
case <-ctx.Done():
shutdown, cancel := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second)
defer cancel()
err := server.Shutdown(shutdown)
if serveErr := <-done; !errors.Is(serveErr, http.ErrServerClosed) {
return errors.Join(err, serveErr)
}
return err
}
}
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
if err := runWithContext(ctx); err != nil {
return err
}
return nil
}

Authenticate returns a fixed principal because this is a local fixture. The random session binding lasts for one process. A production callback maps a real validated login and its session ID. Set exact deployment origins. If page cursors or actions can reach other instances, share the signing keys.

Press Ctrl-C. The server has a five-second shutdown bound. The limiter closes through a separate bounded cleanup context. Memory quota state and the fixture session disappear. Restarting produces a fresh fixture.

Replace fixed authentication with your login or trusted authbind mapping. Keep the console prefix reserved and verify it after all route registration. Use shared quota storage when the application has multiple instances. Configure explicit Operator scope, audit and confirmation policy before enabling mutations.

Run the admin examples to test valid and missing credentials. Use the configuration reference for shell defaults. The full console demo adds store-backed execution, audit and webhook panels and reports its database privilege limitations.