mirror of
https://github.com/openziti/zrok.git
synced 2024-11-28 19:14:07 +01:00
Merge branch 'main' into snyk-upgrade-d8c6f5b4c55065b4ec64d776fcbd3a56
This commit is contained in:
commit
d2e8da8b2c
18
CHANGELOG.md
18
CHANGELOG.md
@ -1,5 +1,23 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v0.4.33
|
||||
|
||||
CHANGE: Updated react-bootstrap to version 2.10.2.
|
||||
|
||||
CHANGE: Updated @mui/material to version 5.15.18.
|
||||
|
||||
## v0.4.32
|
||||
|
||||
FEATURE: New permission mode support for public frontends. Open permission mode frontends are available to all users in the service instance. Closed permission mode frontends reference the new `frontend_grants` table that can be used to control which accounts are allowed to create shares using that frontend. `zrok admin create frontend` now supports `--closed` flag to create closed permission mode frontends (https://github.com/openziti/zrok/issues/539)
|
||||
|
||||
FEATURE: New config `defaultFrontend` that specifies the default frontend to be used for an environment. Provides the default `--frontend` for `zrok share public` and `zrok reserve public` (https://github.com/openziti/zrok/issues/663)
|
||||
|
||||
FEATURE: Resource count limits now include `share_frontends` to limit the number of frontends that are allowed to make connections to a share (https://github.com/openziti/zrok/issues/650)
|
||||
|
||||
CHANGE: The frontend selection flag used by `zrok share public` and `zrok reserve public` has been changed from `--frontends` to `--frontend`
|
||||
|
||||
FIX: use controller config spec v4 in the Docker instance
|
||||
|
||||
## v0.4.31
|
||||
|
||||
FEATURE: New "limits classes" limits implementation (https://github.com/openziti/zrok/issues/606). This new feature allows for extensive limits customization on a per-user basis, with fallback to the global defaults in the controller configuration.
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"github.com/openziti/zrok/environment"
|
||||
"github.com/openziti/zrok/rest_client_zrok/admin"
|
||||
"github.com/openziti/zrok/rest_model_zrok"
|
||||
"github.com/openziti/zrok/sdk/golang/sdk"
|
||||
"github.com/openziti/zrok/tui"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@ -16,6 +17,7 @@ func init() {
|
||||
|
||||
type adminCreateFrontendCommand struct {
|
||||
cmd *cobra.Command
|
||||
closed bool
|
||||
}
|
||||
|
||||
func newAdminCreateFrontendCommand() *adminCreateFrontendCommand {
|
||||
@ -25,6 +27,7 @@ func newAdminCreateFrontendCommand() *adminCreateFrontendCommand {
|
||||
Args: cobra.ExactArgs(3),
|
||||
}
|
||||
command := &adminCreateFrontendCommand{cmd: cmd}
|
||||
cmd.Flags().BoolVar(&command.closed, "closed", false, "Enabled closed permission mode")
|
||||
cmd.Run = command.run
|
||||
return command
|
||||
}
|
||||
@ -44,11 +47,16 @@ func (cmd *adminCreateFrontendCommand) run(_ *cobra.Command, args []string) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
permissionMode := sdk.OpenPermissionMode
|
||||
if cmd.closed {
|
||||
permissionMode = sdk.ClosedPermissionMode
|
||||
}
|
||||
req := admin.NewCreateFrontendParams()
|
||||
req.Body = &rest_model_zrok.CreateFrontendRequest{
|
||||
ZID: zId,
|
||||
PublicName: publicName,
|
||||
URLTemplate: urlTemplate,
|
||||
PermissionMode: string(permissionMode),
|
||||
}
|
||||
|
||||
resp, err := zrok.Admin.CreateFrontend(req, mustGetAdminAuth())
|
||||
|
@ -40,6 +40,12 @@ func (cmd *configGetCommand) run(_ *cobra.Command, args []string) {
|
||||
} else {
|
||||
fmt.Println("apiEndpoint = <unset>")
|
||||
}
|
||||
case "defaultFrontend":
|
||||
if env.Config() != nil && env.Config().DefaultFrontend != "" {
|
||||
fmt.Printf("defaultFrontend = %v\n", env.Config().DefaultFrontend)
|
||||
} else {
|
||||
fmt.Println("defaultFrontend = <unset>")
|
||||
}
|
||||
default:
|
||||
fmt.Printf("unknown config name '%v'\n", configName)
|
||||
}
|
||||
|
@ -63,6 +63,20 @@ func (cmd *configSetCommand) run(_ *cobra.Command, args []string) {
|
||||
fmt.Printf("\n[%v]: because you have a %v-d environment, you won't see your config change until you run %v first!\n\n", tui.WarningLabel, tui.Code.Render("zrok enable"), tui.Code.Render("zrok disable"))
|
||||
}
|
||||
|
||||
case "defaultFrontend":
|
||||
if env.Config() == nil {
|
||||
if err := env.SetConfig(&env_core.Config{DefaultFrontend: value}); err != nil {
|
||||
tui.Error("unable to save config", err)
|
||||
}
|
||||
} else {
|
||||
cfg := env.Config()
|
||||
cfg.DefaultFrontend = value
|
||||
if err := env.SetConfig(cfg); err != nil {
|
||||
tui.Error("unable to save config", err)
|
||||
}
|
||||
}
|
||||
fmt.Println("zrok configuration updated")
|
||||
|
||||
default:
|
||||
fmt.Printf("unknown config name '%v'\n", configName)
|
||||
os.Exit(1)
|
||||
|
@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/openziti/zrok/environment"
|
||||
"github.com/openziti/zrok/environment/env_core"
|
||||
"github.com/openziti/zrok/tui"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
@ -36,18 +35,25 @@ func (cmd *configUnsetCommand) run(_ *cobra.Command, args []string) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if env.Config() != nil {
|
||||
cfg := env.Config()
|
||||
switch configName {
|
||||
case "apiEndpoint":
|
||||
if err := env.SetConfig(&env_core.Config{}); err != nil {
|
||||
tui.Error("unable to save config", err)
|
||||
}
|
||||
fmt.Println("zrok configuration updated")
|
||||
cfg.ApiEndpoint = ""
|
||||
if env.IsEnabled() {
|
||||
fmt.Printf("\n[%v]: because you have a %v-d environment, you won't see your config change until you run %v first!\n\n", tui.WarningLabel, tui.Code.Render("zrok enable"), tui.Code.Render("zrok disable"))
|
||||
}
|
||||
|
||||
case "defaultFrontend":
|
||||
cfg.DefaultFrontend = ""
|
||||
|
||||
default:
|
||||
fmt.Printf("unknown config name '%v'\n", configName)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := env.SetConfig(cfg); err != nil {
|
||||
tui.Error("unable to save config", err)
|
||||
}
|
||||
fmt.Println("zrok configuration updated")
|
||||
}
|
||||
}
|
||||
|
@ -40,8 +40,13 @@ func newReserveCommand() *reserveCommand {
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
}
|
||||
command := &reserveCommand{cmd: cmd}
|
||||
defaultFrontends := []string{"public"}
|
||||
if root, err := environment.LoadRoot(); err == nil {
|
||||
defaultFrontend, _ := root.DefaultFrontend()
|
||||
defaultFrontends = []string{defaultFrontend}
|
||||
}
|
||||
cmd.Flags().StringVarP(&command.uniqueName, "unique-name", "n", "", "A unique name for the reserved share (defaults to generated identifier)")
|
||||
cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share")
|
||||
cmd.Flags().StringArrayVar(&command.frontendSelection, "frontend", defaultFrontends, "Selected frontends to use for the share")
|
||||
cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode (public|private: proxy, web, caddy, drive) (private: tcpTunnel, udpTunnel, socks, vpn)")
|
||||
cmd.Flags().BoolVarP(&command.jsonOutput, "json-output", "j", false, "Emit JSON describing the created reserved share")
|
||||
cmd.Flags().StringArrayVar(&command.basicAuth, "basic-auth", []string{}, "Basic authentication users (<username:password>,...)")
|
||||
|
@ -45,7 +45,12 @@ func newSharePublicCommand() *sharePublicCommand {
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
command := &sharePublicCommand{cmd: cmd}
|
||||
cmd.Flags().StringArrayVar(&command.frontendSelection, "frontends", []string{"public"}, "Selected frontends to use for the share")
|
||||
defaultFrontends := []string{"public"}
|
||||
if root, err := environment.LoadRoot(); err == nil {
|
||||
defaultFrontend, _ := root.DefaultFrontend()
|
||||
defaultFrontends = []string{defaultFrontend}
|
||||
}
|
||||
cmd.Flags().StringArrayVar(&command.frontendSelection, "frontend", defaultFrontends, "Selected frontends to use for the share")
|
||||
cmd.Flags().StringVarP(&command.backendMode, "backend-mode", "b", "proxy", "The backend mode {proxy, web, caddy, drive}")
|
||||
cmd.Flags().BoolVar(&command.headless, "headless", false, "Disable TUI and run headless")
|
||||
cmd.Flags().BoolVar(&command.insecure, "insecure", false, "Enable insecure TLS certificate validation for <target>")
|
||||
|
@ -48,8 +48,10 @@ func (cmd *statusCommand) run(_ *cobra.Command, _ []string) {
|
||||
t.SetOutputMirror(os.Stdout)
|
||||
t.SetStyle(table.StyleColoredDark)
|
||||
t.AppendHeader(table.Row{"Config", "Value", "Source"})
|
||||
apiEndpoint, from := env.ApiEndpoint()
|
||||
t.AppendRow(table.Row{"apiEndpoint", apiEndpoint, from})
|
||||
apiEndpoint, apiEndpointFrom := env.ApiEndpoint()
|
||||
t.AppendRow(table.Row{"apiEndpoint", apiEndpoint, apiEndpointFrom})
|
||||
defaultFrontend, defaultFrontendFrom := env.DefaultFrontend()
|
||||
t.AppendRow(table.Row{"defaultFrontend", defaultFrontend, defaultFrontendFrom})
|
||||
t.Render()
|
||||
_, _ = fmt.Fprintf(os.Stderr, "\n")
|
||||
|
||||
|
@ -2,7 +2,6 @@ package controller
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/lib/pq"
|
||||
"github.com/mattn/go-sqlite3"
|
||||
@ -62,6 +61,7 @@ func (h *createFrontendHandler) Handle(params admin.CreateFrontendParams, princi
|
||||
PublicName: ¶ms.Body.PublicName,
|
||||
UrlTemplate: ¶ms.Body.URLTemplate,
|
||||
Reserved: true,
|
||||
PermissionMode: store.PermissionMode(params.Body.PermissionMode),
|
||||
}
|
||||
if _, err := str.CreateGlobalFrontend(fe, tx); err != nil {
|
||||
perr := &pq.Error{}
|
||||
|
@ -163,32 +163,49 @@ func (a *Agent) CanAccessShare(shrId int, trx *sqlx.Tx) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
if env.AccountId != nil {
|
||||
if err := a.str.LimitCheckLock(*env.AccountId, trx); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
ul, err := a.getUserLimits(*env.AccountId, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if ul.resource.IsGlobal() {
|
||||
if empty, err := a.str.IsBandwidthLimitJournalEmptyForGlobal(*env.AccountId, trx); err == nil && !empty {
|
||||
lj, err := a.str.FindLatestBandwidthLimitJournalForGlobal(*env.AccountId, trx)
|
||||
if scopedBwc, found := ul.scopes[sdk.BackendMode(shr.BackendMode)]; found {
|
||||
latestScopedJe, err := a.isBandwidthClassLimitedForAccount(*env.AccountId, scopedBwc, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if lj.Action == store.LimitLimitAction {
|
||||
if latestScopedJe != nil {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if empty, err := a.str.IsBandwidthLimitJournalEmptyForLimitClass(*env.AccountId, ul.resource.GetLimitClassId(), trx); err == nil && !empty {
|
||||
lj, err := a.str.FindLatestBandwidthLimitJournalForLimitClass(*env.AccountId, ul.resource.GetLimitClassId(), trx)
|
||||
for _, bwc := range ul.bandwidth {
|
||||
latestJe, err := a.isBandwidthClassLimitedForAccount(*env.AccountId, bwc, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if lj.Action == store.LimitLimitAction {
|
||||
if latestJe != nil {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc := ul.resource
|
||||
if scopeRc, found := ul.scopes[sdk.BackendMode(shr.BackendMode)]; found {
|
||||
rc = scopeRc
|
||||
}
|
||||
if rc.GetShareFrontends() > store.Unlimited {
|
||||
fes, err := a.str.FindFrontendsForPrivateShare(shr.Id, trx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(fes)+1 > rc.GetShareFrontends() {
|
||||
logrus.Infof("account '#%d' over frontends per share limit '%d'", *env.AccountId, rc.GetReservedShares())
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ type Config struct {
|
||||
Shares int
|
||||
ReservedShares int
|
||||
UniqueNames int
|
||||
ShareFrontends int
|
||||
Bandwidth *BandwidthPerPeriod
|
||||
Cycle time.Duration
|
||||
Enforcing bool
|
||||
@ -49,6 +50,7 @@ func DefaultConfig() *Config {
|
||||
Shares: store.Unlimited,
|
||||
ReservedShares: store.Unlimited,
|
||||
UniqueNames: store.Unlimited,
|
||||
ShareFrontends: store.Unlimited,
|
||||
Bandwidth: DefaultBandwidthPerPeriod(),
|
||||
Enforcing: false,
|
||||
Cycle: 15 * time.Minute,
|
||||
|
@ -37,6 +37,10 @@ func (rcc *configResourceCountClass) GetUniqueNames() int {
|
||||
return rcc.cfg.UniqueNames
|
||||
}
|
||||
|
||||
func (rcc *configResourceCountClass) String() string {
|
||||
return fmt.Sprintf("Config<environments: %d, shares: %d, reservedShares: %d, uniqueNames: %d>", rcc.cfg.Environments, rcc.cfg.Shares, rcc.cfg.ReservedShares, rcc.cfg.UniqueNames)
|
||||
func (rcc *configResourceCountClass) GetShareFrontends() int {
|
||||
return rcc.cfg.ShareFrontends
|
||||
}
|
||||
|
||||
func (rcc *configResourceCountClass) String() string {
|
||||
return fmt.Sprintf("Config<environments: %d, shares: %d, reservedShares: %d, uniqueNames: %d, share_frontends: %d>", rcc.cfg.Environments, rcc.cfg.Shares, rcc.cfg.ReservedShares, rcc.cfg.UniqueNames, rcc.cfg.ShareFrontends)
|
||||
}
|
||||
|
@ -42,7 +42,6 @@ func (ul *userLimits) ignoreBackends(bwc store.BandwidthClass) map[sdk.BackendMo
|
||||
}
|
||||
return ignoreBackends
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Agent) getUserLimits(acctId int, trx *sqlx.Tx) (*userLimits, error) {
|
||||
@ -85,7 +84,7 @@ func (a *Agent) isResourceCountClass(alc *store.LimitClass) bool {
|
||||
if alc.BackendMode != nil {
|
||||
return false
|
||||
}
|
||||
if alc.Environments == store.Unlimited && alc.Shares == store.Unlimited && alc.ReservedShares == store.Unlimited && alc.UniqueNames == store.Unlimited {
|
||||
if alc.Environments == store.Unlimited && alc.Shares == store.Unlimited && alc.ReservedShares == store.Unlimited && alc.UniqueNames == store.Unlimited && alc.ShareFrontends == store.Unlimited {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -95,7 +94,7 @@ func (a *Agent) isUnscopedBandwidthClass(alc *store.LimitClass) bool {
|
||||
if alc.BackendMode != nil {
|
||||
return false
|
||||
}
|
||||
if alc.Environments > store.Unlimited || alc.Shares > store.Unlimited || alc.ReservedShares > store.Unlimited || alc.UniqueNames > store.Unlimited {
|
||||
if alc.Environments > store.Unlimited || alc.Shares > store.Unlimited || alc.ReservedShares > store.Unlimited || alc.UniqueNames > store.Unlimited || alc.ShareFrontends > store.Unlimited {
|
||||
return false
|
||||
}
|
||||
if alc.PeriodMinutes < 1 {
|
||||
|
@ -116,6 +116,17 @@ func (h *shareHandler) Handle(params share.ShareParams, principal *rest_model_zr
|
||||
logrus.Error(err)
|
||||
return share.NewShareNotFound()
|
||||
}
|
||||
if sfe.PermissionMode == store.ClosedPermissionMode {
|
||||
granted, err := str.IsFrontendGrantedToAccount(int(principal.ID), sfe.Id, trx)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
return share.NewShareInternalServerError()
|
||||
}
|
||||
if !granted {
|
||||
logrus.Errorf("'%v' is not granted access to frontend '%v'", principal.Email, frontendSelection)
|
||||
return share.NewShareNotFound()
|
||||
}
|
||||
}
|
||||
if sfe != nil && sfe.UrlTemplate != nil {
|
||||
frontendZIds = append(frontendZIds, sfe.ZId)
|
||||
frontendTemplates = append(frontendTemplates, *sfe.UrlTemplate)
|
||||
|
@ -14,28 +14,28 @@ type Frontend struct {
|
||||
PublicName *string
|
||||
UrlTemplate *string
|
||||
Reserved bool
|
||||
Deleted bool
|
||||
PermissionMode PermissionMode
|
||||
}
|
||||
|
||||
func (str *Store) CreateFrontend(envId int, f *Frontend, tx *sqlx.Tx) (int, error) {
|
||||
stmt, err := tx.Prepare("insert into frontends (environment_id, private_share_id, token, z_id, public_name, url_template, reserved) values ($1, $2, $3, $4, $5, $6, $7) returning id")
|
||||
stmt, err := tx.Prepare("insert into frontends (environment_id, private_share_id, token, z_id, public_name, url_template, reserved, permission_mode) values ($1, $2, $3, $4, $5, $6, $7, $8) returning id")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error preparing frontends insert statement")
|
||||
}
|
||||
var id int
|
||||
if err := stmt.QueryRow(envId, f.PrivateShareId, f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved).Scan(&id); err != nil {
|
||||
if err := stmt.QueryRow(envId, f.PrivateShareId, f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved, f.PermissionMode).Scan(&id); err != nil {
|
||||
return 0, errors.Wrap(err, "error executing frontends insert statement")
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (str *Store) CreateGlobalFrontend(f *Frontend, tx *sqlx.Tx) (int, error) {
|
||||
stmt, err := tx.Prepare("insert into frontends (token, z_id, public_name, url_template, reserved) values ($1, $2, $3, $4, $5) returning id")
|
||||
stmt, err := tx.Prepare("insert into frontends (token, z_id, public_name, url_template, reserved, permission_mode) values ($1, $2, $3, $4, $5, $6) returning id")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error preparing global frontends insert statement")
|
||||
}
|
||||
var id int
|
||||
if err := stmt.QueryRow(f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved).Scan(&id); err != nil {
|
||||
if err := stmt.QueryRow(f.Token, f.ZId, f.PublicName, f.UrlTemplate, f.Reserved, f.PermissionMode).Scan(&id); err != nil {
|
||||
return 0, errors.Wrap(err, "error executing global frontends insert statement")
|
||||
}
|
||||
return id, nil
|
||||
@ -122,12 +122,12 @@ func (str *Store) FindFrontendsForPrivateShare(shrId int, tx *sqlx.Tx) ([]*Front
|
||||
}
|
||||
|
||||
func (str *Store) UpdateFrontend(fe *Frontend, tx *sqlx.Tx) error {
|
||||
sql := "update frontends set environment_id = $1, private_share_id = $2, token = $3, z_id = $4, public_name = $5, url_template = $6, reserved = $7, updated_at = current_timestamp where id = $8"
|
||||
sql := "update frontends set environment_id = $1, private_share_id = $2, token = $3, z_id = $4, public_name = $5, url_template = $6, reserved = $7, permission_mode = $8, updated_at = current_timestamp where id = $9"
|
||||
stmt, err := tx.Prepare(sql)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error preparing frontends update statement")
|
||||
}
|
||||
_, err = stmt.Exec(fe.EnvironmentId, fe.PrivateShareId, fe.Token, fe.ZId, fe.PublicName, fe.UrlTemplate, fe.Reserved, fe.Id)
|
||||
_, err = stmt.Exec(fe.EnvironmentId, fe.PrivateShareId, fe.Token, fe.ZId, fe.PublicName, fe.UrlTemplate, fe.Reserved, fe.PermissionMode, fe.Id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error executing frontends update statement")
|
||||
}
|
||||
|
18
controller/store/frontendGrant.go
Normal file
18
controller/store/frontendGrant.go
Normal file
@ -0,0 +1,18 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (str *Store) IsFrontendGrantedToAccount(acctId, frontendId int, trx *sqlx.Tx) (bool, error) {
|
||||
stmt, err := trx.Prepare("select count(0) from frontend_grants where account_id = $1 AND frontend_id = $2")
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "error preparing frontend_grants select statement")
|
||||
}
|
||||
var count int
|
||||
if err := stmt.QueryRow(acctId, frontendId).Scan(&count); err != nil {
|
||||
return false, errors.Wrap(err, "error querying frontend_grants count")
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
@ -22,6 +22,7 @@ type ResourceCountClass interface {
|
||||
GetShares() int
|
||||
GetReservedShares() int
|
||||
GetUniqueNames() int
|
||||
GetShareFrontends() int
|
||||
}
|
||||
|
||||
type BandwidthClass interface {
|
||||
@ -37,11 +38,13 @@ type BandwidthClass interface {
|
||||
|
||||
type LimitClass struct {
|
||||
Model
|
||||
Label *string
|
||||
BackendMode *sdk.BackendMode
|
||||
Environments int
|
||||
Shares int
|
||||
ReservedShares int
|
||||
UniqueNames int
|
||||
ShareFrontends int
|
||||
PeriodMinutes int
|
||||
RxBytes int64
|
||||
TxBytes int64
|
||||
@ -77,6 +80,10 @@ func (lc LimitClass) GetUniqueNames() int {
|
||||
return lc.UniqueNames
|
||||
}
|
||||
|
||||
func (lc LimitClass) GetShareFrontends() int {
|
||||
return lc.ShareFrontends
|
||||
}
|
||||
|
||||
func (lc LimitClass) GetBackendMode() sdk.BackendMode {
|
||||
if lc.BackendMode == nil {
|
||||
return ""
|
||||
@ -105,7 +112,12 @@ func (lc LimitClass) GetLimitAction() LimitAction {
|
||||
}
|
||||
|
||||
func (lc LimitClass) String() string {
|
||||
out := fmt.Sprintf("LimitClass<#%d", lc.Id)
|
||||
out := "LimitClass<"
|
||||
if lc.Label != nil && *lc.Label != "" {
|
||||
out += "'" + *lc.Label + "'"
|
||||
} else {
|
||||
out += fmt.Sprintf("#%d", lc.Id)
|
||||
}
|
||||
if lc.BackendMode != nil {
|
||||
out += fmt.Sprintf(", backendMode: '%s'", *lc.BackendMode)
|
||||
}
|
||||
@ -121,6 +133,9 @@ func (lc LimitClass) String() string {
|
||||
if lc.UniqueNames > Unlimited {
|
||||
out += fmt.Sprintf(", uniqueNames: %d", lc.UniqueNames)
|
||||
}
|
||||
if lc.ShareFrontends > Unlimited {
|
||||
out += fmt.Sprintf(", shareFrontends: %d", lc.ShareFrontends)
|
||||
}
|
||||
if lc.RxBytes > Unlimited || lc.TxBytes > Unlimited || lc.TotalBytes > Unlimited {
|
||||
out += fmt.Sprintf(", periodMinutes: %d", lc.PeriodMinutes)
|
||||
}
|
||||
@ -140,12 +155,12 @@ func (lc LimitClass) String() string {
|
||||
var _ BandwidthClass = (*LimitClass)(nil)
|
||||
|
||||
func (str *Store) CreateLimitClass(lc *LimitClass, trx *sqlx.Tx) (int, error) {
|
||||
stmt, err := trx.Prepare("insert into limit_classes (backend_mode, environments, shares, reserved_shares, unique_names, period_minutes, rx_bytes, tx_bytes, total_bytes, limit_action) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) returning id")
|
||||
stmt, err := trx.Prepare("insert into limit_classes (label, backend_mode, environments, shares, reserved_shares, unique_names, share_frontends, period_minutes, rx_bytes, tx_bytes, total_bytes, limit_action) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) returning id")
|
||||
if err != nil {
|
||||
return 0, errors.Wrap(err, "error preparing limit_classes insert statement")
|
||||
}
|
||||
var id int
|
||||
if err := stmt.QueryRow(lc.BackendMode, lc.Environments, lc.Shares, lc.ReservedShares, lc.UniqueNames, lc.PeriodMinutes, lc.RxBytes, lc.TxBytes, lc.TotalBytes, lc.LimitAction).Scan(&id); err != nil {
|
||||
if err := stmt.QueryRow(lc.Label, lc.BackendMode, lc.Environments, lc.Shares, lc.ReservedShares, lc.UniqueNames, lc.ShareFrontends, lc.PeriodMinutes, lc.RxBytes, lc.TxBytes, lc.TotalBytes, lc.LimitAction).Scan(&id); err != nil {
|
||||
return 0, errors.Wrap(err, "error executing limit_classes insert statement")
|
||||
}
|
||||
return id, nil
|
||||
|
@ -7,14 +7,6 @@ const (
|
||||
WarningLimitAction LimitAction = "warning"
|
||||
)
|
||||
|
||||
type LimitScope string
|
||||
|
||||
const (
|
||||
AccountLimitScope LimitScope = "account"
|
||||
EnvironmentLimitScope LimitScope = "environment"
|
||||
ShareLimitScope LimitScope = "share"
|
||||
)
|
||||
|
||||
type PermissionMode string
|
||||
|
||||
const (
|
||||
|
@ -18,7 +18,6 @@ type Share struct {
|
||||
Reserved bool
|
||||
UniqueName bool
|
||||
PermissionMode PermissionMode
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
func (str *Store) CreateShare(envId int, shr *Share, tx *sqlx.Tx) (int, error) {
|
||||
|
@ -0,0 +1,3 @@
|
||||
-- +migrate Up
|
||||
|
||||
alter table limit_classes add column share_frontends int not null default (-1);
|
@ -0,0 +1,17 @@
|
||||
-- +migrate Up
|
||||
|
||||
alter table frontends add column permission_mode permission_mode_type not null default('open');
|
||||
|
||||
create table frontend_grants (
|
||||
id serial primary key,
|
||||
|
||||
account_id integer references accounts (id) not null,
|
||||
frontend_id integer references frontends (id) not null,
|
||||
|
||||
created_at timestamptz not null default(current_timestamp),
|
||||
updated_at timestamptz not null default(current_timestamp),
|
||||
deleted boolean not null default(false)
|
||||
);
|
||||
|
||||
create index frontend_grants_account_id_idx on frontend_grants (account_id);
|
||||
create index frontend_grants_frontend_id_idx on frontend_grants (frontend_id);
|
@ -0,0 +1,3 @@
|
||||
-- +migrate Up
|
||||
|
||||
alter table limit_classes add column label varchar(32);
|
@ -0,0 +1,3 @@
|
||||
-- +migrate Up
|
||||
|
||||
alter table limit_classes add column share_frontends int not null default (-1);
|
17
controller/store/sql/sqlite3/027_v0_4_32_frontend_grants.sql
Normal file
17
controller/store/sql/sqlite3/027_v0_4_32_frontend_grants.sql
Normal file
@ -0,0 +1,17 @@
|
||||
-- +migrate Up
|
||||
|
||||
alter table frontends add column permission_mode string not null default('open');
|
||||
|
||||
create table frontend_grants (
|
||||
id integer primary key,
|
||||
|
||||
account_id integer references accounts (id) not null,
|
||||
frontend_id integer references frontends (id) not null,
|
||||
|
||||
created_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
|
||||
updated_at datetime not null default(strftime('%Y-%m-%d %H:%M:%f', 'now')),
|
||||
deleted boolean not null default(false)
|
||||
);
|
||||
|
||||
create index frontend_grants_account_id_idx on frontend_grants (account_id);
|
||||
create index frontend_grants_frontend_id_idx on frontend_grants (frontend_id);
|
@ -0,0 +1,3 @@
|
||||
-- +migrate Up
|
||||
|
||||
alter table limit_classes add column label varchar(32);
|
@ -90,9 +90,9 @@ ZROK_OAUTH_GOOGLE_CLIENT_ID=abcd1234
|
||||
ZROK_OAUTH_GOOGLE_CLIENT_SECRET=abcd1234
|
||||
|
||||
# zrok version, e.g., 1.0.0
|
||||
ZROK_IMAGE_TAG=latest
|
||||
ZROK_CLI_TAG=latest
|
||||
# ziti version, e.g., 1.0.0
|
||||
ZITI_IMAGE_TAG=latest
|
||||
ZITI_CLI_TAG=latest
|
||||
```
|
||||
|
||||
### Start the Docker Compose Project
|
||||
|
@ -4,7 +4,7 @@
|
||||
# /___|_| \___/|_|\_\
|
||||
# controller configuration
|
||||
|
||||
v: 3
|
||||
v: 4
|
||||
admin:
|
||||
# generate these admin tokens from a source of randomness, e.g.
|
||||
# LC_ALL=C tr -dc _A-Z-a-z-0-9 < /dev/urandom | head -c32
|
||||
|
@ -20,7 +20,7 @@ The limits agent is responsible for controlling the number of resources in use (
|
||||
|
||||
### Types of Limits
|
||||
|
||||
Limits can be specified that control the number of environments, shares, reserved shares, and unique names that can be created by an account. Limits that control the allowed number of resources are called _resource count limits_.
|
||||
Limits can be specified that control the number of environments, shares, reserved shares, unique names, and frontends per-share that can be created by an account. Limits that control the allowed number of resources are called _resource count limits_.
|
||||
|
||||
Limits can be specified to control the amount of data that can be transferred within a time period. Limits that control the amount of data that can be transferred are called _bandwidth limits_.
|
||||
|
||||
@ -40,6 +40,7 @@ limits:
|
||||
shares: -1
|
||||
reserved_shares: -1
|
||||
unique_names: -1
|
||||
share_frontends: -1
|
||||
bandwidth:
|
||||
period: 5m
|
||||
warning:
|
||||
@ -64,7 +65,7 @@ The `cycle` value controls how frequently the limits agent will evaluate enforce
|
||||
|
||||
### Global Resouce Count Limits
|
||||
|
||||
The `environments`, `shares`, `reserved_shares`, and `unique_names` specify the resource count limits, globally for the service instance.
|
||||
The `environments`, `shares`, `reserved_shares`, `unique_names`, and `share_frontends` specify the resource count limits, globally for the service instance.
|
||||
|
||||
These resource counts will be applied to all users in the service instance by default.
|
||||
|
||||
@ -91,11 +92,13 @@ Limit classes are created by creating a record in the `limit_classes` table in t
|
||||
```sql
|
||||
CREATE TABLE public.limit_classes (
|
||||
id integer NOT NULL,
|
||||
label VARCHAR(32),
|
||||
backend_mode public.backend_mode,
|
||||
environments integer DEFAULT '-1'::integer NOT NULL,
|
||||
shares integer DEFAULT '-1'::integer NOT NULL,
|
||||
reserved_shares integer DEFAULT '-1'::integer NOT NULL,
|
||||
unique_names integer DEFAULT '-1'::integer NOT NULL,
|
||||
share_frontends integer DEFAULT '-1'::integer NOT NULL,
|
||||
period_minutes integer DEFAULT 1440 NOT NULL,
|
||||
rx_bytes bigint DEFAULT '-1'::integer NOT NULL,
|
||||
tx_bytes bigint DEFAULT '-1'::integer NOT NULL,
|
||||
@ -130,7 +133,7 @@ Create a row in this table linking the `account_id` to the `limit_class_id` to a
|
||||
To support overriding the resource count limits defined in the global limits configuration, a site administrator can create a limit class by inserting a row into the `limit_classes` table structured like this:
|
||||
|
||||
```sql
|
||||
insert into limit_classes (environments, shares, reserved_shares, unique_names) values (1, 1, 1, 1);
|
||||
insert into limit_classes (environments, shares, reserved_shares, unique_names, share_frontends) values (1, 1, 1, 1, 1);
|
||||
```
|
||||
|
||||
This creates a limit class that sets the `environments`, `shares`, `reserved_shares`, and `unique_names` all to `1`.
|
||||
|
@ -124,6 +124,7 @@ func (t *FilesystemTarget) WriteStream(path string, stream io.Reader, mode os.Fi
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = io.Copy(f, stream)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -13,6 +13,7 @@ type Root interface {
|
||||
|
||||
Client() (*rest_client_zrok.Zrok, error)
|
||||
ApiEndpoint() (string, string)
|
||||
DefaultFrontend() (string, string)
|
||||
|
||||
IsEnabled() bool
|
||||
Environment() *Environment
|
||||
@ -35,6 +36,7 @@ type Environment struct {
|
||||
|
||||
type Config struct {
|
||||
ApiEndpoint string
|
||||
DefaultFrontend string
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
|
@ -85,6 +85,24 @@ func (r *Root) ApiEndpoint() (string, string) {
|
||||
return apiEndpoint, from
|
||||
}
|
||||
|
||||
func (r *Root) DefaultFrontend() (string, string) {
|
||||
defaultFrontend := "public"
|
||||
from := "binary"
|
||||
|
||||
if r.Config() != nil && r.Config().DefaultFrontend != "" {
|
||||
defaultFrontend = r.Config().DefaultFrontend
|
||||
from = "config"
|
||||
}
|
||||
|
||||
env := os.Getenv("ZROK_DEFAULT_FRONTEND")
|
||||
if env != "" {
|
||||
defaultFrontend = env
|
||||
from = "ZROK_DEFAULT_FRONTEND"
|
||||
}
|
||||
|
||||
return defaultFrontend, from
|
||||
}
|
||||
|
||||
func (r *Root) Environment() *env_core.Environment {
|
||||
return r.env
|
||||
}
|
||||
|
@ -85,6 +85,24 @@ func (r *Root) ApiEndpoint() (string, string) {
|
||||
return apiEndpoint, from
|
||||
}
|
||||
|
||||
func (r *Root) DefaultFrontend() (string, string) {
|
||||
defaultFrontend := "public"
|
||||
from := "binary"
|
||||
|
||||
if r.Config() != nil && r.Config().DefaultFrontend != "" {
|
||||
defaultFrontend = r.Config().DefaultFrontend
|
||||
from = "config"
|
||||
}
|
||||
|
||||
env := os.Getenv("ZROK_DEFAULT_FRONTEND")
|
||||
if env != "" {
|
||||
defaultFrontend = env
|
||||
from = "ZROK_DEFAULT_FRONTEND"
|
||||
}
|
||||
|
||||
return defaultFrontend, from
|
||||
}
|
||||
|
||||
func (r *Root) Environment() *env_core.Environment {
|
||||
return r.env
|
||||
}
|
||||
|
@ -224,12 +224,13 @@ func loadConfig() (*env_core.Config, error) {
|
||||
}
|
||||
out := &env_core.Config{
|
||||
ApiEndpoint: cfg.ApiEndpoint,
|
||||
DefaultFrontend: cfg.DefaultFrontend,
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func saveConfig(cfg *env_core.Config) error {
|
||||
in := &config{ApiEndpoint: cfg.ApiEndpoint}
|
||||
in := &config{ApiEndpoint: cfg.ApiEndpoint, DefaultFrontend: cfg.DefaultFrontend}
|
||||
data, err := json.MarshalIndent(in, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error marshaling config")
|
||||
@ -324,6 +325,7 @@ type metadata struct {
|
||||
|
||||
type config struct {
|
||||
ApiEndpoint string `json:"api_endpoint"`
|
||||
DefaultFrontend string `json:"default_frontend"`
|
||||
}
|
||||
|
||||
type environment struct {
|
||||
|
@ -83,6 +83,7 @@ limits:
|
||||
shares: -1
|
||||
reserved_shares: -1
|
||||
unique_names: -1
|
||||
share_frontends: -1
|
||||
bandwidth:
|
||||
period: 5m
|
||||
warning:
|
||||
|
@ -7,9 +7,12 @@ package rest_model_zrok
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
"github.com/go-openapi/validate"
|
||||
)
|
||||
|
||||
// CreateFrontendRequest create frontend request
|
||||
@ -17,6 +20,10 @@ import (
|
||||
// swagger:model createFrontendRequest
|
||||
type CreateFrontendRequest struct {
|
||||
|
||||
// permission mode
|
||||
// Enum: [open closed]
|
||||
PermissionMode string `json:"permissionMode,omitempty"`
|
||||
|
||||
// public name
|
||||
PublicName string `json:"public_name,omitempty"`
|
||||
|
||||
@ -29,6 +36,57 @@ type CreateFrontendRequest struct {
|
||||
|
||||
// Validate validates this create frontend request
|
||||
func (m *CreateFrontendRequest) Validate(formats strfmt.Registry) error {
|
||||
var res []error
|
||||
|
||||
if err := m.validatePermissionMode(formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var createFrontendRequestTypePermissionModePropEnum []interface{}
|
||||
|
||||
func init() {
|
||||
var res []string
|
||||
if err := json.Unmarshal([]byte(`["open","closed"]`), &res); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, v := range res {
|
||||
createFrontendRequestTypePermissionModePropEnum = append(createFrontendRequestTypePermissionModePropEnum, v)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
// CreateFrontendRequestPermissionModeOpen captures enum value "open"
|
||||
CreateFrontendRequestPermissionModeOpen string = "open"
|
||||
|
||||
// CreateFrontendRequestPermissionModeClosed captures enum value "closed"
|
||||
CreateFrontendRequestPermissionModeClosed string = "closed"
|
||||
)
|
||||
|
||||
// prop value enum
|
||||
func (m *CreateFrontendRequest) validatePermissionModeEnum(path, location string, value string) error {
|
||||
if err := validate.EnumCase(path, location, value, createFrontendRequestTypePermissionModePropEnum, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *CreateFrontendRequest) validatePermissionMode(formats strfmt.Registry) error {
|
||||
if swag.IsZero(m.PermissionMode) { // not required
|
||||
return nil
|
||||
}
|
||||
|
||||
// value enum
|
||||
if err := m.validatePermissionModeEnum("permissionMode", "body", m.PermissionMode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -1200,6 +1200,13 @@ func init() {
|
||||
"createFrontendRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"permissionMode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"open",
|
||||
"closed"
|
||||
]
|
||||
},
|
||||
"public_name": {
|
||||
"type": "string"
|
||||
},
|
||||
@ -2956,6 +2963,13 @@ func init() {
|
||||
"createFrontendRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"permissionMode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"open",
|
||||
"closed"
|
||||
]
|
||||
},
|
||||
"public_name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -1 +1 @@
|
||||
7.4.0
|
||||
7.6.0
|
||||
|
@ -16,6 +16,7 @@ export class CreateFrontendRequest {
|
||||
'zId'?: string;
|
||||
'urlTemplate'?: string;
|
||||
'publicName'?: string;
|
||||
'permissionMode'?: CreateFrontendRequest.PermissionModeEnum;
|
||||
|
||||
static discriminator: string | undefined = undefined;
|
||||
|
||||
@ -34,6 +35,11 @@ export class CreateFrontendRequest {
|
||||
"name": "publicName",
|
||||
"baseName": "public_name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "permissionMode",
|
||||
"baseName": "permissionMode",
|
||||
"type": "CreateFrontendRequest.PermissionModeEnum"
|
||||
} ];
|
||||
|
||||
static getAttributeTypeMap() {
|
||||
@ -41,3 +47,9 @@ export class CreateFrontendRequest {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace CreateFrontendRequest {
|
||||
export enum PermissionModeEnum {
|
||||
Open = <any> 'open',
|
||||
Closed = <any> 'closed'
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +108,7 @@ let primitives = [
|
||||
];
|
||||
|
||||
let enumsMap: {[index: string]: any} = {
|
||||
"CreateFrontendRequest.PermissionModeEnum": CreateFrontendRequest.PermissionModeEnum,
|
||||
"ShareRequest.ShareModeEnum": ShareRequest.ShareModeEnum,
|
||||
"ShareRequest.BackendModeEnum": ShareRequest.BackendModeEnum,
|
||||
"ShareRequest.OauthProviderEnum": ShareRequest.OauthProviderEnum,
|
||||
|
@ -120,7 +120,8 @@ export namespace ShareRequest {
|
||||
UdpTunnel = <any> 'udpTunnel',
|
||||
Caddy = <any> 'caddy',
|
||||
Drive = <any> 'drive',
|
||||
Socks = <any> 'socks'
|
||||
Socks = <any> 'socks',
|
||||
Vpn = <any> 'vpn'
|
||||
}
|
||||
export enum OauthProviderEnum {
|
||||
Github = <any> 'github',
|
||||
|
@ -30,20 +30,23 @@ class CreateFrontendRequest(object):
|
||||
swagger_types = {
|
||||
'z_id': 'str',
|
||||
'url_template': 'str',
|
||||
'public_name': 'str'
|
||||
'public_name': 'str',
|
||||
'permission_mode': 'str'
|
||||
}
|
||||
|
||||
attribute_map = {
|
||||
'z_id': 'zId',
|
||||
'url_template': 'url_template',
|
||||
'public_name': 'public_name'
|
||||
'public_name': 'public_name',
|
||||
'permission_mode': 'permissionMode'
|
||||
}
|
||||
|
||||
def __init__(self, z_id=None, url_template=None, public_name=None): # noqa: E501
|
||||
def __init__(self, z_id=None, url_template=None, public_name=None, permission_mode=None): # noqa: E501
|
||||
"""CreateFrontendRequest - a model defined in Swagger""" # noqa: E501
|
||||
self._z_id = None
|
||||
self._url_template = None
|
||||
self._public_name = None
|
||||
self._permission_mode = None
|
||||
self.discriminator = None
|
||||
if z_id is not None:
|
||||
self.z_id = z_id
|
||||
@ -51,6 +54,8 @@ class CreateFrontendRequest(object):
|
||||
self.url_template = url_template
|
||||
if public_name is not None:
|
||||
self.public_name = public_name
|
||||
if permission_mode is not None:
|
||||
self.permission_mode = permission_mode
|
||||
|
||||
@property
|
||||
def z_id(self):
|
||||
@ -115,6 +120,33 @@ class CreateFrontendRequest(object):
|
||||
|
||||
self._public_name = public_name
|
||||
|
||||
@property
|
||||
def permission_mode(self):
|
||||
"""Gets the permission_mode of this CreateFrontendRequest. # noqa: E501
|
||||
|
||||
|
||||
:return: The permission_mode of this CreateFrontendRequest. # noqa: E501
|
||||
:rtype: str
|
||||
"""
|
||||
return self._permission_mode
|
||||
|
||||
@permission_mode.setter
|
||||
def permission_mode(self, permission_mode):
|
||||
"""Sets the permission_mode of this CreateFrontendRequest.
|
||||
|
||||
|
||||
:param permission_mode: The permission_mode of this CreateFrontendRequest. # noqa: E501
|
||||
:type: str
|
||||
"""
|
||||
allowed_values = ["open", "closed"] # noqa: E501
|
||||
if permission_mode not in allowed_values:
|
||||
raise ValueError(
|
||||
"Invalid value for `permission_mode` ({0}), must be one of {1}" # noqa: E501
|
||||
.format(permission_mode, allowed_values)
|
||||
)
|
||||
|
||||
self._permission_mode = permission_mode
|
||||
|
||||
def to_dict(self):
|
||||
"""Returns the model properties as a dict"""
|
||||
result = {}
|
||||
|
@ -766,6 +766,9 @@ definitions:
|
||||
type: string
|
||||
public_name:
|
||||
type: string
|
||||
permissionMode:
|
||||
type: string
|
||||
enum: ["open", "closed"]
|
||||
|
||||
createFrontendResponse:
|
||||
type: object
|
||||
|
685
ui/package-lock.json
generated
685
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -7,14 +7,14 @@
|
||||
"@emotion/styled": "^11.10.4",
|
||||
"@mdi/js": "^7.0.96",
|
||||
"@mdi/react": "^1.6.1",
|
||||
"@mui/material": "^5.10.4",
|
||||
"@mui/material": "^5.15.18",
|
||||
"bootstrap": "^5.2.3",
|
||||
"dagre": "^0.8.5",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"humanize-duration": "^3.27.3",
|
||||
"moment": "^2.29.4",
|
||||
"react": "^18.3.1",
|
||||
"react-bootstrap": "^2.7.0",
|
||||
"react-bootstrap": "^2.10.2",
|
||||
"react-data-table-component": "^7.5.2",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-force-graph": "^1.43.0",
|
||||
|
@ -53,6 +53,7 @@
|
||||
* @property {string} zId
|
||||
* @property {string} url_template
|
||||
* @property {string} public_name
|
||||
* @property {string} permissionMode
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -14,7 +14,7 @@ const ActionsTab = (props) => {
|
||||
|
||||
return (
|
||||
<div className={"actions-tab"}>
|
||||
<div id={"change-password"} style={{"padding-top": "10px"}}>
|
||||
<div id={"change-password"} style={{"paddingTop": "10px"}}>
|
||||
<h3>Change Password?</h3>
|
||||
<p>Change the password used to log into the zrok web console.</p>
|
||||
<Button variant={"danger"} onClick={openChangePasswordModal}>Change Password</Button>
|
||||
|
Loading…
Reference in New Issue
Block a user