Merge branch 'main' into v1.next_canary

This commit is contained in:
Michael Quigley 2025-04-23 11:28:03 -04:00
commit c3dd6ac116
No known key found for this signature in database
GPG Key ID: 9B60314A9DD20A62
37 changed files with 4138 additions and 1304 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@
*.db *.db
/automated-release-build/ /automated-release-build/
etc/dev.yml etc/dev.yml
etc/dev-canary.yml
etc/dev-frontend.yml etc/dev-frontend.yml
# Dependencies # Dependencies

View File

@ -1,5 +1,25 @@
# CHANGELOG # CHANGELOG
## v1.0.3
FEATURE: New `zrok admin unbootstrap` to remove zrok resources from the underlying OpenZiti instance (https://github.com/openziti/zrok/issues/935)
FEATURE: New InfluxDB metrics capture infrastructure for `zrok test canary` framework (https://github.com/openziti/zrok/issues/948)
FEATURE: New `zrok test canary enabler` to validate `enable`/`disable` operations and gather performance metrics around how those paths are operating (https://github.com/openziti/zrok/issues/771)
CHANGE: New _guard_ to prevent users from running potentially dangerous `zrok test canary` commands inadvertently without understanding what they do (https://github.com/openziti/zrok/issues/947)
## v1.0.2
FEATURE: "Auto-rebase" for enabled environments where the `apiEndpoint` is set to `https://api.zrok.io`. This will automatically migrate existing environments to the new `apiEndpoint` for the `v1.0.x` series (https://github.com/openziti/zrok/issues/936)
FEATURE: New `admin/new_account_link` configuration option to allow the insertion of "how do I register for an account?" links into the login form (https://github.com/openziti/zrok/issues/552)
CHANGE: The release environment, share, and access modals in the API console now have a better message letting the user know they will still need to clean up their `zrok` processes (https://github.com/openziti/zrok/issues/910)
CHANGE: The openziti/zrok Docker image has been updated to use the latest version of the ziti CLI, 1.4.3 (https://github.com/openziti/zrok/pull/917)
## v1.0.1 ## v1.0.1
FEATURE: The zrok Agent now persists private accesses and reserved shares between executions. Any `zrok access private` instances or `zrok share reserved` instances created using the agent are now persisted to a registry stored in `${HOME}/.zrok`. When restarting the agent these accesses and reserved shares are re-created from the data in this registry (https://github.com/openziti/zrok/pull/922) FEATURE: The zrok Agent now persists private accesses and reserved shares between executions. Any `zrok access private` instances or `zrok share reserved` instances created using the agent are now persisted to a registry stored in `${HOME}/.zrok`. When restarting the agent these accesses and reserved shares are re-created from the data in this registry (https://github.com/openziti/zrok/pull/922)

31
canary/config.go Normal file
View File

@ -0,0 +1,31 @@
package canary
import (
"github.com/michaelquigley/cf"
"github.com/openziti/zrok/controller/metrics"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
const ConfigVersion = 1
type Config struct {
V int
Influx *metrics.InfluxConfig
}
func DefaultConfig() *Config {
return &Config{}
}
func LoadConfig(path string) (*Config, error) {
cfg := DefaultConfig()
if err := cf.BindYaml(cfg, path, cf.DefaultOptions()); err != nil {
return nil, errors.Wrapf(err, "error loading canary configuration '%v'", path)
}
if cfg.V != ConfigVersion {
return nil, errors.Errorf("expecting canary configuration version '%v', got '%v'", ConfigVersion, cfg.V)
}
logrus.Info(cf.Dump(cfg, cf.DefaultOptions()))
return cfg, nil
}

13
canary/dangerous.go Normal file
View File

@ -0,0 +1,13 @@
package canary
import (
"fmt"
"os"
)
func AcknowledgeDangerousCanary() error {
if _, ok := os.LookupEnv("ZROK_DANGEROUS_CANARY"); !ok {
return fmt.Errorf("this is a dangerous canary; see canary docs for details on enabling")
}
return nil
}

92
canary/disabler.go Normal file
View File

@ -0,0 +1,92 @@
package canary
import (
"github.com/openziti/zrok/environment/env_core"
"github.com/openziti/zrok/sdk/golang/sdk"
"github.com/sirupsen/logrus"
"math/rand"
"time"
)
type DisablerOptions struct {
Environments chan *sdk.Environment
MinDwell time.Duration
MaxDwell time.Duration
MinPacing time.Duration
MaxPacing time.Duration
SnapshotQueue chan *Snapshot
}
type Disabler struct {
Id uint
Done chan struct{}
opt *DisablerOptions
root env_core.Root
}
func NewDisabler(id uint, opt *DisablerOptions, root env_core.Root) *Disabler {
return &Disabler{
Id: id,
Done: make(chan struct{}),
opt: opt,
root: root,
}
}
func (d *Disabler) Run() {
defer logrus.Infof("#%d stopping", d.Id)
defer close(d.Done)
d.dwell()
d.iterate()
}
func (d *Disabler) dwell() {
dwell := d.opt.MinDwell.Milliseconds()
dwelta := d.opt.MaxDwell.Milliseconds() - d.opt.MinDwell.Milliseconds()
if dwelta > 0 {
dwell = int64(rand.Intn(int(dwelta)) + int(d.opt.MinDwell.Milliseconds()))
}
time.Sleep(time.Duration(dwell) * time.Millisecond)
}
func (d *Disabler) iterate() {
iteration := uint64(0)
for {
select {
case env, ok := <-d.opt.Environments:
if !ok {
return
}
snapshot := NewSnapshot("disable", d.Id, iteration)
iteration++
if err := sdk.DisableEnvironment(env, d.root); err == nil {
snapshot.Completed = time.Now()
snapshot.Ok = true
logrus.Infof("#%d disabled environment '%v'", d.Id, env.ZitiIdentity)
} else {
snapshot.Completed = time.Now()
snapshot.Ok = false
snapshot.Error = err
logrus.Errorf("error disabling canary (#%d) environment '%v': %v", d.Id, env.ZitiIdentity, err)
}
if d.opt.SnapshotQueue != nil {
d.opt.SnapshotQueue <- snapshot
} else {
logrus.Info(snapshot)
}
}
pacingMs := d.opt.MaxPacing.Milliseconds()
pacingDelta := d.opt.MaxPacing.Milliseconds() - d.opt.MinPacing.Milliseconds()
if pacingDelta > 0 {
pacingMs = (rand.Int63() % pacingDelta) + d.opt.MinPacing.Milliseconds()
time.Sleep(time.Duration(pacingMs) * time.Millisecond)
}
}
}

93
canary/enabler.go Normal file
View File

@ -0,0 +1,93 @@
package canary
import (
"fmt"
"github.com/openziti/zrok/environment/env_core"
"github.com/openziti/zrok/sdk/golang/sdk"
"github.com/sirupsen/logrus"
"math/rand"
"time"
)
type EnablerOptions struct {
Iterations uint
MinDwell time.Duration
MaxDwell time.Duration
MinPacing time.Duration
MaxPacing time.Duration
SnapshotQueue chan *Snapshot
}
type Enabler struct {
Id uint
Done chan struct{}
opt *EnablerOptions
root env_core.Root
Environments chan *sdk.Environment
}
func NewEnabler(id uint, opt *EnablerOptions, root env_core.Root) *Enabler {
return &Enabler{
Id: id,
Done: make(chan struct{}),
opt: opt,
root: root,
Environments: make(chan *sdk.Environment, opt.Iterations),
}
}
func (e *Enabler) Run() {
defer close(e.Environments)
defer close(e.Done)
defer logrus.Infof("#%d stopping", e.Id)
e.dwell()
e.iterate()
}
func (e *Enabler) dwell() {
dwell := e.opt.MinDwell.Milliseconds()
dwelta := e.opt.MaxDwell.Milliseconds() - e.opt.MinDwell.Milliseconds()
if dwelta > 0 {
dwell = int64(rand.Intn(int(dwelta)) + int(e.opt.MinDwell.Milliseconds()))
}
time.Sleep(time.Duration(dwell) * time.Millisecond)
}
func (e *Enabler) iterate() {
defer logrus.Info("done")
for i := uint(0); i < e.opt.Iterations; i++ {
snapshot := NewSnapshot("enable", e.Id, uint64(i))
env, err := sdk.EnableEnvironment(e.root, &sdk.EnableRequest{
Host: fmt.Sprintf("canary_%d_%d", e.Id, i),
Description: "canary.Enabler",
})
if err == nil {
snapshot.Completed = time.Now()
snapshot.Ok = true
e.Environments <- env
logrus.Infof("#%d enabled environment '%v'", e.Id, env.ZitiIdentity)
} else {
snapshot.Completed = time.Now()
snapshot.Ok = false
snapshot.Error = err
logrus.Errorf("error creating canary (#%d) environment: %v", e.Id, err)
}
if e.opt.SnapshotQueue != nil {
e.opt.SnapshotQueue <- snapshot
} else {
logrus.Info(snapshot)
}
pacingMs := e.opt.MaxPacing.Milliseconds()
pacingDelta := e.opt.MaxPacing.Milliseconds() - e.opt.MinPacing.Milliseconds()
if pacingDelta > 0 {
pacingMs = (rand.Int63() % pacingDelta) + e.opt.MinPacing.Milliseconds()
time.Sleep(time.Duration(pacingMs) * time.Millisecond)
}
}
}

102
canary/metrics.go Normal file
View File

@ -0,0 +1,102 @@
package canary
import (
"context"
"fmt"
influxdb2 "github.com/influxdata/influxdb-client-go/v2"
"github.com/openziti/zrok/util"
"github.com/sirupsen/logrus"
"slices"
"sort"
"time"
)
type Snapshot struct {
Operation string
Instance uint
Iteration uint64
Started time.Time
Completed time.Time
Ok bool
Error error
Size uint64
}
func NewSnapshot(operation string, instance uint, iteration uint64) *Snapshot {
return &Snapshot{Operation: operation, Instance: instance, Iteration: iteration, Started: time.Now()}
}
func (s *Snapshot) String() string {
if s.Ok {
return fmt.Sprintf("[%v, %d, %d] (ok) %v, %v", s.Operation, s.Instance, s.Iteration, s.Completed.Sub(s.Started), util.BytesToSize(int64(s.Size)))
} else {
return fmt.Sprintf("[%v, %d, %d] (err) %v, %v, (%v)", s.Operation, s.Instance, s.Iteration, s.Completed.Sub(s.Started), util.BytesToSize(int64(s.Size)), s.Error)
}
}
type SnapshotCollector struct {
InputQueue chan *Snapshot
Closed chan struct{}
ctx context.Context
cfg *Config
snapshots map[string][]*Snapshot
}
func NewSnapshotCollector(ctx context.Context, cfg *Config) *SnapshotCollector {
return &SnapshotCollector{
InputQueue: make(chan *Snapshot),
Closed: make(chan struct{}),
ctx: ctx,
cfg: cfg,
snapshots: make(map[string][]*Snapshot),
}
}
func (sc *SnapshotCollector) Run() {
defer close(sc.Closed)
defer logrus.Info("stopping")
logrus.Info("starting")
for {
select {
case <-sc.ctx.Done():
return
case snapshot := <-sc.InputQueue:
var snapshots []*Snapshot
if v, ok := sc.snapshots[snapshot.Operation]; ok {
snapshots = v
}
i := sort.Search(len(snapshots), func(i int) bool { return snapshots[i].Completed.After(snapshot.Started) })
snapshots = slices.Insert(snapshots, i, snapshot)
sc.snapshots[snapshot.Operation] = snapshots
}
}
}
func (sc *SnapshotCollector) Store() error {
idb := influxdb2.NewClient(sc.cfg.Influx.Url, sc.cfg.Influx.Token)
writeApi := idb.WriteAPIBlocking(sc.cfg.Influx.Org, sc.cfg.Influx.Bucket)
for key, arr := range sc.snapshots {
for _, snapshot := range arr {
tags := map[string]string{
"instance": fmt.Sprintf("%d", snapshot.Instance),
"iteration": fmt.Sprintf("%d", snapshot.Iteration),
"ok": fmt.Sprintf("%t", snapshot.Ok),
}
if snapshot.Error != nil {
tags["error"] = snapshot.Error.Error()
}
pt := influxdb2.NewPoint(snapshot.Operation, tags, map[string]interface{}{
"duration": snapshot.Completed.Sub(snapshot.Started).Milliseconds(),
"size": snapshot.Size,
}, snapshot.Started)
if err := writeApi.WritePoint(context.Background(), pt); err != nil {
return err
}
}
logrus.Infof("wrote '%v' points for '%v'", len(arr), key)
}
idb.Close()
logrus.Infof("complete")
return nil
}

View File

@ -0,0 +1,40 @@
package main
import (
"github.com/michaelquigley/cf"
"github.com/openziti/zrok/controller"
"github.com/openziti/zrok/controller/config"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func init() {
adminCmd.AddCommand(newAdminUnbootstrap().cmd)
}
type adminUnbootstrap struct {
cmd *cobra.Command
}
func newAdminUnbootstrap() *adminUnbootstrap {
cmd := &cobra.Command{
Use: "unbootstrap <configPath>",
Short: "Unbootstrap the underlying Ziti network from zrok",
Args: cobra.ExactArgs(1),
}
command := &adminUnbootstrap{cmd: cmd}
cmd.Run = command.run
return command
}
func (cmd *adminUnbootstrap) run(_ *cobra.Command, args []string) {
cfg, err := config.LoadConfig(args[0])
if err != nil {
panic(err)
}
logrus.Infof(cf.Dump(cfg, cf.DefaultOptions()))
if err := controller.Unbootstrap(cfg); err != nil {
panic(err)
}
logrus.Infof("unbootstrap complete!")
}

View File

@ -0,0 +1,143 @@
package main
import (
"context"
"github.com/openziti/zrok/canary"
"github.com/openziti/zrok/environment"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"math/rand"
"time"
)
func init() {
testCanaryCmd.AddCommand(newTestCanaryEnabler().cmd)
}
type testCanaryEnabler struct {
cmd *cobra.Command
enablers uint
iterations uint
minPreDelay time.Duration
maxPreDelay time.Duration
minDwell time.Duration
maxDwell time.Duration
minPacing time.Duration
maxPacing time.Duration
skipDisable bool
canaryConfig string
}
func newTestCanaryEnabler() *testCanaryEnabler {
cmd := &cobra.Command{
Use: "enabler",
Short: "Enable a canary enabling environments",
Args: cobra.NoArgs,
}
command := &testCanaryEnabler{cmd: cmd}
cmd.Run = command.run
cmd.Flags().UintVarP(&command.enablers, "enablers", "e", 1, "Number of concurrent enablers to start")
cmd.Flags().UintVarP(&command.iterations, "iterations", "i", 1, "Number of iterations")
cmd.Flags().DurationVar(&command.minDwell, "min-dwell", 0, "Minimum dwell time")
cmd.Flags().DurationVar(&command.maxDwell, "max-dwell", 0, "Maximum dwell time")
cmd.Flags().DurationVar(&command.minPacing, "min-pacing", 0, "Minimum pacing time")
cmd.Flags().DurationVar(&command.maxPacing, "max-pacing", 0, "Maximum pacing time")
cmd.Flags().BoolVar(&command.skipDisable, "skip-disable", false, "Disable (clean up) enabled environments")
cmd.Flags().StringVar(&command.canaryConfig, "canary-config", "", "Path to canary configuration file")
return command
}
func (cmd *testCanaryEnabler) run(_ *cobra.Command, _ []string) {
if err := canary.AcknowledgeDangerousCanary(); err != nil {
logrus.Fatal(err)
}
root, err := environment.LoadRoot()
if err != nil {
panic(err)
}
var sc *canary.SnapshotCollector
var scCtx context.Context
var scCancel context.CancelFunc
if cmd.canaryConfig != "" {
cfg, err := canary.LoadConfig(cmd.canaryConfig)
if err != nil {
panic(err)
}
scCtx, scCancel = context.WithCancel(context.Background())
sc = canary.NewSnapshotCollector(scCtx, cfg)
go sc.Run()
}
var enablers []*canary.Enabler
for i := uint(0); i < cmd.enablers; i++ {
preDelay := cmd.maxPreDelay.Milliseconds()
preDelayDelta := cmd.maxPreDelay.Milliseconds() - cmd.minPreDelay.Milliseconds()
if preDelayDelta > 0 {
preDelay = int64(rand.Intn(int(preDelayDelta))) + cmd.minPreDelay.Milliseconds()
time.Sleep(time.Duration(preDelay) * time.Millisecond)
}
enablerOpts := &canary.EnablerOptions{
Iterations: cmd.iterations,
MinDwell: cmd.minDwell,
MaxDwell: cmd.maxDwell,
MinPacing: cmd.minPacing,
MaxPacing: cmd.maxPacing,
}
if sc != nil {
enablerOpts.SnapshotQueue = sc.InputQueue
}
enabler := canary.NewEnabler(i, enablerOpts, root)
enablers = append(enablers, enabler)
go enabler.Run()
}
if !cmd.skipDisable {
var disablers []*canary.Disabler
for i := uint(0); i < cmd.enablers; i++ {
disablerOpts := &canary.DisablerOptions{
Environments: enablers[i].Environments,
}
if sc != nil {
disablerOpts.SnapshotQueue = sc.InputQueue
}
disabler := canary.NewDisabler(i, disablerOpts, root)
disablers = append(disablers, disabler)
go disabler.Run()
}
for _, disabler := range disablers {
logrus.Infof("waiting for disabler #%d", disabler.Id)
<-disabler.Done
}
} else {
for _, enabler := range enablers {
enablerLoop:
for {
select {
case env, ok := <-enabler.Environments:
if !ok {
break enablerLoop
}
logrus.Infof("enabler #%d: %v", enabler.Id, env.ZitiIdentity)
}
}
}
}
for _, enabler := range enablers {
<-enabler.Done
}
if sc != nil {
scCancel()
<-sc.Closed
if err := sc.Store(); err != nil {
panic(err)
}
}
logrus.Infof("complete")
}

View File

@ -38,6 +38,7 @@ type Config struct {
type AdminConfig struct { type AdminConfig struct {
Secrets []string `cf:"+secret"` Secrets []string `cf:"+secret"`
TouLink string TouLink string
NewAccountLink string
ProfileEndpoint string ProfileEndpoint string
} }

View File

@ -26,6 +26,7 @@ func (ch *configurationHandler) Handle(_ metadata.ConfigurationParams) middlewar
} }
if cfg.Admin != nil { if cfg.Admin != nil {
data.TouLink = cfg.Admin.TouLink data.TouLink = cfg.Admin.TouLink
data.NewAccountLink = cfg.Admin.NewAccountLink
} }
if cfg.Invites != nil { if cfg.Invites != nil {
data.InviteTokenContact = cfg.Invites.TokenContact data.InviteTokenContact = cfg.Invites.TokenContact

284
controller/unbootstrap.go Normal file
View File

@ -0,0 +1,284 @@
package controller
import (
"context"
"fmt"
"github.com/openziti/edge-api/rest_management_api_client"
apiConfig "github.com/openziti/edge-api/rest_management_api_client/config"
"github.com/openziti/edge-api/rest_management_api_client/edge_router_policy"
"github.com/openziti/edge-api/rest_management_api_client/identity"
"github.com/openziti/edge-api/rest_management_api_client/service"
"github.com/openziti/edge-api/rest_management_api_client/service_edge_router_policy"
"github.com/openziti/edge-api/rest_management_api_client/service_policy"
"github.com/openziti/zrok/controller/config"
"github.com/openziti/zrok/controller/zrokEdgeSdk"
"github.com/openziti/zrok/sdk/golang/sdk"
"github.com/sirupsen/logrus"
)
func Unbootstrap(cfg *config.Config) error {
edge, err := zrokEdgeSdk.Client(cfg.Ziti)
if err != nil {
return err
}
if err := unbootstrapServiceEdgeRouterPolicies(edge); err != nil {
logrus.Errorf("error unbootstrapping service edge router policies: %v", err)
}
if err := unbootstrapServicePolicies(edge); err != nil {
logrus.Errorf("error unbootstrapping service policies: %v", err)
}
if err := unbootstrapConfigs(edge); err != nil {
logrus.Errorf("error unbootrapping configs: %v", err)
}
if err := unbootstrapServices(edge); err != nil {
logrus.Errorf("error unbootstrapping services: %v", err)
}
if err := unbootstrapEdgeRouterPolicies(edge); err != nil {
logrus.Errorf("error unbootstrapping edge router policies: %v", err)
}
if err := unbootstrapIdentities(edge); err != nil {
logrus.Errorf("error unbootstrapping identities: %v", err)
}
if err := unbootstrapConfigType(edge); err != nil {
logrus.Errorf("error unbootstrapping config type: %v", err)
}
return nil
}
func unbootstrapServiceEdgeRouterPolicies(edge *rest_management_api_client.ZitiEdgeManagement) error {
for {
filter := "tags.zrok != null"
limit := int64(100)
offset := int64(0)
listReq := &service_edge_router_policy.ListServiceEdgeRouterPoliciesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listResp, err := edge.ServiceEdgeRouterPolicy.ListServiceEdgeRouterPolicies(listReq, nil)
if err != nil {
return err
}
if len(listResp.Payload.Data) < 1 {
break
}
for _, serp := range listResp.Payload.Data {
delReq := &service_edge_router_policy.DeleteServiceEdgeRouterPolicyParams{
ID: *serp.ID,
Context: context.Background(),
}
_, err := edge.ServiceEdgeRouterPolicy.DeleteServiceEdgeRouterPolicy(delReq, nil)
if err == nil {
logrus.Infof("deleted service edge router policy '%v'", *serp.ID)
} else {
return err
}
}
}
return nil
}
func unbootstrapServicePolicies(edge *rest_management_api_client.ZitiEdgeManagement) error {
for {
filter := "tags.zrok != null"
limit := int64(100)
offset := int64(0)
listReq := &service_policy.ListServicePoliciesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listResp, err := edge.ServicePolicy.ListServicePolicies(listReq, nil)
if err != nil {
return err
}
if len(listResp.Payload.Data) < 1 {
break
}
for _, sp := range listResp.Payload.Data {
delReq := &service_policy.DeleteServicePolicyParams{
ID: *sp.ID,
Context: context.Background(),
}
_, err := edge.ServicePolicy.DeleteServicePolicy(delReq, nil)
if err == nil {
logrus.Infof("deleted service policy '%v'", *sp.ID)
} else {
return err
}
}
}
return nil
}
func unbootstrapServices(edge *rest_management_api_client.ZitiEdgeManagement) error {
for {
filter := "tags.zrok != null"
limit := int64(100)
offset := int64(0)
listReq := &service.ListServicesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listResp, err := edge.Service.ListServices(listReq, nil)
if err != nil {
return err
}
if len(listResp.Payload.Data) < 1 {
break
}
for _, svc := range listResp.Payload.Data {
delReq := &service.DeleteServiceParams{
ID: *svc.ID,
Context: context.Background(),
}
_, err := edge.Service.DeleteService(delReq, nil)
if err == nil {
logrus.Infof("deleted service '%v' (%v)", *svc.ID, *svc.Name)
} else {
return err
}
}
}
return nil
}
func unbootstrapEdgeRouterPolicies(edge *rest_management_api_client.ZitiEdgeManagement) error {
for {
filter := "tags.zrok != null"
limit := int64(100)
offset := int64(0)
listReq := &edge_router_policy.ListEdgeRouterPoliciesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listResp, err := edge.EdgeRouterPolicy.ListEdgeRouterPolicies(listReq, nil)
if err != nil {
return err
}
if len(listResp.Payload.Data) < 1 {
break
}
for _, erp := range listResp.Payload.Data {
delReq := &edge_router_policy.DeleteEdgeRouterPolicyParams{
ID: *erp.ID,
Context: context.Background(),
}
_, err := edge.EdgeRouterPolicy.DeleteEdgeRouterPolicy(delReq, nil)
if err == nil {
logrus.Infof("deleted edge router policy '%v'", *erp.ID)
} else {
return err
}
}
}
return nil
}
func unbootstrapIdentities(edge *rest_management_api_client.ZitiEdgeManagement) error {
for {
filter := "tags.zrok != null"
limit := int64(100)
offset := int64(0)
listReq := &identity.ListIdentitiesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listResp, err := edge.Identity.ListIdentities(listReq, nil)
if err != nil {
return err
}
if len(listResp.Payload.Data) < 1 {
break
}
for _, i := range listResp.Payload.Data {
delReq := &identity.DeleteIdentityParams{
ID: *i.ID,
Context: context.Background(),
}
_, err := edge.Identity.DeleteIdentity(delReq, nil)
if err == nil {
logrus.Infof("deleted identity '%v' (%v)", *i.ID, *i.Name)
} else {
return err
}
}
}
return nil
}
func unbootstrapConfigs(edge *rest_management_api_client.ZitiEdgeManagement) error {
for {
filter := "tags.zrok != null"
limit := int64(100)
offset := int64(0)
listReq := &apiConfig.ListConfigsParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listResp, err := edge.Config.ListConfigs(listReq, nil)
if err != nil {
return err
}
if len(listResp.Payload.Data) < 1 {
break
}
for _, listCfg := range listResp.Payload.Data {
delReq := &apiConfig.DeleteConfigParams{
ID: *listCfg.ID,
Context: context.Background(),
}
_, err := edge.Config.DeleteConfig(delReq, nil)
if err == nil {
logrus.Infof("deleted config '%v'", *listCfg.ID)
} else {
return nil
}
}
}
return nil
}
func unbootstrapConfigType(edge *rest_management_api_client.ZitiEdgeManagement) error {
for {
filter := fmt.Sprintf("name = \"%v\"", sdk.ZrokProxyConfig)
limit := int64(100)
offset := int64(0)
listReq := &apiConfig.ListConfigTypesParams{
Filter: &filter,
Limit: &limit,
Offset: &offset,
Context: context.Background(),
}
listResp, err := edge.Config.ListConfigTypes(listReq, nil)
if err != nil {
return err
}
if len(listResp.Payload.Data) < 1 {
break
}
for _, listCfgType := range listResp.Payload.Data {
delReq := &apiConfig.DeleteConfigTypeParams{
ID: *listCfgType.ID,
Context: context.Background(),
}
_, err := edge.Config.DeleteConfigType(delReq, nil)
if err == nil {
logrus.Infof("deleted config type '%v' (%v)", *listCfgType.ID, *listCfgType.Name)
} else {
return err
}
}
}
return nil
}

View File

@ -1,5 +1,5 @@
# this builds docker.io/openziti/zrok # this builds docker.io/openziti/zrok
FROM docker.io/openziti/ziti-cli:1.3.3 FROM docker.io/openziti/ziti-cli:1.4.3
ARG ARTIFACTS_DIR=./dist ARG ARTIFACTS_DIR=./dist
ARG DOCKER_BUILD_DIR=. ARG DOCKER_BUILD_DIR=.

View File

@ -1,4 +1,6 @@
This formula is maintained by the Homebrew community.
```text ```text
brew install zrok brew install zrok
``` ```

View File

@ -19,7 +19,7 @@ import DownloadCardStyles from '@site/src/css/download-card.module.css';
<DownloadCard <DownloadCard
osName="Linux" osName="Linux"
osLogo="/img/logo-linux.svg" osLogo="/img/logo-linux.svg"
infoText="RPM/DEB or Homebrew" infoText="RPM/DEB/AUR or Homebrew"
guideLink="/docs/guides/install/linux" guideLink="/docs/guides/install/linux"
/> />
</div> </div>

View File

@ -10,9 +10,9 @@ import AnsibleRepoSetup from './_ansible_repo_setup.yaml'
import ConcatenateYamlSnippets from '@site/src/components/cat-yaml.jsx' import ConcatenateYamlSnippets from '@site/src/components/cat-yaml.jsx'
import Homebrew from './_homebrew.mdx'; import Homebrew from './_homebrew.mdx';
## Install `zrok` from the Repository ## Package Repository
This will configure the system to receive DEB or RPM package updates. The RedHat (RPM) and Debian (DEB) packages are maintained by NetFoundry.
```text ```text
curl -sSf https://get.openziti.io/install.bash | sudo bash -s zrok curl -sSf https://get.openziti.io/install.bash | sudo bash -s zrok
@ -43,10 +43,6 @@ Check out [zrok frontdoor](/guides/frontdoor.mdx?os=Linux) for running `zrok` as
</Details> </Details>
## Homebrew
<Homebrew />
## Linux Binary ## Linux Binary
<AssetsProvider> <AssetsProvider>
@ -94,7 +90,7 @@ Download the binary distribution for your Linux distribution's architecture or r
/ /| | | (_) | < / /| | | (_) | <
/___|_| \___/|_|\_\ /___|_| \___/|_|\_\
v0.4.0 [c889005] v1.0.0 [c889005]
``` ```
</Details> </Details>
@ -133,4 +129,12 @@ sudo install -o root -g root ./zrok /usr/local/bin/;
zrok version; zrok version;
``` ```
</Details> </Details>
## Arch User Repository
[An Arch User Repository (AUR) package](https://aur.archlinux.org/packages/zrok-bin) is maintained by the Arch community. As of April 2025, the AUR package includes the `zrok` CLI and [the `zrok-agent.service` systemd `--user` service](/guides/agent/linux-service.mdx).
## Homebrew Formula
<Homebrew />

View File

@ -68,7 +68,7 @@ Create a `zrok` controller configuration file in `etc/ctrl.yml`. The controller
# /___|_| \___/|_|\_\ # /___|_| \___/|_|\_\
# controller configuration # controller configuration
v: 3 v: 4
admin: admin:
# generate these admin tokens from a source of randomness, e.g. # generate these admin tokens from a source of randomness, e.g.

32
docs/myzrok/limits.md Normal file
View File

@ -0,0 +1,32 @@
---
title: Limits
---
NetFoundry's public zrok instance implements various limits based on pricing tier,
as well as rate limits in order to protect the service for all users.
### Limits on Shares, Environments, or Bandwidth
The number of shares, enviroments, or allowed bandwidth is based on the limits outlined within your myzrok subscription.
These limits are defined on the [zrok pricing](https://zrok.io/pricing/) page.
Bandwidth limitations are based on a rolling 24 hour window. Note that if you exceed the daily bandwidth of your plan,
any running shares will be disabled, and the zrok API will prevent any new shares from being created until the bandwidth
falls back below the 24 hour limit.
### Rate Limitations For Public Shares
Public shares are subject to API rate limiting, both by IP address, as well as the individual share token.
These limits exist to protect the zrok service so that one user does not negatively impact the experience for others.
The rate limits for public shares are defined below:
#### Per IP Address
2000 requests per 300 seconds (average of 6.66 requests per second)
The rate limiter will allow a burst of requests in a shorter timespan up to 2000 requests, but once the rate limit has been exceeded,
new requests will be blocked until the request rate falls below the limit of the 300 second window.
#### Per Share
7500 requests per 300 seconds from *any number of IP addresses* (average of 25 requests per second)

51
docs/myzrok/upgrading.md Normal file
View File

@ -0,0 +1,51 @@
---
title: Upgrading From 0.4 to 1.0
---
## Upgrading an existing 0.4 environment
If you have not already, [install the latest 1.x zrok binary](/docs/guides/install) into your environment.
:::note
As of zrok `1.0.2`, the zrok rebase is automatic and the configuration will automatically be updated to the v1 API.
No action is necessary.
:::
If you are running version `1.0.0` or `1.0.1`, you can run the following to rebase your environment to use the new versioned API:
```
zrok rebase apiEndpoint https://api-v1.zrok.io
```
Resume zrok API interactions as normal!
## Trouble after upgrade?
If you run into any issues after upgrading your environment, first verify your zrok version and review your current zrok configuration:
```
zrok version
```
Review the `apiEndpoint` configuration, if you are running version `1.0` or later, the `apiEndpoint` should be `https://api-v1.zrok.io`
```
zrok status
```
If you're still having issues, we recommend you reach out to our community support team at our [zrok discourse](https://openziti.discourse.group/c/zrok/24) forum.
If you prefer to do a hard reset of your environment, you can also run the commands below:
:::warning
Running `zrok disable` will delete any running environments or shares, and will release any reserved shares
:::
```
zrok disable
```
Reset the config back to the default API endpoint for the binary version
```
zrok config unset apiEndpoint
```
Create a fresh environment
```
zrok enable <your account token>
```

View File

@ -6,8 +6,10 @@ import (
"github.com/openziti/zrok/environment/env_core" "github.com/openziti/zrok/environment/env_core"
"github.com/openziti/zrok/environment/env_v0_3" "github.com/openziti/zrok/environment/env_v0_3"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus"
"os" "os"
"path/filepath" "path/filepath"
"strings"
) )
const V = "v0.4" const V = "v0.4"
@ -286,6 +288,13 @@ func loadEnvironment() (*env_core.Environment, error) {
ZitiIdentity: env.ZId, ZitiIdentity: env.ZId,
ApiEndpoint: env.ApiEndpoint, ApiEndpoint: env.ApiEndpoint,
} }
if strings.HasPrefix(env.ApiEndpoint, "https://api.zrok.io") {
out.ApiEndpoint = "https://api-v1.zrok.io"
if err := saveEnvironment(out); err != nil {
return nil, errors.Wrap(err, "error auto-rebasing apiEndpoint")
}
logrus.Info("auto-rebased 'apiEndpoint' for v1.0.x")
}
return out, nil return out, nil
} }

View File

@ -20,10 +20,15 @@ admin:
secrets: secrets:
- 77623cad-1847-4d6d-8ffe-37defc33c909 - 77623cad-1847-4d6d-8ffe-37defc33c909
# #
# If `tou_link` is present, the frontend will display the "Terms of Use" link on the login and registration forms # If `tou_link` is present, the API console will display the "Terms of Use" link on the login and registration forms
# #
tou_link: '<a href="https://google.com" target="_">Terms and Conditions</a>' tou_link: '<a href="https://google.com" target="_">Terms and Conditions</a>'
# #
# If `new_account_link` is present, the API console will inject the contents of this setting into the login form; the
# intention is that it is used to present a "How do I get an account?" link.
#
new_account_link: '<a href="https://google.com" target="_">How do I get an account?</a>'
#
# If `profile_endpoint` is present, the controller will start a `net/http/pprof` endpoint at the specified host:port # If `profile_endpoint` is present, the controller will start a `net/http/pprof` endpoint at the specified host:port
# #
#profile_endpoint: localhost:6060 #profile_endpoint: localhost:6060

View File

@ -23,6 +23,9 @@ type Configuration struct {
// invites open // invites open
InvitesOpen bool `json:"invitesOpen,omitempty"` InvitesOpen bool `json:"invitesOpen,omitempty"`
// new account link
NewAccountLink string `json:"newAccountLink,omitempty"`
// requires invite token // requires invite token
RequiresInviteToken bool `json:"requiresInviteToken,omitempty"` RequiresInviteToken bool `json:"requiresInviteToken,omitempty"`

View File

@ -1985,6 +1985,9 @@ func init() {
"invitesOpen": { "invitesOpen": {
"type": "boolean" "type": "boolean"
}, },
"newAccountLink": {
"type": "string"
},
"requiresInviteToken": { "requiresInviteToken": {
"type": "boolean" "type": "boolean"
}, },
@ -4298,6 +4301,9 @@ func init() {
"invitesOpen": { "invitesOpen": {
"type": "boolean" "type": "boolean"
}, },
"newAccountLink": {
"type": "string"
},
"requiresInviteToken": { "requiresInviteToken": {
"type": "boolean" "type": "boolean"
}, },

View File

@ -0,0 +1,50 @@
package sdk
import (
httptransport "github.com/go-openapi/runtime/client"
"github.com/openziti/zrok/environment/env_core"
restEnvironment "github.com/openziti/zrok/rest_client_zrok/environment"
"github.com/pkg/errors"
)
func EnableEnvironment(root env_core.Root, request *EnableRequest) (*Environment, error) {
zrok, err := root.Client()
if err != nil {
return nil, errors.Wrap(err, "could not create zrok client")
}
auth := httptransport.APIKeyAuth("X-TOKEN", "header", root.Environment().AccountToken)
req := restEnvironment.NewEnableParams()
req.Body.Description = request.Description
req.Body.Host = request.Host
resp, err := zrok.Environment.Enable(req, auth)
if err != nil {
return nil, err
}
return &Environment{
Host: request.Host,
Description: request.Description,
ZitiIdentity: resp.Payload.Identity,
ZitiConfig: resp.Payload.Cfg,
}, nil
}
func DisableEnvironment(env *Environment, root env_core.Root) error {
zrok, err := root.Client()
if err != nil {
return errors.Wrap(err, "could not create zrok client")
}
auth := httptransport.APIKeyAuth("X-TOKEN", "header", root.Environment().AccountToken)
req := restEnvironment.NewDisableParams()
req.Body.Identity = env.ZitiIdentity
_, err = zrok.Environment.Disable(req, auth)
if err != nil {
return err
}
return nil
}

View File

@ -2,6 +2,18 @@ package sdk
import "time" import "time"
type EnableRequest struct {
Host string
Description string
}
type Environment struct {
Host string
Description string
ZitiIdentity string
ZitiConfig string
}
type BackendMode string type BackendMode string
const ( const (

View File

@ -31,6 +31,12 @@ export interface ModelConfiguration {
* @memberof ModelConfiguration * @memberof ModelConfiguration
*/ */
touLink?: string; touLink?: string;
/**
*
* @type {string}
* @memberof ModelConfiguration
*/
newAccountLink?: string;
/** /**
* *
* @type {boolean} * @type {boolean}
@ -70,6 +76,7 @@ export function ModelConfigurationFromJSONTyped(json: any, ignoreDiscriminator:
'version': json['version'] == null ? undefined : json['version'], 'version': json['version'] == null ? undefined : json['version'],
'touLink': json['touLink'] == null ? undefined : json['touLink'], 'touLink': json['touLink'] == null ? undefined : json['touLink'],
'newAccountLink': json['newAccountLink'] == null ? undefined : json['newAccountLink'],
'invitesOpen': json['invitesOpen'] == null ? undefined : json['invitesOpen'], 'invitesOpen': json['invitesOpen'] == null ? undefined : json['invitesOpen'],
'requiresInviteToken': json['requiresInviteToken'] == null ? undefined : json['requiresInviteToken'], 'requiresInviteToken': json['requiresInviteToken'] == null ? undefined : json['requiresInviteToken'],
'inviteTokenContact': json['inviteTokenContact'] == null ? undefined : json['inviteTokenContact'], 'inviteTokenContact': json['inviteTokenContact'] == null ? undefined : json['inviteTokenContact'],
@ -89,6 +96,7 @@ export function ModelConfigurationToJSONTyped(value?: ModelConfiguration | null,
'version': value['version'], 'version': value['version'],
'touLink': value['touLink'], 'touLink': value['touLink'],
'newAccountLink': value['newAccountLink'],
'invitesOpen': value['invitesOpen'], 'invitesOpen': value['invitesOpen'],
'requiresInviteToken': value['requiresInviteToken'], 'requiresInviteToken': value['requiresInviteToken'],
'inviteTokenContact': value['inviteTokenContact'], 'inviteTokenContact': value['inviteTokenContact'],

View File

@ -7,6 +7,7 @@ Name | Type | Description | Notes
------------ | ------------- | ------------- | ------------- ------------ | ------------- | ------------- | -------------
**version** | **str** | | [optional] **version** | **str** | | [optional]
**tou_link** | **str** | | [optional] **tou_link** | **str** | | [optional]
**new_account_link** | **str** | | [optional]
**invites_open** | **bool** | | [optional] **invites_open** | **bool** | | [optional]
**requires_invite_token** | **bool** | | [optional] **requires_invite_token** | **bool** | | [optional]
**invite_token_contact** | **str** | | [optional] **invite_token_contact** | **str** | | [optional]

View File

@ -37,6 +37,7 @@ class TestConfiguration(unittest.TestCase):
return Configuration( return Configuration(
version = '', version = '',
tou_link = '', tou_link = '',
new_account_link = '',
invites_open = True, invites_open = True,
requires_invite_token = True, requires_invite_token = True,
invite_token_contact = '' invite_token_contact = ''

View File

@ -28,10 +28,11 @@ class Configuration(BaseModel):
""" # noqa: E501 """ # noqa: E501
version: Optional[StrictStr] = None version: Optional[StrictStr] = None
tou_link: Optional[StrictStr] = Field(default=None, alias="touLink") tou_link: Optional[StrictStr] = Field(default=None, alias="touLink")
new_account_link: Optional[StrictStr] = Field(default=None, alias="newAccountLink")
invites_open: Optional[StrictBool] = Field(default=None, alias="invitesOpen") invites_open: Optional[StrictBool] = Field(default=None, alias="invitesOpen")
requires_invite_token: Optional[StrictBool] = Field(default=None, alias="requiresInviteToken") requires_invite_token: Optional[StrictBool] = Field(default=None, alias="requiresInviteToken")
invite_token_contact: Optional[StrictStr] = Field(default=None, alias="inviteTokenContact") invite_token_contact: Optional[StrictStr] = Field(default=None, alias="inviteTokenContact")
__properties: ClassVar[List[str]] = ["version", "touLink", "invitesOpen", "requiresInviteToken", "inviteTokenContact"] __properties: ClassVar[List[str]] = ["version", "touLink", "newAccountLink", "invitesOpen", "requiresInviteToken", "inviteTokenContact"]
model_config = ConfigDict( model_config = ConfigDict(
populate_by_name=True, populate_by_name=True,
@ -86,6 +87,7 @@ class Configuration(BaseModel):
_obj = cls.model_validate({ _obj = cls.model_validate({
"version": obj.get("version"), "version": obj.get("version"),
"touLink": obj.get("touLink"), "touLink": obj.get("touLink"),
"newAccountLink": obj.get("newAccountLink"),
"invitesOpen": obj.get("invitesOpen"), "invitesOpen": obj.get("invitesOpen"),
"requiresInviteToken": obj.get("requiresInviteToken"), "requiresInviteToken": obj.get("requiresInviteToken"),
"inviteTokenContact": obj.get("inviteTokenContact") "inviteTokenContact": obj.get("inviteTokenContact")

View File

@ -1229,6 +1229,8 @@ definitions:
type: string type: string
touLink: touLink:
type: string type: string
newAccountLink:
type: string
invitesOpen: invitesOpen:
type: boolean type: boolean
requiresInviteToken: requiresInviteToken:

View File

@ -13,14 +13,19 @@ const Login = ({ onLogin }: LoginProps) => {
const [email, setEmail] = useState(""); const [email, setEmail] = useState("");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
const [tou, setTou] = useState(null as string); const [tou, setTou] = useState<string>("");
const [newAccountLink, setNewAccountLink] = useState<string>("");
useEffect(() => { useEffect(() => {
new MetadataApi()._configuration() new MetadataApi()._configuration()
.then(d => { .then(d => {
console.log("d", d);
if(d.touLink && d.touLink.trim() !== "") { if(d.touLink && d.touLink.trim() !== "") {
setTou(d.touLink); setTou(d.touLink);
} }
if(d.newAccountLink && d.newAccountLink.trim() != "") {
setNewAccountLink(d.newAccountLink)
}
}) })
.catch(e => { .catch(e => {
console.log(e); console.log(e);
@ -86,6 +91,9 @@ const Login = ({ onLogin }: LoginProps) => {
<Box component="div" style={{ textAlign: "center" }}> <Box component="div" style={{ textAlign: "center" }}>
<Link to="/forgotPassword">Forgot Password?</Link> <Link to="/forgotPassword">Forgot Password?</Link>
</Box> </Box>
<Box component="div" style={{ textAlign: "center" }}>
<div dangerouslySetInnerHTML={{__html: newAccountLink}}></div>
</Box>
<Box component="div" style={{ textAlign: "center" }}> <Box component="div" style={{ textAlign: "center" }}>
<div dangerouslySetInnerHTML={{__html: tou}}></div> <div dangerouslySetInnerHTML={{__html: tou}}></div>
</Box> </Box>

View File

@ -70,6 +70,9 @@ const ReleaseAccessModal = ({ close, isOpen, user, access, detail }: ReleaseAcce
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center"> <Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
<Typography variant="body1">Would you like to release the access <code>{frontendToken}</code> ?</Typography> <Typography variant="body1">Would you like to release the access <code>{frontendToken}</code> ?</Typography>
</Grid2> </Grid2>
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
<Typography variant="h6" color="red">WARNING: This operation removes permissions and frees resources, but it does NOT terminate your <code>zrok access</code> process&mdash;you must do that manually.</Typography>
</Grid2>
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center"> <Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
<FormControlLabel control={<Checkbox checked={checked} onChange={toggleChecked} />} label={<p>I confirm the release of <code>{frontendToken}</code></p>} sx={{ mt: 2 }} /> <FormControlLabel control={<Checkbox checked={checked} onChange={toggleChecked} />} label={<p>I confirm the release of <code>{frontendToken}</code></p>} sx={{ mt: 2 }} />
</Grid2> </Grid2>

View File

@ -69,7 +69,7 @@ const ReleaseEnvironmentModal = ({ close, isOpen, user, environment, detail }: R
<Typography variant="body1">Would you like to release the environment <code>{description}</code> ?</Typography> <Typography variant="body1">Would you like to release the environment <code>{description}</code> ?</Typography>
</Grid2> </Grid2>
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center"> <Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
<Typography variant="body1">Releasing this environment will also release any shares and accesses that are associated with it.</Typography> <Typography variant="h6" color="red">WARNING: Releasing this environment will also release any shares and accesses that are associated with it. This operation removes permissions and frees resources, but it does NOT terminate your <code>zrok share</code> or <code>zrok access</code> processes&mdash;you must do that manually.</Typography>
</Grid2> </Grid2>
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center"> <Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
<FormControlLabel control={<Checkbox checked={checked} onChange={toggleChecked} />} label={<p>I confirm the release of <code>{description}</code></p>} sx={{ mt: 2 }} /> <FormControlLabel control={<Checkbox checked={checked} onChange={toggleChecked} />} label={<p>I confirm the release of <code>{description}</code></p>} sx={{ mt: 2 }} />

View File

@ -70,6 +70,9 @@ const ReleaseShareModal = ({ close, isOpen, user, share, detail }: ReleaseShareP
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center"> <Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
<Typography variant="body1">Would you like to release the share <code>{shareToken}</code> ?</Typography> <Typography variant="body1">Would you like to release the share <code>{shareToken}</code> ?</Typography>
</Grid2> </Grid2>
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
<Typography variant="h6" color="red">WARNING: This operation removes permissions and frees resources, but it does NOT terminate your <code>zrok share</code> process&mdash;you must do that manually.</Typography>
</Grid2>
<Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center"> <Grid2 container sx={{ flexGrow: 1, p: 1 }} alignItems="center">
<FormControlLabel control={<Checkbox checked={checked} onChange={toggleChecked} />} label={<p>I confirm the release of <code>{shareToken}</code></p>} sx={{ mt: 2 }} /> <FormControlLabel control={<Checkbox checked={checked} onChange={toggleChecked} />} label={<p>I confirm the release of <code>{shareToken}</code></p>} sx={{ mt: 2 }} />
</Grid2> </Grid2>

View File

@ -31,6 +31,12 @@ export interface ModelConfiguration {
* @memberof ModelConfiguration * @memberof ModelConfiguration
*/ */
touLink?: string; touLink?: string;
/**
*
* @type {string}
* @memberof ModelConfiguration
*/
newAccountLink?: string;
/** /**
* *
* @type {boolean} * @type {boolean}
@ -70,6 +76,7 @@ export function ModelConfigurationFromJSONTyped(json: any, ignoreDiscriminator:
'version': json['version'] == null ? undefined : json['version'], 'version': json['version'] == null ? undefined : json['version'],
'touLink': json['touLink'] == null ? undefined : json['touLink'], 'touLink': json['touLink'] == null ? undefined : json['touLink'],
'newAccountLink': json['newAccountLink'] == null ? undefined : json['newAccountLink'],
'invitesOpen': json['invitesOpen'] == null ? undefined : json['invitesOpen'], 'invitesOpen': json['invitesOpen'] == null ? undefined : json['invitesOpen'],
'requiresInviteToken': json['requiresInviteToken'] == null ? undefined : json['requiresInviteToken'], 'requiresInviteToken': json['requiresInviteToken'] == null ? undefined : json['requiresInviteToken'],
'inviteTokenContact': json['inviteTokenContact'] == null ? undefined : json['inviteTokenContact'], 'inviteTokenContact': json['inviteTokenContact'] == null ? undefined : json['inviteTokenContact'],
@ -89,6 +96,7 @@ export function ModelConfigurationToJSONTyped(value?: ModelConfiguration | null,
'version': value['version'], 'version': value['version'],
'touLink': value['touLink'], 'touLink': value['touLink'],
'newAccountLink': value['newAccountLink'],
'invitesOpen': value['invitesOpen'], 'invitesOpen': value['invitesOpen'],
'requiresInviteToken': value['requiresInviteToken'], 'requiresInviteToken': value['requiresInviteToken'],
'inviteTokenContact': value['inviteTokenContact'], 'inviteTokenContact': value['inviteTokenContact'],

4373
website/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,9 +14,9 @@
"write-heading-ids": "docusaurus write-heading-ids" "write-heading-ids": "docusaurus write-heading-ids"
}, },
"dependencies": { "dependencies": {
"@docusaurus/core": "^3.6.0", "@docusaurus/core": "^3.7.0",
"@docusaurus/plugin-client-redirects": "^3.6.0", "@docusaurus/plugin-client-redirects": "^3.7.0",
"@docusaurus/preset-classic": "^3.6.0", "@docusaurus/preset-classic": "^3.7.0",
"@mdx-js/react": "^3.0.1", "@mdx-js/react": "^3.0.1",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"prism-react-renderer": "^1.3.5", "prism-react-renderer": "^1.3.5",
@ -26,7 +26,7 @@
"remark-math": "^5.1.1" "remark-math": "^5.1.1"
}, },
"devDependencies": { "devDependencies": {
"@docusaurus/module-type-aliases": "^3.6.0", "@docusaurus/module-type-aliases": "^3.7.0",
"yaml-loader": "^0.8.0" "yaml-loader": "^0.8.0"
}, },
"browserslist": { "browserslist": {