diff --git a/assets/migrationform1.png b/assets/migrationform1.png new file mode 100644 index 0000000..b99a567 Binary files /dev/null and b/assets/migrationform1.png differ diff --git a/assets/migrationform2.png b/assets/migrationform2.png new file mode 100644 index 0000000..f9ea5c4 Binary files /dev/null and b/assets/migrationform2.png differ diff --git a/assets/migrationprogress1.png b/assets/migrationprogress1.png new file mode 100644 index 0000000..04fe2e5 Binary files /dev/null and b/assets/migrationprogress1.png differ diff --git a/assets/migrationprogress2.png b/assets/migrationprogress2.png new file mode 100644 index 0000000..a4a8ce1 Binary files /dev/null and b/assets/migrationprogress2.png differ diff --git a/assets/rpms/virt-v2v-2.7.13-1.fc42.aarch64.rpm b/assets/rpms/virt-v2v-2.7.13-1.fc42.aarch64.rpm new file mode 100644 index 0000000..2717c9d Binary files /dev/null and b/assets/rpms/virt-v2v-2.7.13-1.fc42.aarch64.rpm differ diff --git a/assets/rpms/virt-v2v-2.7.13-1.fc42.src.rpm b/assets/rpms/virt-v2v-2.7.13-1.fc42.src.rpm new file mode 100644 index 0000000..dc4ffa3 Binary files /dev/null and b/assets/rpms/virt-v2v-2.7.13-1.fc42.src.rpm differ diff --git a/assets/rpms/virt-v2v-2.7.13-1.fc42.x86_64.rpm b/assets/rpms/virt-v2v-2.7.13-1.fc42.x86_64.rpm new file mode 100644 index 0000000..35fa29d Binary files /dev/null and b/assets/rpms/virt-v2v-2.7.13-1.fc42.x86_64.rpm differ diff --git a/assets/scaledown.png b/assets/scaledown.png new file mode 100644 index 0000000..368e56b Binary files /dev/null and b/assets/scaledown.png differ diff --git a/assets/scaleup.png b/assets/scaleup.png new file mode 100644 index 0000000..e875521 Binary files /dev/null and b/assets/scaleup.png differ diff --git a/assets/scaleupagents.png b/assets/scaleupagents.png new file mode 100644 index 0000000..7321ac6 Binary files /dev/null and b/assets/scaleupagents.png differ diff --git a/deploy/00crds.yaml b/deploy/00crds.yaml new file mode 100644 index 0000000..5e037a3 --- /dev/null +++ b/deploy/00crds.yaml @@ -0,0 +1,3398 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + control-plane: controller-manager + name: migration-system +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: bmconfigs.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: BMConfig + listKind: BMConfigList + plural: bmconfigs + singular: bmconfig + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + BMConfig is the Schema for the bmconfigs API that defines authentication and configuration + details for Bare Metal Controller (BMC) providers such as MAAS. It contains credentials, + connection information, and boot source configurations needed to provision physical hosts + for use during the ESXi to PCD migration process. BMConfig enables the automatic + provisioning of PCD hosts as replacement infrastructure for migrated ESXi hosts. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BMConfigSpec defines the desired state of BMConfig + properties: + apiKey: + description: APIKey is the API key for the BM server + type: string + apiUrl: + description: APIUrl is the API URL for the BM server + type: string + bootSource: + description: BootSource is the boot source for the BMC + properties: + release: + default: jammy + description: Release is the OS release version to be used (e.g., + "jammy" for Ubuntu 22.04) + type: string + required: + - release + type: object + insecure: + default: false + description: Insecure is a boolean indicating whether to use insecure + connection + type: boolean + password: + description: Password is the password for the BM server + type: string + providerType: + default: MAAS + description: ProviderType is the BMC provider type + type: string + userDataSecretRef: + description: UserDataSecretRef is the reference to the secret containing + user data for the BMC + properties: + name: + description: name is unique within a namespace to reference a + secret resource. + type: string + namespace: + description: namespace defines the space within which the secret + name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + userName: + description: UserName is the username for the BM server + type: string + required: + - apiKey + - apiUrl + - providerType + type: object + status: + description: BMConfigStatus defines the observed state of BMConfig + properties: + validationMessage: + description: ValidationMessage is the message associated with the + validation + type: string + validationStatus: + description: ValidationStatus is the status of the validation + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: clustermigrations.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: ClusterMigration + listKind: ClusterMigrationList + plural: clustermigrations + singular: clustermigration + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .status.currentESXI + name: Current ESXI + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + ClusterMigration is the Schema for the clustermigrations API that orchestrates the migration + of an entire VMware vCenter cluster to Platform9 Distributed Cloud (PCD). + It manages the ordered migration of ESXi hosts within a cluster, tracking progress + and maintaining references to required credentials and migration plans. + ClusterMigration resources coordinate with ESXIMigration resources to ensure + hosts are migrated in the correct sequence with proper dependency management. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: ClusterMigrationSpec defines the desired state of ClusterMigration + properties: + clusterName: + description: ClusterName is the name of the vCenter cluster to be + migrated + type: string + esxiMigrationSequence: + description: ESXIMigrationSequence is the sequence of ESXi hosts to + be migrated + items: + type: string + type: array + openstackCredsRef: + description: OpenstackCredsRef is the reference to the OpenStack credentials + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + rollingMigrationPlanRef: + description: RollingMigrationPlanRef is the reference to the RollingMigrationPlan + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + vmwareCredsRef: + description: VMwareCredsRef is the reference to the VMware credentials + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + required: + - clusterName + - esxiMigrationSequence + - openstackCredsRef + - rollingMigrationPlanRef + - vmwareCredsRef + type: object + status: + description: ClusterMigrationStatus defines the observed state of ClusterMigration + properties: + currentESXi: + description: CurrentESXi is the name of the current ESXi host being + migrated + type: string + message: + description: Message is the message associated with the current state + of the migration + type: string + phase: + description: Phase is the current phase of the migration + enum: + - Pending + - Running + - Succeeded + - Failed + - Paused + type: string + required: + - currentESXi + - message + - phase + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: esximigrations.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: ESXIMigration + listKind: ESXIMigrationList + plural: esximigrations + singular: esximigration + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + ESXIMigration is the Schema for the esximigrations API that defines + the process of migrating an ESXi host to PCD, including putting it in maintenance mode, + migrating all VMs, and finally removing it from vCenter inventory after completion + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + ESXIMigrationSpec defines the desired state of ESXIMigration including + the ESXi host to migrate and the references to credentials and migration plan + properties: + esxiName: + description: ESXiName is the name of the ESXi host to be migrated + type: string + openstackCredsRef: + description: OpenstackCredsRef is the reference to the OpenStack credentials + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + rollingMigrationPlanRef: + description: RollingMigrationPlanRef is the reference to the RollingMigrationPlan + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + vmwareCredsRef: + description: VMwareCredsRef is the reference to the VMware credentials + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + required: + - esxiName + - openstackCredsRef + - rollingMigrationPlanRef + - vmwareCredsRef + type: object + status: + description: |- + ESXIMigrationStatus defines the observed state of ESXIMigration including + the list of VMs on the host, current phase, and status messages + properties: + message: + description: Message is the message associated with the current state + of the migration + type: string + phase: + description: |- + Phase is the current phase of the migration lifecycle + The final phases include 'Succeeded' when the ESXi host has been successfully + removed from vCenter inventory after migration is complete + type: string + vms: + description: VMs is the list of VMs present on the ESXi host + items: + type: string + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: migrationplans.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: MigrationPlan + listKind: MigrationPlanList + plural: migrationplans + singular: migrationplan + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.migrationStatus + name: Status + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + MigrationPlan is the Schema for the migrationplans API that defines + how to migrate virtual machines from VMware to OpenStack including migration strategy and scheduling. + It allows administrators to configure migration parameters such as timing, health checks, + and VM-specific settings for bulk VM migration operations between environments. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + MigrationPlanSpec defines the desired state of MigrationPlan including + the migration template, strategy, and the list of virtual machines to migrate + properties: + advancedOptions: + description: AdvancedOptions is a list of advanced options for the + migration + properties: + granularNetworks: + description: GranularNetworks is a list of networks to be migrated + items: + type: string + type: array + granularPorts: + description: GranularPorts is a list of ports to be migrated + items: + type: string + type: array + granularVolumeTypes: + description: GranularVolumeTypes is a list of volume types to + be migrated + items: + type: string + type: array + type: object + firstBootScript: + default: echo "Add your startup script here!" + type: string + migrationStrategy: + description: MigrationStrategy is the strategy to be used for the + migration + properties: + adminInitiatedCutOver: + default: false + type: boolean + dataCopyStart: + format: date-time + type: string + healthCheckPort: + default: "443" + type: string + performHealthChecks: + default: false + type: boolean + type: + enum: + - hot + - cold + type: string + vmCutoverEnd: + format: date-time + type: string + vmCutoverStart: + format: date-time + type: string + required: + - type + type: object + migrationTemplate: + description: MigrationTemplate is the template to be used for the + migration + type: string + postMigrationAction: + description: PostMigrationAction defines the post migration action + for the virtual machine + properties: + folderName: + type: string + moveToFolder: + type: boolean + renameVm: + type: boolean + suffix: + type: string + type: object + retry: + description: Retry the migration if it fails + type: boolean + virtualMachines: + description: VirtualMachines is a list of virtual machines to be migrated + items: + items: + type: string + type: array + type: array + required: + - migrationStrategy + - migrationTemplate + - virtualMachines + type: object + status: + description: |- + MigrationPlanStatus defines the observed state of MigrationPlan including + the current status and progress of the migration + properties: + migrationMessage: + description: MigrationMessage is the message associated with the migration + type: string + migrationStatus: + description: |- + MigrationStatus is the status of the migration using Kubernetes PodPhase states + (Pending, Running, Succeeded, Failed, Unknown) + type: string + required: + - migrationMessage + - migrationStatus + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: migrations.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: Migration + listKind: MigrationList + plural: migrations + singular: migration + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .status.agentName + name: Agent Name + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + Migration is the Schema for the migrations API that represents a single virtual machine + migration job from VMware to OpenStack. It tracks the complete migration lifecycle + including validation, data transfer, disk conversion, and cutover phases. Migration resources + provide detailed status monitoring, error handling, and manual intervention points like + cutover initiation. Each Migration is associated with a MigrationPlan and executes on a specific + VjailbreakNode agent, tracking progress via status updates and condition changes. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of Migration + properties: + initiateCutover: + description: InitiateCutover is the flag to initiate cutover + type: boolean + migrationPlan: + description: MigrationPlan is the name of the migration plan + type: string + podRef: + description: PodRef is the name of the pod + type: string + vmName: + description: VMName is the name of the VM getting migrated from VMWare + to Openstack + type: string + required: + - initiateCutover + - migrationPlan + - podRef + - vmName + type: object + status: + description: Status defines the observed state of Migration + properties: + agentName: + description: AgentName is the name of the agent where migration is + running + type: string + conditions: + description: Conditions is the list of conditions of the migration + object pod + items: + description: PodCondition contains details for the current condition + of this pod. + properties: + lastProbeTime: + description: Last time we probed the condition. + format: date-time + type: string + lastTransitionTime: + description: Last time the condition transitioned from one status + to another. + format: date-time + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + observedGeneration: + description: |- + If set, this represents the .metadata.generation that the pod condition was set based upon. + This is an alpha field. Enable PodObservedGenerationTracking to be able to use this field. + format: int64 + type: integer + reason: + description: Unique, one-word, CamelCase reason for the condition's + last transition. + type: string + status: + description: |- + Status is the status of the condition. + Can be True, False, Unknown. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions + type: string + type: + description: |- + Type is the type of the condition. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions + type: string + required: + - status + - type + type: object + type: array + phase: + description: Phase is the current phase of the migration + enum: + - Pending + - Validating + - AwaitingDataCopyStart + - CopyingBlocks + - CopyingChangedBlocks + - ConvertingDisk + - AwaitingCutOverStartTime + - AwaitingAdminCutOver + - Succeeded + - Failed + - Unknown + type: string + required: + - phase + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: migrationtemplates.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: MigrationTemplate + listKind: MigrationTemplateList + plural: migrationtemplates + singular: migrationtemplate + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + MigrationTemplate is the Schema for the migrationtemplates API that defines how VMs should be migrated + from VMware to OpenStack including network and storage mappings. It serves as a reusable template + that can be referenced by multiple migration plans, providing configuration for source and destination + environments, OS-specific settings, and network/storage mappings. Migration templates enable consistent + configuration across multiple VM migrations and simplify the definition of migration plans. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: MigrationTemplateSpec defines the desired state of MigrationTemplate + including source/destination environments and mappings + properties: + destination: + description: Destination is the destination details for the virtual + machine + properties: + openstackRef: + description: OpenstackRef is the reference to the OpenStack credentials + to be used as the destination environment + type: string + required: + - openstackRef + type: object + networkMapping: + description: NetworkMapping is the reference to the NetworkMapping + resource that defines source to destination network mappings + type: string + osFamily: + description: OSFamily is the OS type of the virtual machine + enum: + - windows + - linux + type: string + source: + description: Source is the source details for the virtual machine + properties: + vmwareRef: + description: VMwareRef is the reference to the VMware credentials + to be used as the source environment + type: string + required: + - vmwareRef + type: object + storageMapping: + description: StorageMapping is the reference to the StorageMapping + resource that defines source to destination storage mappings + type: string + targetPCDClusterName: + description: TargetPCDClusterName is the name of the PCD cluster where + the virtual machine will be migrated + type: string + virtioWinDriver: + description: VirtioWinDriver is the driver to be used for the virtual + machine + type: string + required: + - destination + - networkMapping + - source + - storageMapping + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: networkmappings.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: NetworkMapping + listKind: NetworkMappingList + plural: networkmappings + singular: networkmapping + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.networkMappingValidationStatus + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + NetworkMapping is the Schema for the networkmappings API that defines + mappings between VMware and OpenStack networks to be used during migration + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + NetworkMappingSpec defines the desired state of NetworkMapping including + mappings between VMware and OpenStack networks + properties: + networks: + description: Networks is the list of network mappings between source + (VMware) and target (OpenStack) environments + items: + description: Network represents a mapping between source and target + networks + properties: + source: + description: Source is the name of the source network in VMware + type: string + target: + description: Target is the name of the target network in OpenStack + type: string + required: + - source + - target + type: object + type: array + required: + - networks + type: object + status: + description: NetworkMappingStatus defines the observed state of NetworkMapping + properties: + networkMappingValidationMessage: + description: |- + NetworkmappingValidationMessage provides detailed validation information including + information about available networks and any validation errors + type: string + networkMappingValidationStatus: + description: |- + NetworkmappingValidationStatus indicates the validation status of the network mapping + Valid states include: "Valid", "Invalid", "Pending", "ValidationFailed" + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: openstackcreds.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: OpenstackCreds + listKind: OpenstackCredsList + plural: openstackcreds + singular: openstackcreds + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.openstackValidationStatus + name: Status + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + OpenstackCreds is the Schema for the OpenStack credentials API that defines authentication + and connection details for OpenStack environments. It provides a secure way to store and validate + OpenStack credentials for use in migration operations, including authentication information, + available compute flavors, volume types, networks, and Platform9 Distributed Cloud host configurations. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: OpenstackCredsSpec defines the desired state of OpenstackCreds + properties: + flavors: + description: Flavors is the list of available flavors in openstack + items: + description: |- + Flavor represent (virtual) hardware configurations for server resources + in a region. + properties: + OS-FLV-EXT-DATA:ephemeral: + description: Ephemeral is the amount of ephemeral disk space, + measured in GB. + type: integer + description: + description: |- + Description is a free form description of the flavor. Limited to + 65535 characters in length. Only printable characters are allowed. + New in version 2.55 + type: string + disk: + description: Disk is the amount of root disk, measured in GB. + type: integer + id: + description: ID is the flavor's unique ID. + type: string + name: + description: Name is the name of the flavor. + type: string + os-flavor-access:is_public: + description: IsPublic indicates whether the flavor is public. + type: boolean + ram: + description: RAM is the amount of memory, measured in MB. + type: integer + rxtx_factor: + description: RxTxFactor describes bandwidth alterations of the + flavor. + type: number + vcpus: + description: VCPUs indicates how many (virtual) CPUs are available + for this flavor. + type: integer + required: + - OS-FLV-EXT-DATA:ephemeral + - description + - disk + - id + - name + - os-flavor-access:is_public + - ram + - rxtx_factor + - vcpus + type: object + type: array + pcdHostConfig: + description: PCDHostConfig is the list of available clusters in openstack + items: + description: HostConfig defines the configuration for a Platform9 + Distributed Cloud host + properties: + clusterName: + type: string + hostLivenessInterface: + type: string + id: + type: string + imagelibInterface: + type: string + mgmtInterface: + type: string + name: + type: string + networkLabels: + additionalProperties: + type: string + type: object + tunnelingInterface: + type: string + vmConsoleInterface: + type: string + type: object + type: array + secretRef: + description: SecretRef is the reference to the Kubernetes secret holding + OpenStack credentials + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + type: object + status: + description: OpenstackCredsStatus defines the observed state of OpenstackCreds + properties: + openstack: + description: Openstack is the OpenStack configuration for the openstackcreds + properties: + networks: + items: + type: string + type: array + volumeTypes: + items: + type: string + type: array + type: object + openstackValidationMessage: + description: OpenStackValidationMessage is the message associated + with the OpenStack validation + type: string + openstackValidationStatus: + description: OpenStackValidationStatus is the status of the OpenStack + validation + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: pcdclusters.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: PCDCluster + listKind: PCDClusterList + plural: pcdclusters + singular: pcdcluster + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + PCDCluster is the Schema for the pcdclusters API that represents a Platform9 Distributed Cloud + cluster in the migration system. It defines cluster configuration including host membership, + high availability settings, and resource rebalancing policies. PCDCluster resources track both + the desired state of clusters as part of migration planning and the observed state of actual + PCD clusters in the target environment. They serve as destination targets for VM migrations + from VMware ESXi environments. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: PCDClusterSpec defines the desired state of PCDCluster + properties: + clusterName: + description: ClusterName is the name of the PCD cluster + type: string + description: + description: Description is the description of the PCD cluster + type: string + enableAutoResourceRebalancing: + description: EnableAutoResourceRebalancing indicates if auto resource + rebalancing is enabled + type: boolean + hosts: + description: Hosts is the list of hosts in the PCD cluster + items: + type: string + type: array + rebalancingFrequencyMins: + description: RebalancingFrequencyMins defines how often rebalancing + occurs in minutes + type: integer + vmHighAvailability: + description: VMHighAvailability indicates if VM high availability + is enabled + type: boolean + type: object + status: + description: PCDClusterStatus defines the observed state of PCDCluster + properties: + aggregateID: + description: AggregateID is the aggregate ID in the PCD cluster + type: integer + clusterID: + description: ClusterID is the ID of the PCD cluster + type: string + createdAt: + description: CreatedAt indicates when the cluster was created + type: string + updatedAt: + description: UpdatedAt indicates when the cluster was last updated + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: pcdhosts.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: PCDHost + listKind: PCDHostList + plural: pcdhosts + singular: pcdhost + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + PCDHost is the Schema for the pcdhosts API that represents a physical or virtual host + in a Platform9 Distributed Cloud environment. It tracks the host's configuration, + network interfaces, assigned roles, and operational status. PCDHost resources are created + as part of the migration process when converting ESXi hosts to PCD hosts or when provisioning + new infrastructure to replace migrated VMware hosts. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: PCDHostSpec defines the desired state of PCDHost + properties: + arch: + type: string + hostID: + description: HostID is the ID of the host + type: string + hostName: + description: HostName is the name of the host + type: string + hostState: + description: HostState is the state of the host + type: string + interfaces: + items: + description: |- + PCDHostInterface defines the network interface configuration for a Platform9 Distributed Cloud host, + including IP addresses, MAC address, and interface name. It's used to configure proper network + connectivity for PCD hosts created during the migration process. + properties: + ipAddresses: + items: + type: string + type: array + macAddress: + type: string + name: + type: string + type: object + type: array + osFamily: + type: string + osInfo: + type: string + rolesAssigned: + description: RolesAssigned is the list of roles assigned to the host + items: + type: string + type: array + type: object + status: + description: PCDHostStatus defines the observed state of PCDHost + properties: + responding: + type: boolean + roleStatus: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: rollingmigrationplans.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: RollingMigrationPlan + listKind: RollingMigrationPlanList + plural: rollingmigrationplans + singular: rollingmigrationplan + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + RollingMigrationPlan is the Schema for the rollingmigrationplans API that defines a coordinated + migration of multiple VMware clusters and ESXi hosts to Platform9 Distributed Cloud (PCD). + It supports sequenced migration of VMs across clusters with configurable batch sizes, + cluster-to-cluster mapping, and tracking of migration progress across the entire datacenter migration. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: RollingMigrationPlanSpec defines the desired state of RollingMigrationPlan + properties: + advancedOptions: + description: AdvancedOptions is a list of advanced options for the + migration + properties: + granularNetworks: + description: GranularNetworks is a list of networks to be migrated + items: + type: string + type: array + granularPorts: + description: GranularPorts is a list of ports to be migrated + items: + type: string + type: array + granularVolumeTypes: + description: GranularVolumeTypes is a list of volume types to + be migrated + items: + type: string + type: array + type: object + bmConfigRef: + description: BMConfigRef is the reference to the BMC credentials + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + cloudInitConfigRef: + description: CloudInitConfigRef is the reference to the cloud-init + configuration + properties: + name: + description: name is unique within a namespace to reference a + secret resource. + type: string + namespace: + description: namespace defines the space within which the secret + name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + clusterMapping: + description: ClusterMapping is the mapping of vCenter clusters to + PCD clusters + items: + description: |- + ClusterMapping defines the relationship between a VMware vCenter cluster and its corresponding + Platform9 Distributed Cloud (PCD) cluster for migration operations. This mapping ensures that + virtual machines are properly migrated to the appropriate target infrastructure. + properties: + pcdClusterName: + description: PCDClusterName is the name of the PCD cluster + type: string + vmwareClusterName: + description: VMwareClusterName is the name of the vCenter cluster + type: string + required: + - pcdClusterName + - vmwareClusterName + type: object + type: array + clusterSequence: + description: ClusterSequence is the sequence of vCenter clusters to + be migrated + items: + description: |- + ClusterMigrationInfo defines information about a VMware vCenter cluster migration, + including the cluster name and the sequence of virtual machines to be migrated. + This structure allows for coordinated migration of multiple related VMs within a cluster. + properties: + clusterName: + description: ClusterName is the name of the vCenter cluster + to be migrated + type: string + vmMigrationBatchSize: + default: 10 + description: |- + VMMigrationBatchSize is the number of VMs in one batch for migration + batches will be processed sequentially, but all VMs in a batch + will be migrated in parallel. Default is 10 + minimum: 1 + type: integer + vmSequence: + description: VMSequence is the sequence of virtual machines + to be migrated + items: + description: |- + VMSequenceInfo defines information about a virtual machine in the migration sequence, + including its name and the ESXi host where it is located. This information is used to + establish the proper order and grouping of VMs during the migration process. + properties: + esxiName: + description: ESXiName is the name of the ESXi host where + the virtual machine is located + type: string + vmName: + description: VMName is the name of the virtual machine + to be migrated + type: string + required: + - vmName + type: object + type: array + required: + - clusterName + - vmSequence + type: object + type: array + firstBootScript: + default: echo "Add your startup script here!" + type: string + migrationStrategy: + description: MigrationStrategy is the strategy to be used for the + migration + properties: + adminInitiatedCutOver: + default: false + type: boolean + dataCopyStart: + format: date-time + type: string + healthCheckPort: + default: "443" + type: string + performHealthChecks: + default: false + type: boolean + type: + enum: + - hot + - cold + type: string + vmCutoverEnd: + format: date-time + type: string + vmCutoverStart: + format: date-time + type: string + required: + - type + type: object + migrationTemplate: + description: MigrationTemplate is the template to be used for the + migration + type: string + postMigrationAction: + description: PostMigrationAction defines the post migration action + for the virtual machine + properties: + folderName: + type: string + moveToFolder: + type: boolean + renameVm: + type: boolean + suffix: + type: string + type: object + retry: + description: Retry the migration if it fails + type: boolean + vmMigrationPlans: + description: VMMigrationPlans is the reference to the VM migration + plan + items: + type: string + type: array + required: + - bmConfigRef + - clusterSequence + - migrationStrategy + - migrationTemplate + type: object + status: + description: RollingMigrationPlanStatus defines the observed state of + RollingMigrationPlan + properties: + currentCluster: + description: CurrentCluster is the name of the current vCenter cluster + being migrated + type: string + currentESXi: + description: CurrentESXi is the name of the current ESXi host being + migrated + type: string + currentVM: + description: CurrentVM is the name of the current virtual machine + being migrated + type: string + failedClusters: + description: FailedClusters is the list of vCenter clusters that have + failed to migrate + items: + type: string + type: array + failedESXi: + description: FailedESXi is the list of ESXi hosts that have failed + to migrate + items: + type: string + type: array + failedVMs: + description: FailedVMs is the list of virtual machines that have failed + to migrate + items: + type: string + type: array + message: + description: Message is the message associated with the current state + of the migration + type: string + migratedClusters: + description: MigratedClusters is the list of vCenter clusters that + have been migrated + items: + type: string + type: array + migratedESXi: + description: MigratedESXi is the list of ESXi hosts that have been + migrated + items: + type: string + type: array + migratedVMs: + description: MigratedVMs is the list of virtual machines that have + been migrated + items: + type: string + type: array + phase: + description: Phase is the current phase of the migration + type: string + vmMigrationPhase: + description: VMMigrationsPhase is the list of VM migration plans + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: storagemappings.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: StorageMapping + listKind: StorageMappingList + plural: storagemappings + singular: storagemapping + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.storageMappingValidationStatus + name: Status + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + StorageMapping is the Schema for the storagemappings API that defines + mappings between VMware and OpenStack storage types to be used during migration + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + StorageMappingSpec defines the desired state of StorageMapping including + mappings between VMware and OpenStack storage types + properties: + storages: + description: Storages is a list of storage mappings between source + (VMware) and target (OpenStack) environments + items: + description: Storage represents a mapping between source and target + storage types + properties: + source: + description: Source is the name of the source storage type in + VMware + type: string + target: + description: Target is the name of the target storage type in + OpenStack + type: string + required: + - source + - target + type: object + type: array + required: + - storages + type: object + status: + description: StorageMappingStatus defines the observed state of StorageMapping + properties: + storageMappingValidationMessage: + description: |- + StoragemappingValidationMessage provides detailed validation information including + information about available storage types and any validation errors + type: string + storageMappingValidationStatus: + description: |- + StoragemappingValidationStatus indicates the validation status of the storage mapping + Valid states include: "Valid", "Invalid", "Pending", "ValidationFailed" + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: vjailbreaknodes.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: VjailbreakNode + listKind: VjailbreakNodeList + plural: vjailbreaknodes + singular: vjailbreaknode + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.phase + name: Phase + type: string + - jsonPath: .status.vmIP + name: VMIP + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + VjailbreakNode is the Schema for the vjailbreaknodes API that represents + a node in the migration infrastructure with configuration, resource limits, + and statistics for monitoring migration progress + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of VjailbreakNode + properties: + nodeRole: + description: NodeRole is the role assigned to the node (e.g., "migration-worker", + "controller") + type: string + openstackCreds: + description: |- + OpenstackCreds is the reference to the credentials for the OpenStack environment + where the node will be provisioned + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + openstackFlavorID: + description: OpenstackFlavorID is the flavor of the VM + type: string + openstackImageID: + description: OpenstackImageID is the image of the VM + type: string + required: + - nodeRole + - openstackCreds + - openstackFlavorID + - openstackImageID + type: object + status: + description: Status defines the observed state of VjailbreakNode + properties: + activeMigrations: + description: |- + ActiveMigrations is the list of active migrations currently being processed on this node, + containing references to MigrationPlan resources + items: + type: string + type: array + openstackUUID: + description: OpenstackUUID is the UUID of the VM in OpenStack + type: string + phase: + description: |- + Phase is the current lifecycle phase of the node + (e.g., Provisioning, Ready, Error, Decommissioning) + type: string + vmIP: + description: VMIP is the IP address of the VM + type: string + required: + - vmIP + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: vmwareclusters.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: VMwareCluster + listKind: VMwareClusterList + plural: vmwareclusters + singular: vmwarecluster + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + VMwareCluster is the Schema for the vmwareclusters API that represents a VMware vSphere cluster + in the source environment. It tracks cluster configuration, member hosts, and migration status + as part of the VMware to Platform9 Distributed Cloud migration process. VMwareCluster resources + serve as source components that are migrated to corresponding PCDCluster resources in the target environment. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: VMwareClusterSpec defines the desired state of VMwareCluster + properties: + hosts: + description: Hosts is the list of hosts in the VMware cluster + items: + type: string + type: array + name: + description: Name is the name of the VMware cluster + type: string + type: object + status: + description: VMwareClusterStatus defines the observed state of VMwareCluster + properties: + phase: + description: Phase is the current phase of the VMwareCluster + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: vmwarecreds.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: VMwareCreds + listKind: VMwareCredsList + plural: vmwarecreds + singular: vmwarecreds + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.vmwareValidationStatus + name: Status + type: string + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + VMwareCreds is the Schema for the vmwarecreds API that defines authentication + and connection details for VMware vSphere environments. It provides a secure way to + store and validate vCenter credentials for use in migration operations, including + connection parameters, authentication information, and datacenter configuration. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: VMwareCredsSpec defines the desired state of VMwareCreds + properties: + datacenter: + description: DataCenter is the datacenter for the virtual machine + type: string + secretRef: + description: SecretRef is the reference to the Kubernetes secret holding + VMware credentials + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + required: + - datacenter + type: object + status: + description: VMwareCredsStatus defines the observed state of VMwareCreds + properties: + vmwareValidationMessage: + description: VMwareValidationMessage is the message associated with + the VMware validation + type: string + vmwareValidationStatus: + description: VMwareValidationStatus is the status of the VMware validation + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: vmwarehosts.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: VMwareHost + listKind: VMwareHostList + plural: vmwarehosts + singular: vmwarehost + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + VMwareHost is the Schema for the vmwarehosts API that represents a VMware ESXi host + in the source environment. It tracks host configuration, hardware identification, and cluster membership + as part of the VMware to Platform9 Distributed Cloud migration process. VMwareHost resources are + source components that are migrated to PCDHost resources in the target environment and are automatically + removed from vCenter inventory after all VMs have been migrated off and the host is in maintenance mode. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: VMwareHostSpec defines the desired state of VMwareHost + properties: + clusterName: + description: Cluster name of the host + type: string + hardwareUuid: + description: Hardware UUID of the host + type: string + hostConfigId: + description: Host config ID of the host + type: string + name: + description: Name of the host + type: string + type: object + status: + description: VMwareHostStatus defines the observed state of VMwareHost + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.1 + name: vmwaremachines.vjailbreak.k8s.pf9.io +spec: + group: vjailbreak.k8s.pf9.io + names: + kind: VMwareMachine + listKind: VMwareMachineList + plural: vmwaremachines + singular: vmwaremachine + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + VMwareMachine is the Schema for the vmwaremachines API that represents a virtual machine + in the VMware source environment targeted for migration. It tracks VM configuration, + resource allocation, migration status, and target environment specifications. + VMwareMachine resources are the primary workloads migrated from VMware to OpenStack + as part of the migration process and contain all necessary information to recreate + equivalent virtual machines in the target environment. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: VMwareMachineSpec defines the desired state of VMwareMachine + properties: + targetFlavorId: + description: TargetFlavorId is the flavor to be used to create the + target VM on openstack + type: string + vms: + description: VMInfo is the info of the VMs in the VMwareMachine + properties: + assignedIp: + description: AssignedIp is the IP address assigned to the VM + type: string + clusterName: + description: ClusterName is the name of the cluster + type: string + cpu: + description: CPU is the number of CPUs in the virtual machine + type: integer + datastores: + description: Datastores is the list of datastores for the virtual + machine + items: + type: string + type: array + disks: + description: Disks is the list of disks for the virtual machine + items: + type: string + type: array + esxiName: + description: ESXiName is the name of the ESXi host + type: string + ipAddress: + description: IPAddress is the IP address of the virtual machine + type: string + memory: + description: Memory is the amount of memory in the virtual machine + type: integer + name: + description: Name is the name of the virtual machine + type: string + networks: + description: Networks is the list of networks for the virtual + machine + items: + type: string + type: array + osFamily: + description: OSFamily is the OS family of the virtual machine + type: string + vmState: + description: VMState is the state of the virtual machine + type: string + required: + - name + type: object + type: object + status: + description: VMwareMachineStatus defines the observed state of VMwareMachine + properties: + migrated: + default: false + description: Migrated flag to indicate if the VMs have been migrated + type: boolean + powerState: + description: PowerState is the state of the VMs in the VMware + type: string + required: + - migrated + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-controller-manager + namespace: migration-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-leader-election-role + namespace: migration-system +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-bmconfig-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - bmconfigs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - bmconfigs/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-bmconfig-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - bmconfigs + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - bmconfigs/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-clustermigration-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - clustermigrations + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - clustermigrations/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-clustermigration-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - clustermigrations + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - clustermigrations/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-esximigration-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - esximigrations + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - esximigrations/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-esximigration-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - esximigrations + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - esximigrations/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: migration-manager-role +rules: +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - events + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - nodes + verbs: + - delete + - get + - list + - watch +- apiGroups: + - "" + resources: + - pods/status + verbs: + - get + - patch + - update +- apiGroups: + - batch + resources: + - jobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - bmconfigs + - clustermigrations + - esximigrations + - migrationplans + - migrations + - migrationtemplates + - networkmappings + - openstackcreds + - pcdclusters + - pcdhosts + - rollingmigrationplans + - storagemappings + - vjailbreaknodes + - vmwareclusters + - vmwarecreds + - vmwarehosts + - vmwaremachines + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - bmconfigs/finalizers + - clustermigrations/finalizers + - esximigrations/finalizers + - migrationplans/finalizers + - migrationtemplates/finalizers + - networkmappings/finalizers + - openstackcreds/finalizers + - pcdclusters/finalizers + - pcdhosts/finalizers + - rollingmigrationplans/finalizers + - storagemappings/finalizers + - vjailbreaknodes/finalizers + - vmwarecreds/finalizers + verbs: + - update +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - bmconfigs/status + - clustermigrations/status + - esximigrations/status + - migrationplans/status + - migrations/status + - migrationtemplates/status + - networkmappings/status + - openstackcreds/status + - pcdclusters/status + - pcdhosts/status + - rollingmigrationplans/status + - storagemappings/status + - vjailbreaknodes/status + - vmwarecreds/status + - vmwaremachines/status + verbs: + - get + - patch + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-migration-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - migrations + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - migrations/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-migration-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - migrations + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - migrations/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-migrationplan-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - migrationplans + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - migrationplans/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-migrationplan-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - migrationplans + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - migrationplans/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-migrationtemplate-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - migrationtemplates + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - migrationtemplates/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-migrationtemplate-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - migrationtemplates + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - migrationtemplates/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-networkmapping-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - networkmappings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - networkmappings/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-networkmapping-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - networkmappings + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - networkmappings/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-openstackcreds-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - openstackcreds + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - openstackcreds/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-openstackcreds-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - openstackcreds + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - openstackcreds/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-pcdcluster-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - pcdclusters + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - pcdclusters/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-pcdcluster-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - pcdclusters + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - pcdclusters/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-pcdhost-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - pcdhosts + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - pcdhosts/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-pcdhost-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - pcdhosts + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - pcdhosts/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-rollingmigrationplan-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - rollingmigrationplans + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - rollingmigrationplans/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-rollingmigrationplan-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - rollingmigrationplans + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - rollingmigrationplans/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-storagemapping-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - storagemappings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - storagemappings/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-storagemapping-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - storagemappings + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - storagemappings/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-vjailbreaknode-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vjailbreaknodes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vjailbreaknodes/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-vjailbreaknode-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vjailbreaknodes + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vjailbreaknodes/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-vmwarecluster-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwareclusters + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwareclusters/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-vmwarecluster-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwareclusters + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwareclusters/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-vmwarecreds-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwarecreds + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwarecreds/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-vmwarecreds-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwarecreds + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwarecreds/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-vmwarehost-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwarehosts + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwarehosts/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-vmwarehost-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwarehosts + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwarehosts/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-vmwaremachine-editor-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwaremachines + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwaremachines/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-vmwaremachine-viewer-role +rules: +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwaremachines + verbs: + - get + - list + - watch +- apiGroups: + - vjailbreak.k8s.pf9.io + resources: + - vmwaremachines/status + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-leader-election-rolebinding + namespace: migration-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: migration-leader-election-role +subjects: +- kind: ServiceAccount + name: migration-controller-manager + namespace: migration-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + name: migration-manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: migration-manager-role +subjects: +- kind: ServiceAccount + name: migration-controller-manager + namespace: migration-system +--- +apiVersion: v1 +kind: Service +metadata: + name: migration-vpwned-service + namespace: migration-system +spec: + ports: + - port: 80 + protocol: TCP + targetPort: 3001 + selector: + app: vpwned-sdk + type: ClusterIP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/name: migration + control-plane: controller-manager + name: migration-controller-manager + namespace: migration-system +spec: + replicas: 1 + selector: + matchLabels: + control-plane: controller-manager + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + control-plane: controller-manager + spec: + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: Exists + containers: + - args: + - --leader-elect=false + - --health-probe-bind-address=:8081 + command: + - /manager + image: platform9/vjailbreak-controller:v0.1.13 + imagePullPolicy: IfNotPresent + lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - sleep 5 + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + name: manager + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + requests: + cpu: 200m + memory: 256Mi + volumeMounts: + - mountPath: /etc/pf9/k3s + name: master-token + - mountPath: /home/ubuntu + name: vddk + hostNetwork: true + securityContext: + runAsGroup: 0 + runAsUser: 0 + serviceAccountName: migration-controller-manager + terminationGracePeriodSeconds: 30 + volumes: + - hostPath: + path: /var/lib/rancher/k3s/server + type: Directory + name: master-token + - hostPath: + path: /home/ubuntu + type: Directory + name: vddk +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: vpwned-sdk + name: migration-vpwned-sdk + namespace: migration-system +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: vpwned-sdk + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: vpwned-sdk + spec: + containers: + - image: platform9/vjailbreak-vpwned:v0.1.13 + imagePullPolicy: IfNotPresent + name: vpwned + ports: + - containerPort: 3001 + protocol: TCP + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + nginx.ingress.kubernetes.io/backend-protocol: HTTP + nginx.ingress.kubernetes.io/rewrite-target: /$1 + nginx.ingress.kubernetes.io/use-regex: "true" + name: migration-vpwned-ingress + namespace: migration-system +spec: + ingressClassName: nginx + rules: + - http: + paths: + - backend: + service: + name: migration-vpwned-service + port: + number: 80 + path: /dev-api/sdk/(.*) + pathType: ImplementationSpecific diff --git a/deploy/01openstackcreds.yaml b/deploy/01openstackcreds.yaml new file mode 100644 index 0000000..85ba829 --- /dev/null +++ b/deploy/01openstackcreds.yaml @@ -0,0 +1,12 @@ +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: OpenstackCreds +metadata: + name: openstackcreds + namespace: migration-system +spec: + OS_AUTH_URL: + OS_DOMAIN_NAME: + OS_USERNAME: + OS_PASSWORD: + OS_REGION_NAME: + OS_TENANT_NAME: \ No newline at end of file diff --git a/deploy/01vmwarecreds.yaml b/deploy/01vmwarecreds.yaml new file mode 100644 index 0000000..e499315 --- /dev/null +++ b/deploy/01vmwarecreds.yaml @@ -0,0 +1,10 @@ +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: VMwareCreds +metadata: + name: vmwarecreds + namespace: migration-system +spec: + VCENTER_HOST: + VCENTER_INSECURE: true + VCENTER_PASSWORD: + VCENTER_USERNAME: \ No newline at end of file diff --git a/deploy/02networkmapping.yaml b/deploy/02networkmapping.yaml new file mode 100644 index 0000000..d7441b5 --- /dev/null +++ b/deploy/02networkmapping.yaml @@ -0,0 +1,11 @@ +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: NetworkMapping +metadata: + name: networkmapping + namespace: migration-system +spec: + networks: + - source: VM Network + target: vlan1 + - source: VM Network 2 + target: vlan2 diff --git a/deploy/02storagemapping.yaml b/deploy/02storagemapping.yaml new file mode 100644 index 0000000..52b5133 --- /dev/null +++ b/deploy/02storagemapping.yaml @@ -0,0 +1,9 @@ +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: StorageMapping +metadata: + name: storagemapping + namespace: migration-system +spec: + storages: + - source: vcenter-datastore-1 + target: lvm \ No newline at end of file diff --git a/deploy/03migrationtemplate.yaml b/deploy/03migrationtemplate.yaml new file mode 100644 index 0000000..bfb2763 --- /dev/null +++ b/deploy/03migrationtemplate.yaml @@ -0,0 +1,14 @@ +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: MigrationTemplate +metadata: + name: migrationtemplate-windows + namespace: migration-system +spec: + networkMapping: networkmapping + storageMapping: storagemapping + osType: windows + source: + datacenter: Datacenter Name + vmwareRef: vmwarecreds + destination: + openstackRef: openstackcreds \ No newline at end of file diff --git a/deploy/04migrationplan.yaml b/deploy/04migrationplan.yaml new file mode 100644 index 0000000..8f086b5 --- /dev/null +++ b/deploy/04migrationplan.yaml @@ -0,0 +1,18 @@ +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: MigrationPlan +metadata: + name: vm-migration-windows-app + namespace: migration-system +spec: + migrationTemplate: migrationtemplate-windows + retry: true + migrationStrategy: + type: hot + dataCopyStart: 2024-08-2717:30:25.230Z + vmCutoverStart: 2024-08-27T17:30:25.230Z + vmCutoverEnd: 2024-08-28T17:30:25.230Z + virtualmachines: + - - winserver2k12 + - winserver2k26 + - - winserver2k16 + - winserver2k19 \ No newline at end of file diff --git a/deploy/04ui.yaml b/deploy/04ui.yaml new file mode 100644 index 0000000..54fc65d --- /dev/null +++ b/deploy/04ui.yaml @@ -0,0 +1,85 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: vjailbreak-ui + namespace: migration-system +spec: + replicas: 1 + selector: + matchLabels: + app: vjailbreak-ui + template: + metadata: + labels: + app: vjailbreak-ui + spec: + serviceAccountName: migration-controller-manager + containers: + - name: vjailbreak-ui-container + image: platform9/vjailbreak-ui:v0.1 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: vjailbreak-ui-service + namespace: migration-system +spec: + selector: + app: vjailbreak-ui + type: ClusterIP + ports: + - protocol: TCP + port: 80 + targetPort: 80 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: vjailbreak-ui-ingress + namespace: migration-system + annotations: + nginx.ingress.kubernetes.io/backend-protocol: "HTTP" +spec: + ingressClassName: nginx + rules: + - http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: vjailbreak-ui-service + port: + number: 80 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: vjailbreak-api-ingress + namespace: default + annotations: + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" + nginx.ingress.kubernetes.io/rewrite-target: /$1 + nginx.ingress.kubernetes.io/use-regex: "true" +spec: + ingressClassName: nginx + rules: + - http: + paths: + - path: /(apis.*) + pathType: ImplementationSpecific + backend: + service: + name: kubernetes + port: + number: 443 + - path: /(api.*) + pathType: ImplementationSpecific + backend: + service: + name: kubernetes + port: + number: 443 \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..6240da8 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,21 @@ +# build output +dist/ +# generated types +.astro/ + +# dependencies +node_modules/ + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + + +# environment variables +.env +.env.production + +# macOS-specific files +.DS_Store diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/docs/404.html b/docs/404.html new file mode 100644 index 0000000..f1aceb0 --- /dev/null +++ b/docs/404.html @@ -0,0 +1,107 @@ + 404 | vJailbreak + Skip to content

404

Page not found. Check the URL or try using the search bar.
\ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..f9f6d31 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,54 @@ +# Starlight Starter Kit: Basics + +[![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build) + +``` +npm create astro@latest -- --template starlight +``` + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/starlight/tree/main/examples/basics) +[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/starlight/tree/main/examples/basics) +[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/withastro/starlight&create_from_path=examples/basics) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fwithastro%2Fstarlight%2Ftree%2Fmain%2Fexamples%2Fbasics&project-name=my-starlight-docs&repository-name=my-starlight-docs) + +> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! + +## 🚀 Project Structure + +Inside of your Astro + Starlight project, you'll see the following folders and files: + +``` +. +├── public/ +├── src/ +│ ├── assets/ +│ ├── content/ +│ │ ├── docs/ +│ └── content.config.ts +├── astro.config.mjs +├── package.json +└── tsconfig.json +``` + +Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name. + +Images can be added to `src/assets/` and embedded in Markdown with a relative link. + +Static assets, like favicons, can be placed in the `public/` directory. + +## 🧞 Commands + +All commands are run from the root of the project, from a terminal: + +| Command | Action | +| :------------------------ | :----------------------------------------------- | +| `npm install` | Installs dependencies | +| `npm run dev` | Starts local dev server at `localhost:4321` | +| `npm run build` | Build your production site to `./dist/` | +| `npm run preview` | Preview your build locally, before deploying | +| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | +| `npm run astro -- --help` | Get help using the Astro CLI | + +## 👀 Want to learn more? + +Check out [Starlight’s docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat). diff --git a/docs/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.C181hMzK.js b/docs/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.C181hMzK.js new file mode 100644 index 0000000..c628d52 --- /dev/null +++ b/docs/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.C181hMzK.js @@ -0,0 +1 @@ +import{S as r}from"./TableOfContents.astro_astro_type_script_index_0_lang.CKWWgpjV.js";class c extends r{set current(e){super.current=e;const t=this.querySelector(".display-current");t&&(t.textContent=e.textContent)}constructor(){super();const e=this.querySelector("details");if(!e)return;const t=()=>{e.open=!1};e.querySelectorAll("a").forEach(s=>{s.addEventListener("click",t)}),window.addEventListener("click",s=>{e.contains(s.target)||t()}),window.addEventListener("keydown",s=>{if(s.key==="Escape"&&e.open){const o=e.contains(document.activeElement);if(t(),o){const n=e.querySelector("summary");n&&n.focus()}}})}}customElements.define("mobile-starlight-toc",c); diff --git a/docs/_astro/Search.astro_astro_type_script_index_0_lang.18-vQxR-.js b/docs/_astro/Search.astro_astro_type_script_index_0_lang.18-vQxR-.js new file mode 100644 index 0000000..9377cdb --- /dev/null +++ b/docs/_astro/Search.astro_astro_type_script_index_0_lang.18-vQxR-.js @@ -0,0 +1 @@ +const y="modulepreload",w=function(h){return"/"+h},g={},S=function(u,i,c){let m=Promise.resolve();if(i&&i.length>0){document.getElementsByTagName("link");const r=document.querySelector("meta[property=csp-nonce]"),t=r?.nonce||r?.getAttribute("nonce");m=Promise.allSettled(i.map(n=>{if(n=w(n),n in g)return;g[n]=!0;const d=n.endsWith(".css"),f=d?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${n}"]${f}`))return;const o=document.createElement("link");if(o.rel=d?"stylesheet":y,d||(o.as="script"),o.crossOrigin="",o.href=n,t&&o.setAttribute("nonce",t),document.head.appendChild(o),d)return new Promise((e,s)=>{o.addEventListener("load",e),o.addEventListener("error",()=>s(new Error(`Unable to preload CSS for ${n}`)))})}))}function l(r){const t=new Event("vite:preloadError",{cancelable:!0});if(t.payload=r,window.dispatchEvent(t),!t.defaultPrevented)throw r}return m.then(r=>{for(const t of r||[])t.status==="rejected"&&l(t.reason);return u().catch(l)})},E={ranking:{pageLength:.1,termFrequency:.1,termSaturation:2,termSimilarity:9}};class v extends HTMLElement{constructor(){super();const u=this.querySelector("button[data-open-modal]"),i=this.querySelector("button[data-close-modal]"),c=this.querySelector("dialog"),m=this.querySelector(".dialog-frame"),l=e=>{("href"in(e.target||{})||document.body.contains(e.target)&&!m.contains(e.target))&&t()},r=e=>{c.showModal(),document.body.toggleAttribute("data-search-modal-open",!0),this.querySelector("input")?.focus(),e?.stopPropagation(),window.addEventListener("click",l)},t=()=>c.close();u.addEventListener("click",r),u.disabled=!1,i.addEventListener("click",t),c.addEventListener("close",()=>{document.body.toggleAttribute("data-search-modal-open",!1),window.removeEventListener("click",l)}),window.addEventListener("keydown",e=>{(e.metaKey===!0||e.ctrlKey===!0)&&e.key==="k"&&(c.open?t():r(),e.preventDefault())});let n={};try{n=JSON.parse(this.dataset.translations||"{}")}catch{}const o=this.dataset.stripTrailingSlash!==void 0?e=>e.replace(/(.)\/(#.*)?$/,"$1$2"):e=>e;window.addEventListener("DOMContentLoaded",()=>{(window.requestIdleCallback||(s=>setTimeout(s,1)))(async()=>{const{PagefindUI:s}=await S(async()=>{const{PagefindUI:a}=await import("./ui-core.DklwHuoF.js");return{PagefindUI:a}},[]);new s({...E,element:"#starlight__search",baseUrl:"/",bundlePath:"/".replace(/\/$/,"")+"/pagefind/",showImages:!1,translations:n,showSubResults:!0,processResult:a=>{a.url=o(a.url),a.sub_results=a.sub_results.map(p=>(p.url=o(p.url),p))}})})})}}customElements.define("site-search",v);export{S as _}; diff --git a/docs/_astro/TableOfContents.astro_astro_type_script_index_0_lang.CKWWgpjV.js b/docs/_astro/TableOfContents.astro_astro_type_script_index_0_lang.CKWWgpjV.js new file mode 100644 index 0000000..1a8ac41 --- /dev/null +++ b/docs/_astro/TableOfContents.astro_astro_type_script_index_0_lang.CKWWgpjV.js @@ -0,0 +1 @@ +const g="_top";class f extends HTMLElement{constructor(){super(),this._current=this.querySelector('a[aria-current="true"]'),this.minH=parseInt(this.dataset.minH||"2",10),this.maxH=parseInt(this.dataset.maxH||"3",10),this.onIdle=e=>(window.requestIdleCallback||(o=>setTimeout(o,1)))(e),this.init=()=>{const e=[...this.querySelectorAll("a")],o=t=>{if(t instanceof HTMLHeadingElement){if(t.id===g)return!0;const s=t.tagName[1];if(s){const n=parseInt(s,10);if(n>=this.minH&&n<=this.maxH)return!0}}return!1},i=t=>{if(!t)return null;const s=t;for(;t;){if(o(t))return t;for(t=t.previousElementSibling;t?.lastElementChild;)t=t.lastElementChild;const n=i(t);if(n)return n}return i(s.parentElement)},c=t=>{for(const{isIntersecting:s,target:n}of t){if(!s)continue;const l=i(n);if(!l)continue;const m=e.find(d=>d.hash==="#"+encodeURIComponent(l.id));if(m){this.current=m;break}}},a=document.querySelectorAll("main [id], main [id] ~ *, main .content > *");let r;const u=()=>{r||(r=new IntersectionObserver(c,{rootMargin:this.getRootMargin()}),a.forEach(t=>r.observe(t)))};u();let h;window.addEventListener("resize",()=>{r&&(r.disconnect(),r=void 0),clearTimeout(h),h=setTimeout(()=>this.onIdle(u),200)})},this.onIdle(()=>this.init())}set current(e){e!==this._current&&(this._current&&this._current.removeAttribute("aria-current"),e.setAttribute("aria-current","true"),this._current=e)}getRootMargin(){const e=document.querySelector("header")?.getBoundingClientRect().height||0,o=this.querySelector("summary")?.getBoundingClientRect().height||0,i=e+o+32,c=i+53,a=document.documentElement.clientHeight;return`-${i}px 0% ${c-a}px`}}customElements.define("starlight-toc",f);export{f as S}; diff --git a/docs/_astro/collections/docs.schema.json b/docs/_astro/collections/docs.schema.json new file mode 100644 index 0000000..3a38d06 --- /dev/null +++ b/docs/_astro/collections/docs.schema.json @@ -0,0 +1,636 @@ +{ + "$ref": "#/definitions/docs", + "definitions": { + "docs": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "editUrl": { + "anyOf": [ + { + "type": "string", + "format": "uri" + }, + { + "type": "boolean" + } + ], + "default": true + }, + "head": { + "type": "array", + "items": { + "type": "object", + "properties": { + "tag": { + "type": "string", + "enum": [ + "title", + "base", + "link", + "style", + "meta", + "script", + "noscript", + "template" + ] + }, + "attrs": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "boolean" + }, + { + "not": {} + } + ] + }, + "default": {} + }, + "content": { + "type": "string", + "default": "" + } + }, + "required": [ + "tag" + ], + "additionalProperties": false + }, + "default": [] + }, + "tableOfContents": { + "anyOf": [ + { + "type": "object", + "properties": { + "minHeadingLevel": { + "type": "integer", + "minimum": 1, + "maximum": 6, + "default": 2 + }, + "maxHeadingLevel": { + "type": "integer", + "minimum": 1, + "maximum": 6, + "default": 3 + } + }, + "additionalProperties": false + }, + { + "type": "boolean" + } + ], + "default": { + "minHeadingLevel": 2, + "maxHeadingLevel": 3 + } + }, + "template": { + "type": "string", + "enum": [ + "doc", + "splash" + ], + "default": "doc" + }, + "hero": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "tagline": { + "type": "string" + }, + "image": { + "anyOf": [ + { + "type": "object", + "properties": { + "alt": { + "type": "string", + "default": "" + }, + "file": { + "type": "string" + } + }, + "required": [ + "file" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "alt": { + "type": "string", + "default": "" + }, + "dark": { + "type": "string" + }, + "light": { + "type": "string" + } + }, + "required": [ + "dark", + "light" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "html": { + "type": "string" + } + }, + "required": [ + "html" + ], + "additionalProperties": false + } + ] + }, + "actions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "link": { + "type": "string" + }, + "variant": { + "type": "string", + "enum": [ + "primary", + "secondary", + "minimal" + ], + "default": "primary" + }, + "icon": { + "anyOf": [ + { + "type": "string", + "enum": [ + "up-caret", + "down-caret", + "right-caret", + "left-caret", + "up-arrow", + "down-arrow", + "right-arrow", + "left-arrow", + "bars", + "translate", + "pencil", + "pen", + "document", + "add-document", + "setting", + "external", + "download", + "cloud-download", + "moon", + "sun", + "laptop", + "open-book", + "information", + "magnifier", + "forward-slash", + "close", + "error", + "warning", + "approve-check-circle", + "approve-check", + "rocket", + "star", + "puzzle", + "list-format", + "random", + "comment", + "comment-alt", + "heart", + "github", + "gitlab", + "bitbucket", + "codePen", + "farcaster", + "discord", + "gitter", + "twitter", + "x.com", + "mastodon", + "codeberg", + "youtube", + "threads", + "linkedin", + "twitch", + "azureDevOps", + "microsoftTeams", + "instagram", + "stackOverflow", + "telegram", + "rss", + "facebook", + "email", + "phone", + "reddit", + "patreon", + "signal", + "slack", + "matrix", + "hackerOne", + "openCollective", + "blueSky", + "discourse", + "zulip", + "pinterest", + "tiktok", + "astro", + "alpine", + "pnpm", + "biome", + "bun", + "mdx", + "apple", + "linux", + "homebrew", + "nix", + "starlight", + "pkl", + "node", + "cloudflare", + "vercel", + "netlify", + "deno", + "jsr", + "nostr", + "backstage", + "confluence", + "jira", + "storybook", + "seti:folder", + "seti:bsl", + "seti:mdo", + "seti:salesforce", + "seti:asm", + "seti:bicep", + "seti:bazel", + "seti:c", + "seti:c-sharp", + "seti:html", + "seti:cpp", + "seti:clojure", + "seti:coldfusion", + "seti:config", + "seti:crystal", + "seti:crystal_embedded", + "seti:json", + "seti:css", + "seti:csv", + "seti:xls", + "seti:cu", + "seti:cake", + "seti:cake_php", + "seti:d", + "seti:word", + "seti:elixir", + "seti:elixir_script", + "seti:hex", + "seti:elm", + "seti:favicon", + "seti:f-sharp", + "seti:git", + "seti:go", + "seti:godot", + "seti:gradle", + "seti:grails", + "seti:graphql", + "seti:hacklang", + "seti:haml", + "seti:mustache", + "seti:haskell", + "seti:haxe", + "seti:jade", + "seti:java", + "seti:javascript", + "seti:jinja", + "seti:julia", + "seti:karma", + "seti:kotlin", + "seti:dart", + "seti:liquid", + "seti:livescript", + "seti:lua", + "seti:markdown", + "seti:argdown", + "seti:info", + "seti:clock", + "seti:maven", + "seti:nim", + "seti:github", + "seti:notebook", + "seti:nunjucks", + "seti:npm", + "seti:ocaml", + "seti:odata", + "seti:perl", + "seti:php", + "seti:pipeline", + "seti:pddl", + "seti:plan", + "seti:happenings", + "seti:powershell", + "seti:prisma", + "seti:pug", + "seti:puppet", + "seti:purescript", + "seti:python", + "seti:react", + "seti:rescript", + "seti:R", + "seti:ruby", + "seti:rust", + "seti:sass", + "seti:spring", + "seti:slim", + "seti:smarty", + "seti:sbt", + "seti:scala", + "seti:ethereum", + "seti:stylus", + "seti:svelte", + "seti:swift", + "seti:db", + "seti:terraform", + "seti:tex", + "seti:default", + "seti:twig", + "seti:typescript", + "seti:tsconfig", + "seti:vala", + "seti:vite", + "seti:vue", + "seti:wasm", + "seti:wat", + "seti:xml", + "seti:yml", + "seti:prolog", + "seti:zig", + "seti:zip", + "seti:wgt", + "seti:illustrator", + "seti:photoshop", + "seti:pdf", + "seti:font", + "seti:image", + "seti:svg", + "seti:sublime", + "seti:code-search", + "seti:shell", + "seti:video", + "seti:audio", + "seti:windows", + "seti:jenkins", + "seti:babel", + "seti:bower", + "seti:docker", + "seti:code-climate", + "seti:eslint", + "seti:firebase", + "seti:firefox", + "seti:gitlab", + "seti:grunt", + "seti:gulp", + "seti:ionic", + "seti:platformio", + "seti:rollup", + "seti:stylelint", + "seti:yarn", + "seti:webpack", + "seti:lock", + "seti:license", + "seti:makefile", + "seti:heroku", + "seti:todo", + "seti:ignored" + ] + }, + { + "type": "string", + "pattern": "^\\ import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Findex.mdx&astroContentModuleFlag=true")], +["src/content/docs/introduction/getting_started.mdx", () => import("astro:content-layer-deferred-module?astro%3Acontent-layer-deferred-module=&fileName=src%2Fcontent%2Fdocs%2Fintroduction%2Fgetting_started.mdx&astroContentModuleFlag=true")]]); + \ No newline at end of file diff --git a/docs/_astro/content.d.ts b/docs/_astro/content.d.ts new file mode 100644 index 0000000..6feb0f3 --- /dev/null +++ b/docs/_astro/content.d.ts @@ -0,0 +1,175 @@ +declare module 'astro:content' { + interface Render { + '.mdx': Promise<{ + Content: import('astro').MarkdownInstance<{}>['Content']; + headings: import('astro').MarkdownHeading[]; + remarkPluginFrontmatter: Record; + components: import('astro').MDXInstance<{}>['components']; + }>; + } +} + +declare module 'astro:content' { + export interface RenderResult { + Content: import('astro/runtime/server/index.js').AstroComponentFactory; + headings: import('astro').MarkdownHeading[]; + remarkPluginFrontmatter: Record; + } + interface Render { + '.md': Promise; + } + + export interface RenderedContent { + html: string; + metadata?: { + imagePaths: Array; + [key: string]: unknown; + }; + } +} + +declare module 'astro:content' { + type Flatten = T extends { [K: string]: infer U } ? U : never; + + export type CollectionKey = keyof AnyEntryMap; + export type CollectionEntry = Flatten; + + export type ContentCollectionKey = keyof ContentEntryMap; + export type DataCollectionKey = keyof DataEntryMap; + + type AllValuesOf = T extends any ? T[keyof T] : never; + type ValidContentEntrySlug = AllValuesOf< + ContentEntryMap[C] + >['slug']; + + export type ReferenceDataEntry< + C extends CollectionKey, + E extends keyof DataEntryMap[C] = string, + > = { + collection: C; + id: E; + }; + export type ReferenceContentEntry< + C extends keyof ContentEntryMap, + E extends ValidContentEntrySlug | (string & {}) = string, + > = { + collection: C; + slug: E; + }; + + /** @deprecated Use `getEntry` instead. */ + export function getEntryBySlug< + C extends keyof ContentEntryMap, + E extends ValidContentEntrySlug | (string & {}), + >( + collection: C, + // Note that this has to accept a regular string too, for SSR + entrySlug: E, + ): E extends ValidContentEntrySlug + ? Promise> + : Promise | undefined>; + + /** @deprecated Use `getEntry` instead. */ + export function getDataEntryById( + collection: C, + entryId: E, + ): Promise>; + + export function getCollection>( + collection: C, + filter?: (entry: CollectionEntry) => entry is E, + ): Promise; + export function getCollection( + collection: C, + filter?: (entry: CollectionEntry) => unknown, + ): Promise[]>; + + export function getEntry< + C extends keyof ContentEntryMap, + E extends ValidContentEntrySlug | (string & {}), + >( + entry: ReferenceContentEntry, + ): E extends ValidContentEntrySlug + ? Promise> + : Promise | undefined>; + export function getEntry< + C extends keyof DataEntryMap, + E extends keyof DataEntryMap[C] | (string & {}), + >( + entry: ReferenceDataEntry, + ): E extends keyof DataEntryMap[C] + ? Promise + : Promise | undefined>; + export function getEntry< + C extends keyof ContentEntryMap, + E extends ValidContentEntrySlug | (string & {}), + >( + collection: C, + slug: E, + ): E extends ValidContentEntrySlug + ? Promise> + : Promise | undefined>; + export function getEntry< + C extends keyof DataEntryMap, + E extends keyof DataEntryMap[C] | (string & {}), + >( + collection: C, + id: E, + ): E extends keyof DataEntryMap[C] + ? string extends keyof DataEntryMap[C] + ? Promise | undefined + : Promise + : Promise | undefined>; + + /** Resolve an array of entry references from the same collection */ + export function getEntries( + entries: ReferenceContentEntry>[], + ): Promise[]>; + export function getEntries( + entries: ReferenceDataEntry[], + ): Promise[]>; + + export function render( + entry: AnyEntryMap[C][string], + ): Promise; + + export function reference( + collection: C, + ): import('astro/zod').ZodEffects< + import('astro/zod').ZodString, + C extends keyof ContentEntryMap + ? ReferenceContentEntry> + : ReferenceDataEntry + >; + // Allow generic `string` to avoid excessive type errors in the config + // if `dev` is not running to update as you edit. + // Invalid collection names will be caught at build time. + export function reference( + collection: C, + ): import('astro/zod').ZodEffects; + + type ReturnTypeOrOriginal = T extends (...args: any[]) => infer R ? R : T; + type InferEntrySchema = import('astro/zod').infer< + ReturnTypeOrOriginal['schema']> + >; + + type ContentEntryMap = { + + }; + + type DataEntryMap = { + "docs": Record; + rendered?: RenderedContent; + filePath?: string; +}>; + + }; + + type AnyEntryMap = ContentEntryMap & DataEntryMap; + + export type ContentConfig = typeof import("../src/content.config.js"); +} diff --git a/docs/_astro/ec.8zarh.js b/docs/_astro/ec.8zarh.js new file mode 100644 index 0000000..bf1115d --- /dev/null +++ b/docs/_astro/ec.8zarh.js @@ -0,0 +1,3 @@ +try{(()=>{function a(e){if(!e)return;let t=e.getAttribute("tabindex")!==null,n=e.scrollWidth>e.clientWidth;n&&!t?e.setAttribute("tabindex","0"):!n&&t&&e.removeAttribute("tabindex")}var u=window.requestIdleCallback||(e=>setTimeout(e,1)),i=window.cancelIdleCallback||clearTimeout;function l(e){let t=new Set,n,r;return new ResizeObserver(c=>{c.forEach(o=>t.add(o.target)),n&&clearTimeout(n),r&&i(r),n=setTimeout(()=>{r&&i(r),r=u(()=>{t.forEach(o=>e(o)),t.clear()})},250)})}function d(e,t){e.querySelectorAll?.(".expressive-code pre > code").forEach(n=>{let r=n.parentElement;r&&t.observe(r)})}var s=l(a);d(document,s);var b=new MutationObserver(e=>e.forEach(t=>t.addedNodes.forEach(n=>{d(n,s)})));b.observe(document.body,{childList:!0,subtree:!0});document.addEventListener("astro:page-load",()=>{d(document,s)});})();}catch(e){console.error("[EC] tabindex-js-module failed:",e)} +try{(()=>{function i(o){let e=document.createElement("pre");Object.assign(e.style,{opacity:"0",pointerEvents:"none",position:"absolute",overflow:"hidden",left:"0",top:"0",width:"20px",height:"20px",webkitUserSelect:"auto",userSelect:"all"}),e.ariaHidden="true",e.textContent=o,document.body.appendChild(e);let a=document.createRange();a.selectNode(e);let n=getSelection();if(!n)return!1;n.removeAllRanges(),n.addRange(a);let r=!1;try{r=document.execCommand("copy")}finally{n.removeAllRanges(),document.body.removeChild(e)}return r}async function l(o){let e=o.currentTarget,a=e.dataset,n=!1,r=a.code.replace(/\u007f/g,` +`);try{await navigator.clipboard.writeText(r),n=!0}catch{n=i(r)}if(!n||e.parentNode?.querySelector(".feedback"))return;let t=document.createElement("div");t.classList.add("feedback"),t.append(a.copied),e.before(t),t.offsetWidth,requestAnimationFrame(()=>t?.classList.add("show"));let c=()=>!t||t.classList.remove("show"),d=()=>{!t||parseFloat(getComputedStyle(t).opacity)>0||(t.remove(),t=void 0)};setTimeout(c,1500),setTimeout(d,2500),e.addEventListener("blur",c),t.addEventListener("transitioncancel",d),t.addEventListener("transitionend",d)}function s(o){o.querySelectorAll?.(".expressive-code .copy button").forEach(e=>e.addEventListener("click",l))}s(document);var u=new MutationObserver(o=>o.forEach(e=>e.addedNodes.forEach(a=>{s(a)})));u.observe(document.body,{childList:!0,subtree:!0});document.addEventListener("astro:page-load",()=>{s(document)});})();}catch(e){console.error("[EC] copy-js-module failed:",e)} \ No newline at end of file diff --git a/docs/_astro/ec.tm3va.css b/docs/_astro/ec.tm3va.css new file mode 100644 index 0000000..eb0dc37 --- /dev/null +++ b/docs/_astro/ec.tm3va.css @@ -0,0 +1 @@ +.expressive-code{font-family:var(--ec-uiFontFml);font-size:var(--ec-uiFontSize);font-weight:var(--ec-uiFontWg);line-height:var(--ec-uiLineHt);text-size-adjust:none;-webkit-text-size-adjust:none}.expressive-code *:not(:is(svg, svg *)){all:revert;box-sizing:border-box}.expressive-code pre{display:flex;margin:0;padding:0;border:var(--ec-brdWd) solid var(--ec-brdCol);border-radius:calc(var(--ec-brdRad) + var(--ec-brdWd));background:var(--ec-codeBg)}.expressive-code pre:focus-visible{outline:3px solid var(--ec-focusBrd);outline-offset:-3px}.expressive-code pre > code{all:unset;display:block;flex:1 0 100%;padding:var(--ec-codePadBlk) 0;color:var(--ec-codeFg);font-family:var(--ec-codeFontFml);font-size:var(--ec-codeFontSize);font-weight:var(--ec-codeFontWg);line-height:var(--ec-codeLineHt)}.expressive-code pre{overflow-x:auto}.expressive-code pre.wrap .ec-line .code{white-space:pre-wrap;overflow-wrap:break-word;min-width:min(20ch, var(--ecMaxLine, 20ch))}.expressive-code pre.wrap .ec-line .code span.indent{white-space:pre}.expressive-code pre::-webkit-scrollbar,.expressive-code pre::-webkit-scrollbar-track{background-color:inherit;border-radius:calc(var(--ec-brdRad) + var(--ec-brdWd));border-top-left-radius:0;border-top-right-radius:0}.expressive-code pre::-webkit-scrollbar-thumb{background-color:var(--ec-sbThumbCol);border:4px solid transparent;background-clip:content-box;border-radius:10px}.expressive-code pre::-webkit-scrollbar-thumb:hover{background-color:var(--ec-sbThumbHoverCol)}.expressive-code .ec-line{direction:ltr;unicode-bidi:isolate;display:grid;grid-template-areas:'gutter code';grid-template-columns:auto 1fr;position:relative}.expressive-code .ec-line .gutter{grid-area:gutter;color:var(--ec-gtrFg)}.expressive-code .ec-line .gutter > *{pointer-events:none;user-select:none;-webkit-user-select:none}.expressive-code .ec-line .gutter ~ .code{--ecLineBrdCol:var(--ec-gtrBrdCol)}.expressive-code .ec-line.highlight .gutter{color:var(--ec-gtrHlFg)}.expressive-code .ec-line .code{grid-area:code;position:relative;box-sizing:content-box;padding-inline-start:calc(var(--ecIndent, 0ch) + var(--ec-codePadInl) - var(--ecGtrBrdWd));padding-inline-end:var(--ec-codePadInl);text-indent:calc(var(--ecIndent, 0ch) * -1)}.expressive-code .ec-line .code::before,.expressive-code .ec-line .code::after,.expressive-code .ec-line .code :where(*){text-indent:0}.expressive-code .ec-line .code{--ecGtrBrdWd:var(--ec-gtrBrdWd);border-inline-start:var(--ecGtrBrdWd) solid var(--ecLineBrdCol, transparent)}.expressive-code .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0}.expressive-code .ec-line.mark{--tmLineBgCol:var(--ec-tm-markBg)}.expressive-code .ec-line.mark .code{--ecLineBrdCol:var(--ec-tm-markBrdCol)}.expressive-code .ec-line.ins{--tmLineBgCol:var(--ec-tm-insBg);--tmLabel:var(--ec-tm-insDiffIndContent)}.expressive-code .ec-line.ins .code{--ecLineBrdCol:var(--ec-tm-insBrdCol)}.expressive-code .ec-line.ins .code::before{color:var(--ec-tm-insDiffIndCol)}.expressive-code .ec-line.del{--tmLineBgCol:var(--ec-tm-delBg);--tmLabel:var(--ec-tm-delDiffIndContent)}.expressive-code .ec-line.del .code{--ecLineBrdCol:var(--ec-tm-delBrdCol)}.expressive-code .ec-line.del .code::before{color:var(--ec-tm-delDiffIndCol)}.expressive-code .ec-line.mark,.expressive-code .ec-line.ins,.expressive-code .ec-line.del{background:var(--tmLineBgCol)}.expressive-code .ec-line.mark .code,.expressive-code .ec-line.ins .code,.expressive-code .ec-line.del .code{--ecGtrBrdWd:var(--ec-tm-lineMarkerAccentWd)}.expressive-code .ec-line.mark .code::before,.expressive-code .ec-line.ins .code::before,.expressive-code .ec-line.del .code::before{display:block;position:absolute;left:0;box-sizing:border-box;content:var(--tmLabel, ' ');padding-inline-start:var(--ec-tm-lineDiffIndMargLeft);text-align:center;white-space:pre}.expressive-code .ec-line.mark.tm-label .code::before,.expressive-code .ec-line.ins.tm-label .code::before,.expressive-code .ec-line.del.tm-label .code::before{background:var(--ecLineBrdCol);padding:0 calc(var(--ec-tm-lineMarkerLabelPadInl) + var(--ec-tm-lineMarkerAccentWd)) 0 var(--ec-tm-lineMarkerLabelPadInl);color:var(--ec-tm-lineMarkerLabelCol)}.expressive-code .ec-line mark{--tmInlineBgCol:var(--ec-tm-markBg);--tmInlineBrdCol:var(--ec-tm-markBrdCol)}.expressive-code .ec-line ins{--tmInlineBgCol:var(--ec-tm-insBg);--tmInlineBrdCol:var(--ec-tm-insBrdCol)}.expressive-code .ec-line del{--tmInlineBgCol:var(--ec-tm-delBg);--tmInlineBrdCol:var(--ec-tm-delBrdCol)}.expressive-code .ec-line mark,.expressive-code .ec-line ins,.expressive-code .ec-line del{all:unset;display:inline-block;position:relative;--tmBrdL:var(--ec-tm-inlMarkerBrdWd);--tmBrdR:var(--ec-tm-inlMarkerBrdWd);--tmRadL:var(--ec-tm-inlMarkerBrdRad);--tmRadR:var(--ec-tm-inlMarkerBrdRad);margin-inline:0.025rem;padding-inline:var(--ec-tm-inlMarkerPad);border-radius:var(--tmRadL) var(--tmRadR) var(--tmRadR) var(--tmRadL);background:var(--tmInlineBgCol);background-clip:padding-box}.expressive-code .ec-line mark.open-start,.expressive-code .ec-line ins.open-start,.expressive-code .ec-line del.open-start{margin-inline-start:0;padding-inline-start:0;--tmBrdL:0px;--tmRadL:0}.expressive-code .ec-line mark.open-end,.expressive-code .ec-line ins.open-end,.expressive-code .ec-line del.open-end{margin-inline-end:0;padding-inline-end:0;--tmBrdR:0px;--tmRadR:0}.expressive-code .ec-line mark::before,.expressive-code .ec-line ins::before,.expressive-code .ec-line del::before{content:'';position:absolute;pointer-events:none;display:inline-block;inset:0;border-radius:var(--tmRadL) var(--tmRadR) var(--tmRadR) var(--tmRadL);border:var(--ec-tm-inlMarkerBrdWd) solid var(--tmInlineBrdCol);border-inline-width:var(--tmBrdL) var(--tmBrdR)}.expressive-code .frame{all:unset;position:relative;display:block;--header-border-radius:calc(var(--ec-brdRad) + var(--ec-brdWd));--tab-border-radius:calc(var(--ec-frm-edTabBrdRad) + var(--ec-brdWd));--button-spacing:0.4rem;--code-background:var(--ec-frm-edBg);border-radius:var(--header-border-radius);box-shadow:var(--ec-frm-frameBoxShdCssVal)}.expressive-code .frame .header{display:none;z-index:1;position:relative;border-radius:var(--header-border-radius) var(--header-border-radius) 0 0}.expressive-code .frame.has-title pre,.expressive-code .frame.has-title code,.expressive-code .frame.is-terminal pre,.expressive-code .frame.is-terminal code{border-top:none;border-top-left-radius:0;border-top-right-radius:0}.expressive-code .frame .title:empty:before{content:'\a0'}.expressive-code .frame.has-title:not(.is-terminal){--button-spacing:calc(1.9rem + 2 * (var(--ec-uiPadBlk) + var(--ec-frm-edActTabIndHt)))}.expressive-code .frame.has-title:not(.is-terminal) .title{position:relative;color:var(--ec-frm-edActTabFg);background:var(--ec-frm-edActTabBg);background-clip:padding-box;margin-block-start:var(--ec-frm-edTabsMargBlkStart);padding:calc(var(--ec-uiPadBlk) + var(--ec-frm-edActTabIndHt)) var(--ec-uiPadInl);border:var(--ec-brdWd) solid var(--ec-frm-edActTabBrdCol);border-radius:var(--tab-border-radius) var(--tab-border-radius) 0 0;border-bottom:none;overflow:hidden}.expressive-code .frame.has-title:not(.is-terminal) .title::after{content:'';position:absolute;pointer-events:none;inset:0;border-top:var(--ec-frm-edActTabIndHt) solid var(--ec-frm-edActTabIndTopCol);border-bottom:var(--ec-frm-edActTabIndHt) solid var(--ec-frm-edActTabIndBtmCol)}.expressive-code .frame.has-title:not(.is-terminal) .header{display:flex;background:linear-gradient(to top, var(--ec-frm-edTabBarBrdBtmCol) var(--ec-brdWd), transparent var(--ec-brdWd)),linear-gradient(var(--ec-frm-edTabBarBg), var(--ec-frm-edTabBarBg));background-repeat:no-repeat;padding-inline-start:var(--ec-frm-edTabsMargInlStart)}.expressive-code .frame.has-title:not(.is-terminal) .header::before{content:'';position:absolute;pointer-events:none;inset:0;border:var(--ec-brdWd) solid var(--ec-frm-edTabBarBrdCol);border-radius:inherit;border-bottom:none}.expressive-code .frame.is-terminal{--button-spacing:calc(1.9rem + var(--ec-brdWd) + 2 * var(--ec-uiPadBlk));--code-background:var(--ec-frm-trmBg)}.expressive-code .frame.is-terminal .header{display:flex;align-items:center;justify-content:center;padding-block:var(--ec-uiPadBlk);padding-block-end:calc(var(--ec-uiPadBlk) + var(--ec-brdWd));position:relative;font-weight:500;letter-spacing:0.025ch;color:var(--ec-frm-trmTtbFg);background:var(--ec-frm-trmTtbBg);border:var(--ec-brdWd) solid var(--ec-brdCol);border-bottom:none}.expressive-code .frame.is-terminal .header::before{content:'';position:absolute;pointer-events:none;left:var(--ec-uiPadInl);width:2.1rem;height:0.56rem;line-height:0;background-color:var(--ec-frm-trmTtbDotsFg);opacity:var(--ec-frm-trmTtbDotsOpa);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 16' preserveAspectRatio='xMidYMid meet'%3E%3Ccircle cx='8' cy='8' r='8'/%3E%3Ccircle cx='30' cy='8' r='8'/%3E%3Ccircle cx='52' cy='8' r='8'/%3E%3C/svg%3E");-webkit-mask-repeat:no-repeat;mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 16' preserveAspectRatio='xMidYMid meet'%3E%3Ccircle cx='8' cy='8' r='8'/%3E%3Ccircle cx='30' cy='8' r='8'/%3E%3Ccircle cx='52' cy='8' r='8'/%3E%3C/svg%3E");mask-repeat:no-repeat}.expressive-code .frame.is-terminal .header::after{content:'';position:absolute;pointer-events:none;inset:0;border-bottom:var(--ec-brdWd) solid var(--ec-frm-trmTtbBrdBtmCol)}.expressive-code .frame pre{background:var(--code-background)}.expressive-code .copy{display:flex;gap:0.25rem;flex-direction:row;position:absolute;inset-block-start:calc(var(--ec-brdWd) + var(--button-spacing));inset-inline-end:calc(var(--ec-brdWd) + var(--ec-uiPadInl) / 2)}@media (scripting: none){.expressive-code .copy{display:none}}.expressive-code .copy{direction:ltr;unicode-bidi:isolate}.expressive-code .copy button{position:relative;align-self:flex-end;margin:0;padding:0;border:none;border-radius:0.2rem;z-index:1;cursor:pointer;transition-property:opacity, background, border-color;transition-duration:0.2s;transition-timing-function:cubic-bezier(0.25, 0.46, 0.45, 0.94);width:2.5rem;height:2.5rem;background:var(--code-background);opacity:0.75}.expressive-code .copy button div{position:absolute;inset:0;border-radius:inherit;background:var(--ec-frm-inlBtnBg);opacity:var(--ec-frm-inlBtnBgIdleOpa);transition-property:inherit;transition-duration:inherit;transition-timing-function:inherit}.expressive-code .copy button::before{content:'';position:absolute;pointer-events:none;inset:0;border-radius:inherit;border:var(--ec-brdWd) solid var(--ec-frm-inlBtnBrd);opacity:var(--ec-frm-inlBtnBrdOpa)}.expressive-code .copy button::after{content:'';position:absolute;pointer-events:none;inset:0;background-color:var(--ec-frm-inlBtnFg);-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.75'%3E%3Cpath d='M3 19a2 2 0 0 1-1-2V2a2 2 0 0 1 1-1h13a2 2 0 0 1 2 1'/%3E%3Crect x='6' y='5' width='16' height='18' rx='1.5' ry='1.5'/%3E%3C/svg%3E");-webkit-mask-repeat:no-repeat;mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.75'%3E%3Cpath d='M3 19a2 2 0 0 1-1-2V2a2 2 0 0 1 1-1h13a2 2 0 0 1 2 1'/%3E%3Crect x='6' y='5' width='16' height='18' rx='1.5' ry='1.5'/%3E%3C/svg%3E");mask-repeat:no-repeat;margin:0.475rem;line-height:0}.expressive-code .copy button:hover,.expressive-code .copy button:focus:focus-visible{opacity:1}.expressive-code .copy button:hover div,.expressive-code .copy button:focus:focus-visible div{opacity:var(--ec-frm-inlBtnBgHoverOrFocusOpa)}.expressive-code .copy button:active{opacity:1}.expressive-code .copy button:active div{opacity:var(--ec-frm-inlBtnBgActOpa)}.expressive-code .copy .feedback{--tooltip-arrow-size:0.35rem;--tooltip-bg:var(--ec-frm-tooltipSuccessBg);color:var(--ec-frm-tooltipSuccessFg);pointer-events:none;user-select:none;-webkit-user-select:none;position:relative;align-self:center;background-color:var(--tooltip-bg);z-index:99;padding:0.125rem 0.75rem;border-radius:0.2rem;margin-inline-end:var(--tooltip-arrow-size);opacity:0;transition-property:opacity, transform;transition-duration:0.2s;transition-timing-function:ease-in-out;transform:translate3d(0, 0.25rem, 0)}.expressive-code .copy .feedback::after{content:'';position:absolute;pointer-events:none;top:calc(50% - var(--tooltip-arrow-size));inset-inline-end:calc(-2 * (var(--tooltip-arrow-size) - 0.5px));border:var(--tooltip-arrow-size) solid transparent;border-inline-start-color:var(--tooltip-bg)}.expressive-code .copy .feedback.show{opacity:1;transform:translate3d(0, 0, 0)}@media (hover: hover){.expressive-code{}.expressive-code .copy button{opacity:0;width:2rem;height:2rem}.expressive-code .frame:hover .copy button:not(:hover),.expressive-code .frame:focus-within :focus-visible ~ .copy button:not(:hover),.expressive-code .frame .copy .feedback.show ~ button:not(:hover){opacity:0.75}}.expressive-code :nth-child(1 of .ec-line) .code{padding-inline-end:calc(2rem + var(--ec-codePadInl))}:root,:root:not([data-theme='dark']) .expressive-code[data-theme='dark']{--ec-brdRad:0px;--ec-brdWd:1px;--ec-brdCol:color-mix(in srgb, var(--sl-color-gray-5), transparent 25%);--ec-codeFontFml:var(--__sl-font-mono);--ec-codeFontSize:var(--sl-text-code);--ec-codeFontWg:400;--ec-codeLineHt:var(--sl-line-height);--ec-codePadBlk:0.75rem;--ec-codePadInl:1rem;--ec-codeBg:#23262f;--ec-codeFg:#d6deeb;--ec-codeSelBg:#1d3b53;--ec-gtrFg:#63798b;--ec-gtrBrdCol:#63798b33;--ec-gtrBrdWd:1.5px;--ec-gtrHlFg:#c5e4fd97;--ec-uiFontFml:var(--__sl-font);--ec-uiFontSize:0.9rem;--ec-uiFontWg:400;--ec-uiLineHt:1.65;--ec-uiPadBlk:0.25rem;--ec-uiPadInl:1rem;--ec-uiSelBg:#234d708c;--ec-uiSelFg:#ffffff;--ec-focusBrd:#122d42;--ec-sbThumbCol:#ffffff17;--ec-sbThumbHoverCol:#ffffff47;--ec-tm-lineMarkerAccentMarg:0rem;--ec-tm-lineMarkerAccentWd:0.15rem;--ec-tm-lineMarkerLabelPadInl:0.2rem;--ec-tm-lineMarkerLabelCol:white;--ec-tm-lineDiffIndMargLeft:0.25rem;--ec-tm-inlMarkerBrdWd:1.5px;--ec-tm-inlMarkerBrdRad:0.2rem;--ec-tm-inlMarkerPad:0.15rem;--ec-tm-insDiffIndContent:'+';--ec-tm-delDiffIndContent:'-';--ec-tm-markBg:#ffffff17;--ec-tm-markBrdCol:#ffffff40;--ec-tm-insBg:#1e571599;--ec-tm-insBrdCol:#487f3bd0;--ec-tm-insDiffIndCol:#79b169d0;--ec-tm-delBg:#862d2799;--ec-tm-delBrdCol:#b4554bd0;--ec-tm-delDiffIndCol:#ed8779d0;--ec-frm-shdCol:#011627;--ec-frm-frameBoxShdCssVal:none;--ec-frm-edActTabBg:var(--sl-color-gray-6);--ec-frm-edActTabFg:var(--sl-color-text);--ec-frm-edActTabBrdCol:transparent;--ec-frm-edActTabIndHt:1px;--ec-frm-edActTabIndTopCol:var(--sl-color-accent-high);--ec-frm-edActTabIndBtmCol:transparent;--ec-frm-edTabsMargInlStart:0;--ec-frm-edTabsMargBlkStart:0;--ec-frm-edTabBrdRad:0px;--ec-frm-edTabBarBg:var(--sl-color-black);--ec-frm-edTabBarBrdCol:color-mix(in srgb, var(--sl-color-gray-5), transparent 25%);--ec-frm-edTabBarBrdBtmCol:color-mix(in srgb, var(--sl-color-gray-5), transparent 25%);--ec-frm-edBg:var(--sl-color-gray-6);--ec-frm-trmTtbDotsFg:color-mix(in srgb, var(--sl-color-gray-5), transparent 25%);--ec-frm-trmTtbDotsOpa:0.75;--ec-frm-trmTtbBg:var(--sl-color-black);--ec-frm-trmTtbFg:var(--sl-color-text);--ec-frm-trmTtbBrdBtmCol:color-mix(in srgb, var(--sl-color-gray-5), transparent 25%);--ec-frm-trmBg:var(--sl-color-gray-6);--ec-frm-inlBtnFg:var(--sl-color-text);--ec-frm-inlBtnBg:var(--sl-color-text);--ec-frm-inlBtnBgIdleOpa:0;--ec-frm-inlBtnBgHoverOrFocusOpa:0.2;--ec-frm-inlBtnBgActOpa:0.3;--ec-frm-inlBtnBrd:var(--sl-color-text);--ec-frm-inlBtnBrdOpa:0.4;--ec-frm-tooltipSuccessBg:#158744;--ec-frm-tooltipSuccessFg:white}.expressive-code .ec-line :where(span[style^='--']:not([class])),:root:not([data-theme='dark']) .expressive-code[data-theme='dark'] .ec-line :where(span[style^='--']:not([class])){color:var(--0, inherit);font-style:var(--0fs, inherit);font-weight:var(--0fw, inherit);text-decoration:var(--0td, inherit)}@media (prefers-color-scheme: light){:root:not([data-theme='dark']){--ec-codeBg:#f6f7f9;--ec-codeFg:#403f53;--ec-codeSelBg:#e0e0e0;--ec-gtrFg:#788b94;--ec-gtrBrdCol:#788b9433;--ec-gtrHlFg:#403f53c4;--ec-uiSelBg:#d3e8f8;--ec-uiSelFg:#403f53;--ec-focusBrd:#93a1a1;--ec-sbThumbCol:#0000001a;--ec-sbThumbHoverCol:#0000005c;--ec-tm-markBg:#0000001a;--ec-tm-markBrdCol:#00000055;--ec-tm-insBg:#8ec77d99;--ec-tm-insDiffIndCol:#336a28d0;--ec-tm-delBg:#ff9c8e99;--ec-tm-delDiffIndCol:#9d4138d0;--ec-frm-shdCol:#d9d9d9;--ec-frm-edActTabBg:var(--sl-color-gray-7);--ec-frm-edActTabIndTopCol:var(--sl-color-accent);--ec-frm-edTabBarBg:var(--sl-color-gray-6);--ec-frm-edBg:var(--sl-color-gray-7);--ec-frm-trmTtbBg:var(--sl-color-gray-6);--ec-frm-trmBg:var(--sl-color-gray-7);--ec-frm-tooltipSuccessBg:#078662}:root:not([data-theme='dark']) .expressive-code .ec-line :where(span[style^='--']:not([class])){color:var(--1, inherit);font-style:var(--1fs, inherit);font-weight:var(--1fw, inherit);text-decoration:var(--1td, inherit)}}:root[data-theme='light'] .expressive-code:not([data-theme='dark']),.expressive-code[data-theme='light']{--ec-codeBg:#f6f7f9;--ec-codeFg:#403f53;--ec-codeSelBg:#e0e0e0;--ec-gtrFg:#788b94;--ec-gtrBrdCol:#788b9433;--ec-gtrHlFg:#403f53c4;--ec-uiSelBg:#d3e8f8;--ec-uiSelFg:#403f53;--ec-focusBrd:#93a1a1;--ec-sbThumbCol:#0000001a;--ec-sbThumbHoverCol:#0000005c;--ec-tm-markBg:#0000001a;--ec-tm-markBrdCol:#00000055;--ec-tm-insBg:#8ec77d99;--ec-tm-insDiffIndCol:#336a28d0;--ec-tm-delBg:#ff9c8e99;--ec-tm-delDiffIndCol:#9d4138d0;--ec-frm-shdCol:#d9d9d9;--ec-frm-edActTabBg:var(--sl-color-gray-7);--ec-frm-edActTabIndTopCol:var(--sl-color-accent);--ec-frm-edTabBarBg:var(--sl-color-gray-6);--ec-frm-edBg:var(--sl-color-gray-7);--ec-frm-trmTtbBg:var(--sl-color-gray-6);--ec-frm-trmBg:var(--sl-color-gray-7);--ec-frm-tooltipSuccessBg:#078662}:root[data-theme='light'] .expressive-code:not([data-theme='dark']) .ec-line :where(span[style^='--']:not([class])),.expressive-code[data-theme='light'] .ec-line :where(span[style^='--']:not([class])){color:var(--1, inherit);font-style:var(--1fs, inherit);font-weight:var(--1fw, inherit);text-decoration:var(--1td, inherit)} \ No newline at end of file diff --git a/docs/_astro/index.CK0XFbLG.css b/docs/_astro/index.CK0XFbLG.css new file mode 100644 index 0000000..4774d8d --- /dev/null +++ b/docs/_astro/index.CK0XFbLG.css @@ -0,0 +1 @@ +:root,::backdrop{--sl-color-white: hsl(0, 0%, 100%);--sl-color-gray-1: hsl(224, 20%, 94%);--sl-color-gray-2: hsl(224, 6%, 77%);--sl-color-gray-3: hsl(224, 6%, 56%);--sl-color-gray-4: hsl(224, 7%, 36%);--sl-color-gray-5: hsl(224, 10%, 23%);--sl-color-gray-6: hsl(224, 14%, 16%);--sl-color-black: hsl(224, 10%, 10%);--sl-hue-orange: 41;--sl-color-orange-low: hsl(var(--sl-hue-orange), 39%, 22%);--sl-color-orange: hsl(var(--sl-hue-orange), 82%, 63%);--sl-color-orange-high: hsl(var(--sl-hue-orange), 82%, 87%);--sl-hue-green: 101;--sl-color-green-low: hsl(var(--sl-hue-green), 39%, 22%);--sl-color-green: hsl(var(--sl-hue-green), 82%, 63%);--sl-color-green-high: hsl(var(--sl-hue-green), 82%, 80%);--sl-hue-blue: 234;--sl-color-blue-low: hsl(var(--sl-hue-blue), 54%, 20%);--sl-color-blue: hsl(var(--sl-hue-blue), 100%, 60%);--sl-color-blue-high: hsl(var(--sl-hue-blue), 100%, 87%);--sl-hue-purple: 281;--sl-color-purple-low: hsl(var(--sl-hue-purple), 39%, 22%);--sl-color-purple: hsl(var(--sl-hue-purple), 82%, 63%);--sl-color-purple-high: hsl(var(--sl-hue-purple), 82%, 89%);--sl-hue-red: 339;--sl-color-red-low: hsl(var(--sl-hue-red), 39%, 22%);--sl-color-red: hsl(var(--sl-hue-red), 82%, 63%);--sl-color-red-high: hsl(var(--sl-hue-red), 82%, 87%);--sl-color-accent-low: hsl(224, 54%, 20%);--sl-color-accent: hsl(224, 100%, 60%);--sl-color-accent-high: hsl(224, 100%, 85%);--sl-color-text: var(--sl-color-gray-2);--sl-color-text-accent: var(--sl-color-accent-high);--sl-color-text-invert: var(--sl-color-accent-low);--sl-color-bg: var(--sl-color-black);--sl-color-bg-nav: var(--sl-color-gray-6);--sl-color-bg-sidebar: var(--sl-color-gray-6);--sl-color-bg-inline-code: var(--sl-color-gray-5);--sl-color-bg-accent: var(--sl-color-accent-high);--sl-color-hairline-light: var(--sl-color-gray-5);--sl-color-hairline: var(--sl-color-gray-6);--sl-color-hairline-shade: var(--sl-color-black);--sl-color-backdrop-overlay: hsla(223, 13%, 10%, .66);--sl-shadow-sm: 0px 1px 1px hsla(0, 0%, 0%, .12), 0px 2px 1px hsla(0, 0%, 0%, .24);--sl-shadow-md: 0px 8px 4px hsla(0, 0%, 0%, .08), 0px 5px 2px hsla(0, 0%, 0%, .08), 0px 3px 2px hsla(0, 0%, 0%, .12), 0px 1px 1px hsla(0, 0%, 0%, .15);--sl-shadow-lg: 0px 25px 7px hsla(0, 0%, 0%, .03), 0px 16px 6px hsla(0, 0%, 0%, .1), 0px 9px 5px hsla(223, 13%, 10%, .33), 0px 4px 4px hsla(0, 0%, 0%, .75), 0px 4px 2px hsla(0, 0%, 0%, .25);--sl-text-2xs: .75rem;--sl-text-xs: .8125rem;--sl-text-sm: .875rem;--sl-text-base: 1rem;--sl-text-lg: 1.125rem;--sl-text-xl: 1.25rem;--sl-text-2xl: 1.5rem;--sl-text-3xl: 1.8125rem;--sl-text-4xl: 2.1875rem;--sl-text-5xl: 2.625rem;--sl-text-6xl: 4rem;--sl-text-body: var(--sl-text-base);--sl-text-body-sm: var(--sl-text-xs);--sl-text-code: var(--sl-text-sm);--sl-text-code-sm: var(--sl-text-xs);--sl-text-h1: var(--sl-text-4xl);--sl-text-h2: var(--sl-text-3xl);--sl-text-h3: var(--sl-text-2xl);--sl-text-h4: var(--sl-text-xl);--sl-text-h5: var(--sl-text-lg);--sl-line-height: 1.75;--sl-line-height-headings: 1.2;--sl-font-system: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--sl-font-system-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--__sl-font: var(--sl-font, var(--sl-font-system)), var(--sl-font-system);--__sl-font-mono: var(--sl-font-mono, var(--sl-font-system-mono)), var(--sl-font-system-mono);--sl-nav-height: 3.5rem;--sl-nav-pad-x: 1rem;--sl-nav-pad-y: .75rem;--sl-mobile-toc-height: 3rem;--sl-sidebar-width: 18.75rem;--sl-sidebar-pad-x: 1rem;--sl-content-width: 45rem;--sl-content-pad-x: 1rem;--sl-menu-button-size: 2rem;--sl-nav-gap: var(--sl-content-pad-x);--sl-outline-offset-inside: -.1875rem;--sl-z-index-toc: 4;--sl-z-index-menu: 5;--sl-z-index-navbar: 10;--sl-z-index-skiplink: 20}:root[data-theme=light],[data-theme=light] ::backdrop{--sl-color-white: hsl(224, 10%, 10%);--sl-color-gray-1: hsl(224, 14%, 16%);--sl-color-gray-2: hsl(224, 10%, 23%);--sl-color-gray-3: hsl(224, 7%, 36%);--sl-color-gray-4: hsl(224, 6%, 56%);--sl-color-gray-5: hsl(224, 6%, 77%);--sl-color-gray-6: hsl(224, 20%, 94%);--sl-color-gray-7: hsl(224, 19%, 97%);--sl-color-black: hsl(0, 0%, 100%);--sl-color-orange-high: hsl(var(--sl-hue-orange), 80%, 25%);--sl-color-orange: hsl(var(--sl-hue-orange), 90%, 60%);--sl-color-orange-low: hsl(var(--sl-hue-orange), 90%, 88%);--sl-color-green-high: hsl(var(--sl-hue-green), 80%, 22%);--sl-color-green: hsl(var(--sl-hue-green), 90%, 46%);--sl-color-green-low: hsl(var(--sl-hue-green), 85%, 90%);--sl-color-blue-high: hsl(var(--sl-hue-blue), 80%, 30%);--sl-color-blue: hsl(var(--sl-hue-blue), 90%, 60%);--sl-color-blue-low: hsl(var(--sl-hue-blue), 88%, 90%);--sl-color-purple-high: hsl(var(--sl-hue-purple), 90%, 30%);--sl-color-purple: hsl(var(--sl-hue-purple), 90%, 60%);--sl-color-purple-low: hsl(var(--sl-hue-purple), 80%, 90%);--sl-color-red-high: hsl(var(--sl-hue-red), 80%, 30%);--sl-color-red: hsl(var(--sl-hue-red), 90%, 60%);--sl-color-red-low: hsl(var(--sl-hue-red), 80%, 90%);--sl-color-accent-high: hsl(234, 80%, 30%);--sl-color-accent: hsl(234, 90%, 60%);--sl-color-accent-low: hsl(234, 88%, 90%);--sl-color-text-accent: var(--sl-color-accent);--sl-color-text-invert: var(--sl-color-black);--sl-color-bg-nav: var(--sl-color-gray-7);--sl-color-bg-sidebar: var(--sl-color-bg);--sl-color-bg-inline-code: var(--sl-color-gray-6);--sl-color-bg-accent: var(--sl-color-accent);--sl-color-hairline-light: var(--sl-color-gray-6);--sl-color-hairline-shade: var(--sl-color-gray-6);--sl-color-backdrop-overlay: hsla(225, 9%, 36%, .66);--sl-shadow-sm: 0px 1px 1px hsla(0, 0%, 0%, .06), 0px 2px 1px hsla(0, 0%, 0%, .06);--sl-shadow-md: 0px 8px 4px hsla(0, 0%, 0%, .03), 0px 5px 2px hsla(0, 0%, 0%, .03), 0px 3px 2px hsla(0, 0%, 0%, .06), 0px 1px 1px hsla(0, 0%, 0%, .06);--sl-shadow-lg: 0px 25px 7px rgba(0, 0, 0, .01), 0px 16px 6px hsla(0, 0%, 0%, .03), 0px 9px 5px hsla(223, 13%, 10%, .08), 0px 4px 4px hsla(0, 0%, 0%, .16), 0px 4px 2px hsla(0, 0%, 0%, .04)}@media (min-width: 50em){:root{--sl-nav-height: 4rem;--sl-nav-pad-x: 1.5rem;--sl-text-h1: var(--sl-text-5xl);--sl-text-h2: var(--sl-text-4xl);--sl-text-h3: var(--sl-text-3xl);--sl-text-h4: var(--sl-text-2xl)}}@media (min-width: 72rem){:root{--sl-content-pad-x: 1.5rem;--sl-mobile-toc-height: 0rem}}*,*:before,*:after{box-sizing:border-box}*{margin:0}html{color-scheme:dark;accent-color:var(--sl-color-accent)}html[data-theme=light]{color-scheme:light}body{font-family:var(--__sl-font);line-height:var(--sl-line-height);-webkit-font-smoothing:antialiased;color:var(--sl-color-text);background-color:var(--sl-color-bg)}input,button,textarea,select{font:inherit}p,h1,h2,h3,h4,h5,h6,code{overflow-wrap:anywhere}code{font-family:var(--__sl-font-mono)}:root{--astro-code-foreground: var(--sl-color-white);--astro-code-background: var(--sl-color-gray-6);--astro-code-token-constant: var(--sl-color-blue-high);--astro-code-token-string: var(--sl-color-green-high);--astro-code-token-comment: var(--sl-color-gray-2);--astro-code-token-keyword: var(--sl-color-purple-high);--astro-code-token-parameter: var(--sl-color-red-high);--astro-code-token-function: var(--sl-color-red-high);--astro-code-token-string-expression: var(--sl-color-green-high);--astro-code-token-punctuation: var(--sl-color-gray-2);--astro-code-token-link: var(--sl-color-blue-high)}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.sl-hidden{display:none}.sl-flex{display:flex}.sl-block{display:block}@media (min-width: 50rem){.md\:sl-hidden{display:none}.md\:sl-flex{display:flex}.md\:sl-block{display:block}}@media (min-width: 72rem){.lg\:sl-hidden{display:none}.lg\:sl-flex{display:flex}.lg\:sl-block{display:block}}[data-theme=light] .light\:sl-hidden,[data-theme=dark] .dark\:sl-hidden{display:none}[dir=rtl] .rtl\:flip:not(:where([dir=rtl] [dir=ltr] *)){transform:scaleX(-1)}.sl-banner:where(.astro-laz2plt2){--__sl-banner-text: var(--sl-color-banner-text, var(--sl-color-text-invert));padding:var(--sl-nav-pad-y) var(--sl-nav-pad-x);background-color:var(--sl-color-banner-bg, var(--sl-color-bg-accent));color:var(--__sl-banner-text);line-height:var(--sl-line-height-headings);text-align:center;text-wrap:balance;box-shadow:var(--sl-shadow-sm)}.sl-banner:where(.astro-laz2plt2) a{color:var(--__sl-banner-text)}.content-panel:where(.astro-7nkwcw3z){padding:1.5rem var(--sl-content-pad-x)}.content-panel:where(.astro-7nkwcw3z)+.content-panel:where(.astro-7nkwcw3z){border-top:1px solid var(--sl-color-hairline)}.sl-container:where(.astro-7nkwcw3z){max-width:var(--sl-content-width)}.sl-container:where(.astro-7nkwcw3z)>*+*{margin-top:1.5rem}@media (min-width: 72rem){.sl-container:where(.astro-7nkwcw3z){margin-inline:var(--sl-content-margin-inline, auto)}}p:where(.astro-opzsrvew){border:1px solid var(--sl-color-orange);padding:.75em 1em;background-color:var(--sl-color-orange-low);color:var(--sl-color-orange-high);width:max-content;max-width:100%;align-items:center;gap:.75em;font-size:var(--sl-text-body-sm);line-height:var(--sl-line-height-headings)}a:where(.astro-eez2twj6){gap:.5rem;align-items:center;text-decoration:none;color:var(--sl-color-gray-3)}a:where(.astro-eez2twj6):hover{color:var(--sl-color-white)}.pagination-links:where(.astro-u2l5gyhi){display:grid;grid-template-columns:repeat(auto-fit,minmax(min(18rem,100%),1fr));gap:1rem}a:where(.astro-u2l5gyhi){display:flex;align-items:center;justify-content:flex-start;gap:.5rem;width:100%;flex-basis:calc(50% - .5rem);flex-grow:1;border:1px solid var(--sl-color-gray-5);border-radius:.5rem;padding:1rem;text-decoration:none;color:var(--sl-color-gray-2);box-shadow:var(--sl-shadow-md);overflow-wrap:anywhere}:where(.astro-u2l5gyhi)[rel=next]{justify-content:end;text-align:end;flex-direction:row-reverse}a:where(.astro-u2l5gyhi):hover{border-color:var(--sl-color-gray-2)}.link-title:where(.astro-u2l5gyhi){color:var(--sl-color-white);font-size:var(--sl-text-2xl);line-height:var(--sl-line-height-headings)}svg:where(.astro-u2l5gyhi){flex-shrink:0}footer:where(.astro-3yyafb3n){flex-direction:column;gap:1.5rem}.meta:where(.astro-3yyafb3n){gap:.75rem 3rem;justify-content:space-between;flex-wrap:wrap;margin-top:3rem;font-size:var(--sl-text-sm);color:var(--sl-color-gray-3)}.meta:where(.astro-3yyafb3n)>p:only-child{margin-inline-start:auto}.kudos:where(.astro-3yyafb3n){align-items:center;gap:.5em;margin:1.5rem auto;font-size:var(--sl-text-xs);text-decoration:none;color:var(--sl-color-gray-3)}.kudos:where(.astro-3yyafb3n) svg{color:var(--sl-color-orange)}.kudos:where(.astro-3yyafb3n):hover{color:var(--sl-color-white)}label:where(.astro-4yphtoen){--sl-label-icon-size: .875rem;--sl-caret-size: 1.25rem;--sl-inline-padding: .5rem;position:relative;display:flex;align-items:center;gap:.25rem;color:var(--sl-color-gray-1)}label:where(.astro-4yphtoen):hover{color:var(--sl-color-gray-2)}.icon:where(.astro-4yphtoen){position:absolute;top:50%;transform:translateY(-50%);pointer-events:none}.label-icon:where(.astro-4yphtoen){font-size:var(--sl-label-icon-size);inset-inline-start:0}.caret:where(.astro-4yphtoen){font-size:var(--sl-caret-size);inset-inline-end:0}select:where(.astro-4yphtoen){border:0;padding-block:.625rem;padding-inline:calc(var(--sl-label-icon-size) + var(--sl-inline-padding) + .25rem) calc(var(--sl-caret-size) + var(--sl-inline-padding) + .25rem);margin-inline:calc(var(--sl-inline-padding) * -1);width:calc(var(--sl-select-width) + var(--sl-inline-padding) * 2);background-color:transparent;text-overflow:ellipsis;color:inherit;cursor:pointer;appearance:none}option:where(.astro-4yphtoen){background-color:var(--sl-color-bg-nav);color:var(--sl-color-gray-1)}@media (min-width: 50rem){select:where(.astro-4yphtoen){font-size:var(--sl-text-sm)}}.pagefind-ui__result.svelte-j9e30.svelte-j9e30{list-style-type:none;display:flex;align-items:flex-start;gap:min(calc(40px * var(--pagefind-ui-scale)),3%);padding:calc(30px * var(--pagefind-ui-scale)) 0 calc(40px * var(--pagefind-ui-scale));border-top:solid var(--pagefind-ui-border-width) var(--pagefind-ui-border)}.pagefind-ui__result.svelte-j9e30.svelte-j9e30:last-of-type{border-bottom:solid var(--pagefind-ui-border-width) var(--pagefind-ui-border)}.pagefind-ui__result-thumb.svelte-j9e30.svelte-j9e30{width:min(30%,calc((30% - (100px * var(--pagefind-ui-scale))) * 100000));max-width:calc(120px * var(--pagefind-ui-scale));margin-top:calc(10px * var(--pagefind-ui-scale));aspect-ratio:var(--pagefind-ui-image-box-ratio);position:relative}.pagefind-ui__result-image.svelte-j9e30.svelte-j9e30{display:block;position:absolute;left:50%;transform:translate(-50%);font-size:0;width:auto;height:auto;max-width:100%;max-height:100%;border-radius:var(--pagefind-ui-image-border-radius)}.pagefind-ui__result-inner.svelte-j9e30.svelte-j9e30{flex:1;display:flex;flex-direction:column;align-items:flex-start;margin-top:calc(10px * var(--pagefind-ui-scale))}.pagefind-ui__result-title.svelte-j9e30.svelte-j9e30{display:inline-block;font-weight:700;font-size:calc(21px * var(--pagefind-ui-scale));margin-top:0;margin-bottom:0}.pagefind-ui__result-title.svelte-j9e30 .pagefind-ui__result-link.svelte-j9e30{color:var(--pagefind-ui-text);text-decoration:none}.pagefind-ui__result-title.svelte-j9e30 .pagefind-ui__result-link.svelte-j9e30:hover{text-decoration:underline}.pagefind-ui__result-excerpt.svelte-j9e30.svelte-j9e30{display:inline-block;font-weight:400;font-size:calc(16px * var(--pagefind-ui-scale));margin-top:calc(4px * var(--pagefind-ui-scale));margin-bottom:0;min-width:calc(250px * var(--pagefind-ui-scale))}.pagefind-ui__loading.svelte-j9e30.svelte-j9e30{color:var(--pagefind-ui-text);background-color:var(--pagefind-ui-text);border-radius:var(--pagefind-ui-border-radius);opacity:.1;pointer-events:none}.pagefind-ui__result-tags.svelte-j9e30.svelte-j9e30{list-style-type:none;padding:0;display:flex;gap:calc(20px * var(--pagefind-ui-scale));flex-wrap:wrap;margin-top:calc(20px * var(--pagefind-ui-scale))}.pagefind-ui__result-tag.svelte-j9e30.svelte-j9e30{padding:calc(4px * var(--pagefind-ui-scale)) calc(8px * var(--pagefind-ui-scale));font-size:calc(14px * var(--pagefind-ui-scale));border-radius:var(--pagefind-ui-border-radius);background-color:var(--pagefind-ui-tag)}.pagefind-ui__result.svelte-4xnkmf.svelte-4xnkmf{list-style-type:none;display:flex;align-items:flex-start;gap:min(calc(40px * var(--pagefind-ui-scale)),3%);padding:calc(30px * var(--pagefind-ui-scale)) 0 calc(40px * var(--pagefind-ui-scale));border-top:solid var(--pagefind-ui-border-width) var(--pagefind-ui-border)}.pagefind-ui__result.svelte-4xnkmf.svelte-4xnkmf:last-of-type{border-bottom:solid var(--pagefind-ui-border-width) var(--pagefind-ui-border)}.pagefind-ui__result-nested.svelte-4xnkmf.svelte-4xnkmf{display:flex;flex-direction:column;padding-left:calc(20px * var(--pagefind-ui-scale))}.pagefind-ui__result-nested.svelte-4xnkmf.svelte-4xnkmf:first-of-type{padding-top:calc(10px * var(--pagefind-ui-scale))}.pagefind-ui__result-nested.svelte-4xnkmf .pagefind-ui__result-link.svelte-4xnkmf{font-size:.9em;position:relative}.pagefind-ui__result-nested.svelte-4xnkmf .pagefind-ui__result-link.svelte-4xnkmf:before{content:"⤷ ";position:absolute;top:0;right:calc(100% + .1em)}.pagefind-ui__result-thumb.svelte-4xnkmf.svelte-4xnkmf{width:min(30%,calc((30% - (100px * var(--pagefind-ui-scale))) * 100000));max-width:calc(120px * var(--pagefind-ui-scale));margin-top:calc(10px * var(--pagefind-ui-scale));aspect-ratio:var(--pagefind-ui-image-box-ratio);position:relative}.pagefind-ui__result-image.svelte-4xnkmf.svelte-4xnkmf{display:block;position:absolute;left:50%;transform:translate(-50%);font-size:0;width:auto;height:auto;max-width:100%;max-height:100%;border-radius:var(--pagefind-ui-image-border-radius)}.pagefind-ui__result-inner.svelte-4xnkmf.svelte-4xnkmf{flex:1;display:flex;flex-direction:column;align-items:flex-start;margin-top:calc(10px * var(--pagefind-ui-scale))}.pagefind-ui__result-title.svelte-4xnkmf.svelte-4xnkmf{display:inline-block;font-weight:700;font-size:calc(21px * var(--pagefind-ui-scale));margin-top:0;margin-bottom:0}.pagefind-ui__result-title.svelte-4xnkmf .pagefind-ui__result-link.svelte-4xnkmf{color:var(--pagefind-ui-text);text-decoration:none}.pagefind-ui__result-title.svelte-4xnkmf .pagefind-ui__result-link.svelte-4xnkmf:hover{text-decoration:underline}.pagefind-ui__result-excerpt.svelte-4xnkmf.svelte-4xnkmf{display:inline-block;font-weight:400;font-size:calc(16px * var(--pagefind-ui-scale));margin-top:calc(4px * var(--pagefind-ui-scale));margin-bottom:0;min-width:calc(250px * var(--pagefind-ui-scale))}.pagefind-ui__loading.svelte-4xnkmf.svelte-4xnkmf{color:var(--pagefind-ui-text);background-color:var(--pagefind-ui-text);border-radius:var(--pagefind-ui-border-radius);opacity:.1;pointer-events:none}.pagefind-ui__result-tags.svelte-4xnkmf.svelte-4xnkmf{list-style-type:none;padding:0;display:flex;gap:calc(20px * var(--pagefind-ui-scale));flex-wrap:wrap;margin-top:calc(20px * var(--pagefind-ui-scale))}.pagefind-ui__result-tag.svelte-4xnkmf.svelte-4xnkmf{padding:calc(4px * var(--pagefind-ui-scale)) calc(8px * var(--pagefind-ui-scale));font-size:calc(14px * var(--pagefind-ui-scale));border-radius:var(--pagefind-ui-border-radius);background-color:var(--pagefind-ui-tag)}legend.svelte-1v2r7ls.svelte-1v2r7ls{position:absolute;clip:rect(0 0 0 0)}.pagefind-ui__filter-panel.svelte-1v2r7ls.svelte-1v2r7ls{min-width:min(calc(260px * var(--pagefind-ui-scale)),100%);flex:1;display:flex;flex-direction:column;margin-top:calc(20px * var(--pagefind-ui-scale))}.pagefind-ui__filter-group.svelte-1v2r7ls.svelte-1v2r7ls{border:0;padding:0}.pagefind-ui__filter-block.svelte-1v2r7ls.svelte-1v2r7ls{padding:0;display:block;border-bottom:solid calc(2px * var(--pagefind-ui-scale)) var(--pagefind-ui-border);padding:calc(20px * var(--pagefind-ui-scale)) 0}.pagefind-ui__filter-name.svelte-1v2r7ls.svelte-1v2r7ls{font-size:calc(16px * var(--pagefind-ui-scale));position:relative;display:flex;align-items:center;list-style:none;font-weight:700;cursor:pointer;height:calc(24px * var(--pagefind-ui-scale))}.pagefind-ui__filter-name.svelte-1v2r7ls.svelte-1v2r7ls::-webkit-details-marker{display:none}.pagefind-ui__filter-name.svelte-1v2r7ls.svelte-1v2r7ls:after{position:absolute;content:"";right:calc(6px * var(--pagefind-ui-scale));top:50%;width:calc(8px * var(--pagefind-ui-scale));height:calc(8px * var(--pagefind-ui-scale));border:solid calc(2px * var(--pagefind-ui-scale)) currentColor;border-right:0;border-top:0;transform:translateY(-70%) rotate(-45deg)}.pagefind-ui__filter-block[open].svelte-1v2r7ls .pagefind-ui__filter-name.svelte-1v2r7ls:after{transform:translateY(-70%) rotate(-225deg)}.pagefind-ui__filter-group.svelte-1v2r7ls.svelte-1v2r7ls{display:flex;flex-direction:column;gap:calc(20px * var(--pagefind-ui-scale));padding-top:calc(30px * var(--pagefind-ui-scale))}.pagefind-ui__filter-value.svelte-1v2r7ls.svelte-1v2r7ls{position:relative;display:flex;align-items:center;gap:calc(8px * var(--pagefind-ui-scale))}.pagefind-ui__filter-value.svelte-1v2r7ls.svelte-1v2r7ls:before{position:absolute;content:"";top:50%;left:calc(8px * var(--pagefind-ui-scale));width:0px;height:0px;border:solid 1px #fff;opacity:0;transform:translate(calc(4.5px * var(--pagefind-ui-scale) * -1),calc(.8px * var(--pagefind-ui-scale))) skew(-5deg) rotate(-45deg);transform-origin:top left;border-top:0;border-right:0;pointer-events:none}.pagefind-ui__filter-value.pagefind-ui__filter-value--checked.svelte-1v2r7ls.svelte-1v2r7ls:before{opacity:1;width:calc(9px * var(--pagefind-ui-scale));height:calc(4px * var(--pagefind-ui-scale));transition:width .1s ease-out .1s,height .1s ease-in}.pagefind-ui__filter-checkbox.svelte-1v2r7ls.svelte-1v2r7ls{margin:0;width:calc(16px * var(--pagefind-ui-scale));height:calc(16px * var(--pagefind-ui-scale));border:solid 1px var(--pagefind-ui-border);appearance:none;-webkit-appearance:none;border-radius:calc(var(--pagefind-ui-border-radius) / 2);background-color:var(--pagefind-ui-background);cursor:pointer}.pagefind-ui__filter-checkbox.svelte-1v2r7ls.svelte-1v2r7ls:checked{background-color:var(--pagefind-ui-primary);border:solid 1px var(--pagefind-ui-primary)}.pagefind-ui__filter-label.svelte-1v2r7ls.svelte-1v2r7ls{cursor:pointer;font-size:calc(16px * var(--pagefind-ui-scale));font-weight:400}.pagefind-ui--reset *:where(:not(html,iframe,canvas,img,svg,video):not(svg *,symbol *)){all:unset;display:revert;outline:revert}.pagefind-ui--reset *,.pagefind-ui--reset *:before,.pagefind-ui--reset *:after{box-sizing:border-box}.pagefind-ui--reset a,.pagefind-ui--reset button{cursor:revert}.pagefind-ui--reset ol,.pagefind-ui--reset ul,.pagefind-ui--reset menu{list-style:none}.pagefind-ui--reset img{max-width:100%}.pagefind-ui--reset table{border-collapse:collapse}.pagefind-ui--reset input,.pagefind-ui--reset textarea{-webkit-user-select:auto}.pagefind-ui--reset textarea{white-space:revert}.pagefind-ui--reset meter{-webkit-appearance:revert;appearance:revert}.pagefind-ui--reset ::placeholder{color:unset}.pagefind-ui--reset :where([hidden]){display:none}.pagefind-ui--reset :where([contenteditable]:not([contenteditable=false])){-moz-user-modify:read-write;-webkit-user-modify:read-write;overflow-wrap:break-word;-webkit-line-break:after-white-space;-webkit-user-select:auto}.pagefind-ui--reset :where([draggable=true]){-webkit-user-drag:element}.pagefind-ui--reset mark{all:revert}:root{--pagefind-ui-scale:.8;--pagefind-ui-primary:#393939;--pagefind-ui-text:#393939;--pagefind-ui-background:#ffffff;--pagefind-ui-border:#eeeeee;--pagefind-ui-tag:#eeeeee;--pagefind-ui-border-width:2px;--pagefind-ui-border-radius:8px;--pagefind-ui-image-border-radius:8px;--pagefind-ui-image-box-ratio:3 / 2;--pagefind-ui-font:system, -apple-system, "BlinkMacSystemFont", ".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif}.pagefind-ui.svelte-e9gkc3{width:100%;color:var(--pagefind-ui-text);font-family:var(--pagefind-ui-font)}.pagefind-ui__hidden.svelte-e9gkc3{display:none!important}.pagefind-ui__suppressed.svelte-e9gkc3{opacity:0;pointer-events:none}.pagefind-ui__form.svelte-e9gkc3{position:relative}.pagefind-ui__form.svelte-e9gkc3:before{background-color:var(--pagefind-ui-text);width:calc(18px * var(--pagefind-ui-scale));height:calc(18px * var(--pagefind-ui-scale));top:calc(23px * var(--pagefind-ui-scale));left:calc(20px * var(--pagefind-ui-scale));content:"";position:absolute;display:block;opacity:.7;-webkit-mask-image:url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.7549 11.255H11.9649L11.6849 10.985C12.6649 9.845 13.2549 8.365 13.2549 6.755C13.2549 3.165 10.3449 0.255005 6.75488 0.255005C3.16488 0.255005 0.254883 3.165 0.254883 6.755C0.254883 10.345 3.16488 13.255 6.75488 13.255C8.36488 13.255 9.84488 12.665 10.9849 11.685L11.2549 11.965V12.755L16.2549 17.745L17.7449 16.255L12.7549 11.255ZM6.75488 11.255C4.26488 11.255 2.25488 9.245 2.25488 6.755C2.25488 4.26501 4.26488 2.255 6.75488 2.255C9.24488 2.255 11.2549 4.26501 11.2549 6.755C11.2549 9.245 9.24488 11.255 6.75488 11.255Z' fill='%23000000'/%3E%3C/svg%3E%0A");mask-image:url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.7549 11.255H11.9649L11.6849 10.985C12.6649 9.845 13.2549 8.365 13.2549 6.755C13.2549 3.165 10.3449 0.255005 6.75488 0.255005C3.16488 0.255005 0.254883 3.165 0.254883 6.755C0.254883 10.345 3.16488 13.255 6.75488 13.255C8.36488 13.255 9.84488 12.665 10.9849 11.685L11.2549 11.965V12.755L16.2549 17.745L17.7449 16.255L12.7549 11.255ZM6.75488 11.255C4.26488 11.255 2.25488 9.245 2.25488 6.755C2.25488 4.26501 4.26488 2.255 6.75488 2.255C9.24488 2.255 11.2549 4.26501 11.2549 6.755C11.2549 9.245 9.24488 11.255 6.75488 11.255Z' fill='%23000000'/%3E%3C/svg%3E%0A");-webkit-mask-size:100%;mask-size:100%;z-index:9;pointer-events:none}.pagefind-ui__search-input.svelte-e9gkc3{height:calc(64px * var(--pagefind-ui-scale));padding:0 calc(70px * var(--pagefind-ui-scale)) 0 calc(54px * var(--pagefind-ui-scale));background-color:var(--pagefind-ui-background);border:var(--pagefind-ui-border-width) solid var(--pagefind-ui-border);border-radius:var(--pagefind-ui-border-radius);font-size:calc(21px * var(--pagefind-ui-scale));position:relative;appearance:none;-webkit-appearance:none;display:flex;width:100%;box-sizing:border-box;font-weight:700}.pagefind-ui__search-input.svelte-e9gkc3::placeholder{opacity:.2}.pagefind-ui__search-clear.svelte-e9gkc3{position:absolute;top:calc(3px * var(--pagefind-ui-scale));right:calc(3px * var(--pagefind-ui-scale));height:calc(58px * var(--pagefind-ui-scale));padding:0 calc(15px * var(--pagefind-ui-scale)) 0 calc(2px * var(--pagefind-ui-scale));color:var(--pagefind-ui-text);font-size:calc(14px * var(--pagefind-ui-scale));cursor:pointer;background-color:var(--pagefind-ui-background);border-radius:var(--pagefind-ui-border-radius)}.pagefind-ui__drawer.svelte-e9gkc3{gap:calc(60px * var(--pagefind-ui-scale));display:flex;flex-direction:row;flex-wrap:wrap}.pagefind-ui__results-area.svelte-e9gkc3{min-width:min(calc(400px * var(--pagefind-ui-scale)),100%);flex:1000;margin-top:calc(20px * var(--pagefind-ui-scale))}.pagefind-ui__results.svelte-e9gkc3{padding:0}.pagefind-ui__message.svelte-e9gkc3{box-sizing:content-box;font-size:calc(16px * var(--pagefind-ui-scale));height:calc(24px * var(--pagefind-ui-scale));padding:calc(20px * var(--pagefind-ui-scale)) 0;display:flex;align-items:center;font-weight:700;margin-top:0}.pagefind-ui__button.svelte-e9gkc3{margin-top:calc(40px * var(--pagefind-ui-scale));border:var(--pagefind-ui-border-width) solid var(--pagefind-ui-border);border-radius:var(--pagefind-ui-border-radius);height:calc(48px * var(--pagefind-ui-scale));padding:0 calc(12px * var(--pagefind-ui-scale));font-size:calc(16px * var(--pagefind-ui-scale));color:var(--pagefind-ui-primary);background:var(--pagefind-ui-background);width:100%;text-align:center;font-weight:700;cursor:pointer}.pagefind-ui__button.svelte-e9gkc3:hover{border-color:var(--pagefind-ui-primary);color:var(--pagefind-ui-primary);background:var(--pagefind-ui-background)}[data-search-modal-open]{overflow:hidden}#starlight__search{--sl-search-result-spacing: calc(1.25rem * var(--pagefind-ui-scale));--sl-search-result-pad-inline-start: calc(3.75rem * var(--pagefind-ui-scale));--sl-search-result-pad-inline-end: calc(1.25rem * var(--pagefind-ui-scale));--sl-search-result-pad-block: calc(.9375rem * var(--pagefind-ui-scale));--sl-search-result-nested-pad-block: calc(.625rem * var(--pagefind-ui-scale));--sl-search-corners: calc(.3125rem * var(--pagefind-ui-scale));--sl-search-page-icon-size: calc(1.875rem * var(--pagefind-ui-scale));--sl-search-page-icon-inline-start: calc( (var(--sl-search-result-pad-inline-start) - var(--sl-search-page-icon-size)) / 2 );--sl-search-tree-diagram-size: calc(2.5rem * var(--pagefind-ui-scale));--sl-search-tree-diagram-inline-start: calc( (var(--sl-search-result-pad-inline-start) - var(--sl-search-tree-diagram-size)) / 2 )}#starlight__search .pagefind-ui__form:before{--pagefind-ui-text: var(--sl-color-gray-1);opacity:1}#starlight__search .pagefind-ui__search-input{color:var(--sl-color-white);font-weight:400;width:calc(100% - var(--sl-search-cancel-space))}#starlight__search input:focus{--pagefind-ui-border: var(--sl-color-accent)}#starlight__search .pagefind-ui__search-clear{inset-inline-end:var(--sl-search-cancel-space);width:calc(60px * var(--pagefind-ui-scale));padding:0;background-color:transparent;overflow:hidden}#starlight__search .pagefind-ui__search-clear:focus{outline:1px solid var(--sl-color-accent)}#starlight__search .pagefind-ui__search-clear:before{content:"";-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m13.41 12 6.3-6.29a1 1 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1 1 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 .33 1.64 1 1 0 0 0 1.09-.22l6.29-6.3 6.29 6.3a1 1 0 0 0 1.64-.33 1 1 0 0 0-.22-1.09L13.41 12Z'/%3E%3C/svg%3E") center / 50% no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m13.41 12 6.3-6.29a1 1 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1 1 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 .33 1.64 1 1 0 0 0 1.09-.22l6.29-6.3 6.29 6.3a1 1 0 0 0 1.64-.33 1 1 0 0 0-.22-1.09L13.41 12Z'/%3E%3C/svg%3E") center / 50% no-repeat;background-color:var(--sl-color-text-accent);display:block;width:100%;height:100%}#starlight__search .pagefind-ui__results>*+*{margin-top:var(--sl-search-result-spacing)}#starlight__search .pagefind-ui__result{border:0;padding:0}#starlight__search .pagefind-ui__result-nested{position:relative;padding:var(--sl-search-result-nested-pad-block) var(--sl-search-result-pad-inline-end);padding-inline-start:var(--sl-search-result-pad-inline-start)}#starlight__search .pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)),#starlight__search .pagefind-ui__result-nested{position:relative;background-color:var(--sl-color-black)}#starlight__search .pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):hover,#starlight__search .pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):focus-within,#starlight__search .pagefind-ui__result-nested:hover,#starlight__search .pagefind-ui__result-nested:focus-within{outline:1px solid var(--sl-color-accent-high)}#starlight__search .pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):focus-within,#starlight__search .pagefind-ui__result-nested:focus-within{background-color:var(--sl-color-accent-low)}#starlight__search .pagefind-ui__result-thumb,#starlight__search .pagefind-ui__result-inner{margin-top:0}#starlight__search .pagefind-ui__result-inner>:first-child{border-radius:var(--sl-search-corners) var(--sl-search-corners) 0 0}#starlight__search .pagefind-ui__result-inner>:last-child{border-radius:0 0 var(--sl-search-corners) var(--sl-search-corners)}#starlight__search .pagefind-ui__result-inner>.pagefind-ui__result-title{padding:var(--sl-search-result-pad-block) var(--sl-search-result-pad-inline-end);padding-inline-start:var(--sl-search-result-pad-inline-start)}#starlight__search .pagefind-ui__result-inner>.pagefind-ui__result-title:before{content:"";position:absolute;inset-block:0;inset-inline-start:var(--sl-search-page-icon-inline-start);width:var(--sl-search-page-icon-size);background:var(--sl-color-gray-3);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3V8l-6-6a1 1 0 0 0-1 0H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V9Zm-6-4 3 3h-2a1 1 0 0 1-1-1V5Zm4 14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z'/%3E%3C/svg%3E") center no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3V8l-6-6a1 1 0 0 0-1 0H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V9Zm-6-4 3 3h-2a1 1 0 0 1-1-1V5Zm4 14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z'/%3E%3C/svg%3E") center no-repeat}#starlight__search .pagefind-ui__result-inner{align-items:stretch;gap:1px}#starlight__search .pagefind-ui__result-link{position:unset;--pagefind-ui-text: var(--sl-color-white);font-weight:600}#starlight__search .pagefind-ui__result-link:hover{text-decoration:none}#starlight__search .pagefind-ui__result-nested .pagefind-ui__result-link:before{content:unset}#starlight__search .pagefind-ui__result-nested:before{content:"";position:absolute;inset-block:0;inset-inline-start:var(--sl-search-tree-diagram-inline-start);width:var(--sl-search-tree-diagram-size);background:var(--sl-color-gray-4);-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m6-988H8'/%3E%3C/svg%3E") 0% 0% / 100% no-repeat;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m6-988H8'/%3E%3C/svg%3E") 0% 0% / 100% no-repeat}#starlight__search .pagefind-ui__result-nested:last-of-type:before{-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 16 16'%3E%3Cpath d='M8 0v12m6 0H8'/%3E%3C/svg%3E");mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 16 16'%3E%3Cpath d='M8 0v12m6 0H8'/%3E%3C/svg%3E")}[dir=rtl] .pagefind-ui__result-title:before,[dir=rtl] .pagefind-ui__result-nested:before{transform:scaleX(-1)}#starlight__search .pagefind-ui__result-link:after{content:"";position:absolute;inset:0}#starlight__search .pagefind-ui__result-excerpt{font-size:calc(1rem * var(--pagefind-ui-scale));overflow-wrap:anywhere}#starlight__search mark{color:var(--sl-color-gray-2);background-color:transparent;font-weight:600}#starlight__search .pagefind-ui__filter-value:before{border-color:var(--sl-color-text-invert)}#starlight__search .pagefind-ui__result-tags{background-color:var(--sl-color-black);margin-top:0;padding:var(--sl-search-result-nested-pad-block) var(--sl-search-result-pad-inline-end)}site-search:where(.astro-v37mnknz){display:contents}button:where(.astro-v37mnknz)[data-open-modal]{display:flex;align-items:center;gap:.5rem;border:0;background-color:transparent;color:var(--sl-color-gray-1);cursor:pointer;height:2.5rem;font-size:var(--sl-text-xl)}@media (min-width: 50rem){button:where(.astro-v37mnknz)[data-open-modal]{border:1px solid var(--sl-color-gray-5);border-radius:.5rem;padding-inline-start:.75rem;padding-inline-end:.5rem;background-color:var(--sl-color-black);color:var(--sl-color-gray-2);font-size:var(--sl-text-sm);width:100%;max-width:22rem}button:where(.astro-v37mnknz)[data-open-modal]:hover{border-color:var(--sl-color-gray-2);color:var(--sl-color-white)}button:where(.astro-v37mnknz)[data-open-modal]>:last-child:where(.astro-v37mnknz){margin-inline-start:auto}}button:where(.astro-v37mnknz)>kbd:where(.astro-v37mnknz){border-radius:.25rem;font-size:var(--sl-text-2xs);gap:.25em;padding-inline:.375rem;background-color:var(--sl-color-gray-6)}kbd:where(.astro-v37mnknz){font-family:var(--__sl-font)}dialog:where(.astro-v37mnknz){margin:0;background-color:var(--sl-color-gray-6);border:1px solid var(--sl-color-gray-5);width:100%;max-width:100%;height:100%;max-height:100%;box-shadow:var(--sl-shadow-lg)}dialog:where(.astro-v37mnknz)[open]{display:flex}dialog:where(.astro-v37mnknz)::backdrop{background-color:var(--sl-color-backdrop-overlay);-webkit-backdrop-filter:blur(.25rem);backdrop-filter:blur(.25rem)}.dialog-frame:where(.astro-v37mnknz){position:relative;overflow:auto;flex-direction:column;flex-grow:1;gap:1rem;padding:1rem}button:where(.astro-v37mnknz)[data-close-modal]{position:absolute;z-index:1;align-items:center;align-self:flex-end;height:calc(64px * var(--pagefind-ui-scale));padding:.25rem;border:0;background:transparent;cursor:pointer;color:var(--sl-color-text-accent)}#starlight__search:where(.astro-v37mnknz){--pagefind-ui-primary: var(--sl-color-text);--pagefind-ui-text: var(--sl-color-gray-2);--pagefind-ui-font: var(--__sl-font);--pagefind-ui-background: var(--sl-color-black);--pagefind-ui-border: var(--sl-color-gray-5);--pagefind-ui-border-width: 1px;--pagefind-ui-tag: var(--sl-color-gray-5);--sl-search-cancel-space: 5rem}:root[data-theme=light] #starlight__search:where(.astro-v37mnknz){--pagefind-ui-tag: var(--sl-color-gray-6)}@media (min-width: 50rem){#starlight__search:where(.astro-v37mnknz){--sl-search-cancel-space: 0px}dialog:where(.astro-v37mnknz){margin:4rem auto auto;border-radius:.5rem;width:90%;max-width:40rem;height:max-content;min-height:15rem;max-height:calc(100% - 8rem)}.dialog-frame:where(.astro-v37mnknz){padding:1.5rem}}.site-title:where(.astro-m46x6ez3){align-items:center;gap:var(--sl-nav-gap);font-size:var(--sl-text-h4);font-weight:600;color:var(--sl-color-text-accent);text-decoration:none;white-space:nowrap}img:where(.astro-m46x6ez3){height:calc(var(--sl-nav-height) - 2 * var(--sl-nav-pad-y));width:auto;max-width:100%;object-fit:contain;object-position:0 50%}a:where(.astro-wy4te6ga){color:var(--sl-color-text-accent);padding:.5em;margin:-.5em}a:where(.astro-wy4te6ga):hover{opacity:.66}.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:1.5rem;padding:0}.card{height:100%;margin:0!important;transform:none!important}.card-grid[data-stagger] .card{animation:none!important}@font-face{font-family:Geist;src:url(https://assets.website-files.com/638d2ca7e839e538fc311594/638d2ca7e839e54527311772_Geist-Regular.woff2) format("woff2"),url(https://assets.website-files.com/638d2ca7e839e538fc311594/638d2ca7e839e54527311772_Geist-Regular.woff) format("woff");font-weight:400;font-style:normal}@font-face{font-family:Geist;src:url(https://assets.website-files.com/638d2ca7e839e538fc311594/638d2ca7e839e54527311773_Geist-Medium.woff2) format("woff2"),url(https://assets.website-files.com/638d2ca7e839e538fc311594/638d2ca7e839e54527311773_Geist-Medium.woff) format("woff");font-weight:500;font-style:normal}:root{--sl-font: "Geist", -apple-system, BlinkMacSystemFont, sans-serif}h1,h2,h3,h4,h5,h6{font-family:var(--sl-font);font-weight:500}.hero-image{width:100%;height:auto;max-width:1200px;margin:0 auto}.hero-image.large{max-width:1600px}.hero h1{font-weight:500;letter-spacing:-.02em}p,li{font-family:var(--sl-font);font-weight:400}:root{--p9-blue: #0091ff;--p9-dark-blue: #0d1b2a;--p9-light-blue: #61a0ff}@keyframes shimmer{0%{background-position:-200% center}to{background-position:200% center}}.video-container{position:relative;z-index:2;pointer-events:auto}.video-wrapper{position:relative;border-radius:12px;box-shadow:0 12px 32px #00000026;border:1px solid rgba(97,160,255,.1);overflow:hidden;transform:translateY(0);transition:transform .3s ease,box-shadow .3s ease}.video-wrapper iframe{position:relative;z-index:3;width:100%;height:calc(100% - 10px);border-radius:12px;border:none}[data-has-hero] .action{transition:transform .2s ease,box-shadow .2s ease}[data-has-hero] .action:hover{transform:translateY(-2px)}[data-has-hero] .action.primary{background-image:linear-gradient(135deg,var(--p9-blue),var(--p9-light-blue));border-radius:8px}[data-has-hero] .action.minimal{border-color:var(--p9-blue)}.section-title{color:var(--sl-color-white);font-size:2rem;margin:3rem auto;background-image:linear-gradient(to right,var(--sl-color-text),var(--p9-blue),var(--sl-color-text));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.card-grid[data-theme=light] .card{background-image:linear-gradient(to bottom right,#ffffffe6,#f0f5facc);border:1px solid rgba(0,145,255,.1)}.card-grid[data-theme=dark] .card{background-image:linear-gradient(to bottom right,#0d1b2ae6,#1b263bcc);border:1px solid rgba(0,145,255,.2)}.card{transition:transform .2s ease,box-shadow .2s ease;box-shadow:0 #0091ff1a}.card:hover{transform:translateY(-4px);box-shadow:0 #0091ff26}.download-section{margin:2rem auto;padding:1.5rem;background-image:linear-gradient(to bottom right,var(--sl-color-bg),var(--sl-color-bg-sidebar));border-radius:12px;border:1px solid rgba(0,145,255,.1)}.download-content{display:flex;align-items:center;gap:2rem;max-width:var(--sl-content-width);margin:0 auto}.download-section h3{font-size:1.2rem;margin:0;color:var(--sl-color-text)}.command-box{position:relative;flex-grow:1;margin:0;padding:.75rem;background-color:var(--sl-color-bg-sidebar);border-radius:8px}.command-box pre{margin:0;padding-right:4rem}.copy-button{position:absolute;right:.75rem;top:50%;transform:translateY(-50%);background:var(--sl-color-text-accent);color:#fff;border:none;border-radius:4px;padding:.3rem;cursor:pointer;font-size:1.2rem;transition:all .2s ease;display:flex;justify-content:center;align-items:center;gap:.4rem}.copy-button svg{width:1.2rem;height:1.2rem}.copy-button:hover{background:var(--p9-light-blue);transform:translateY(-50%) scale(1.05)}.oras-link{font-size:.8rem;color:var(--sl-color-text-accent);text-decoration:none;display:flex;align-items:center;gap:.3rem;white-space:nowrap}.oras-link:hover{text-decoration:underline;color:var(--p9-light-blue)}.modal-overlay{display:none;justify-content:center;align-items:center;position:fixed;top:0;left:0;width:100%;height:100%;background-color:#00000080;opacity:0;transition:opacity .3s ease}.modal-overlay.active{display:flex;opacity:1}.modal-container{padding:2rem;max-width:500px;width:calc(100% - 2rem);position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background-color:#fff;padding:20px;box-shadow:0 4px 6px #0000001a;border-radius:8px;z-index:1000}.modal-header{display:flex;justify-content:space-between;align-items:center}.modal-header h3{margin:0}.modal-close{background:none;border:none;font-size:1.5rem;cursor:pointer}input{width:calc(100% - 2rem);padding:.75rem}button[type=submit]{margin-top:1rem;color:#fff;background-image:linear-gradient(to bottom,var(--p9-blue),var(--p9-light-blue))}.slack-invite{position:relative}.custom-icon-button{background:none;border:none;cursor:pointer}#custom-form{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background-color:#fff;padding:20px;box-shadow:0 4px 6px #0000001a;border-radius:8px;z-index:1000}.close-button{position:absolute;top:10px;right:10px;font-size:20px;cursor:pointer}.rate-limit-warning{display:none;color:#dc3545;padding:.5rem;margin-top:.5rem;border:1px solid #dc3545;border-radius:4px}.header:where(.astro-3ef6ksr2){gap:var(--sl-nav-gap);justify-content:space-between;align-items:center;height:100%}.title-wrapper:where(.astro-3ef6ksr2){overflow:clip;padding:.25rem;margin:-.25rem;min-width:0}.right-group:where(.astro-3ef6ksr2),.social-icons:where(.astro-3ef6ksr2){gap:1rem;align-items:center}.social-icons:where(.astro-3ef6ksr2):after{content:"";height:2rem;border-inline-end:1px solid var(--sl-color-gray-5)}.release-dropdown:where(.astro-3ef6ksr2){gap:1rem;align-items:center}.release-dropdown:where(.astro-3ef6ksr2):after{content:"";height:2rem;border-inline-end:1px solid var(--sl-color-gray-5)}@media (min-width: 50rem){:root[data-has-sidebar]{--__sidebar-pad: calc(2 * var(--sl-nav-pad-x))}:root:not([data-has-toc]){--__toc-width: 0rem}.header:where(.astro-3ef6ksr2){--__sidebar-width: max(0rem, var(--sl-content-inline-start, 0rem) - var(--sl-nav-pad-x));--__main-column-fr: calc( ( 100% + var(--__sidebar-pad, 0rem) - var(--__toc-width, var(--sl-sidebar-width)) - (2 * var(--__toc-width, var(--sl-nav-pad-x))) - var(--sl-content-inline-start, 0rem) - var(--sl-content-width) ) / 2 );display:grid;grid-template-columns:minmax(calc(var(--__sidebar-width) + max(0rem,var(--__main-column-fr) - var(--sl-nav-gap))),auto) 1fr auto;align-content:center}}.hero:where(.astro-jbfsktt5){display:grid;align-items:center;gap:1rem;padding-bottom:1rem}.hero:where(.astro-jbfsktt5)>img:where(.astro-jbfsktt5),.hero:where(.astro-jbfsktt5)>.hero-html:where(.astro-jbfsktt5){object-fit:contain;width:min(70%,20rem);height:auto;margin-inline:auto}.stack:where(.astro-jbfsktt5){flex-direction:column;gap:clamp(1.5rem,calc(1.5rem + 1vw),2rem);text-align:center}.copy:where(.astro-jbfsktt5){flex-direction:column;gap:1rem;align-items:center}.copy:where(.astro-jbfsktt5)>:where(.astro-jbfsktt5){max-width:50ch}h1:where(.astro-jbfsktt5){font-size:clamp(var(--sl-text-3xl),calc(.25rem + 5vw),var(--sl-text-6xl));line-height:var(--sl-line-height-headings);font-weight:600;color:var(--sl-color-white)}.tagline:where(.astro-jbfsktt5){font-size:clamp(var(--sl-text-base),calc(.0625rem + 2vw),var(--sl-text-xl));color:var(--sl-color-gray-2)}.actions:where(.astro-jbfsktt5){gap:1rem 2rem;flex-wrap:wrap;justify-content:center}@media (min-width: 50rem){.hero:where(.astro-jbfsktt5){grid-template-columns:7fr 4fr;gap:3%;padding-block:clamp(2.5rem,calc(1rem + 10vmin),10rem)}.hero:where(.astro-jbfsktt5)>img:where(.astro-jbfsktt5),.hero:where(.astro-jbfsktt5)>.hero-html:where(.astro-jbfsktt5){order:2;width:min(100%,25rem)}.stack:where(.astro-jbfsktt5){text-align:start}.copy:where(.astro-jbfsktt5){align-items:flex-start}.actions:where(.astro-jbfsktt5){justify-content:flex-start}}.sl-markdown-content :not(a,strong,em,del,span,input,code,br)+:not(a,strong,em,del,span,input,code,br,:where(.not-content *)){margin-top:1rem}.sl-markdown-content :not(h1,h2,h3,h4,h5,h6)+:is(h1,h2,h3,h4,h5,h6):not(:where(.not-content *)){margin-top:1.5em}.sl-markdown-content li+li:not(:where(.not-content *)),.sl-markdown-content dt+dt:not(:where(.not-content *)),.sl-markdown-content dt+dd:not(:where(.not-content *)),.sl-markdown-content dd+dd:not(:where(.not-content *)){margin-top:.25rem}.sl-markdown-content li:not(:where(.not-content *)){overflow-wrap:anywhere}.sl-markdown-content li>:last-child:not(li,ul,ol,a,strong,em,del,span,input,code,br,:where(.not-content *)){margin-bottom:1.25rem}.sl-markdown-content dt:not(:where(.not-content *)){font-weight:700}.sl-markdown-content dd:not(:where(.not-content *)){padding-inline-start:1rem}.sl-markdown-content :is(h1,h2,h3,h4,h5,h6):not(:where(.not-content *)){color:var(--sl-color-white);line-height:var(--sl-line-height-headings);font-weight:600}.sl-markdown-content :is(img,picture,video,canvas,svg,iframe):not(:where(.not-content *)){display:block;max-width:100%;height:auto}.sl-markdown-content h1:not(:where(.not-content *)){font-size:var(--sl-text-h1)}.sl-markdown-content h2:not(:where(.not-content *)){font-size:var(--sl-text-h2)}.sl-markdown-content h3:not(:where(.not-content *)){font-size:var(--sl-text-h3)}.sl-markdown-content h4:not(:where(.not-content *)){font-size:var(--sl-text-h4)}.sl-markdown-content h5:not(:where(.not-content *)){font-size:var(--sl-text-h5)}.sl-markdown-content h6:not(:where(.not-content *)){font-size:var(--sl-text-h6)}.sl-markdown-content a:not(:where(.not-content *)){color:var(--sl-color-text-accent)}.sl-markdown-content a:hover:not(:where(.not-content *)){color:var(--sl-color-white)}.sl-markdown-content code:not(:where(.not-content *)){background-color:var(--sl-color-bg-inline-code);margin-block:-.125rem;padding:.125rem .375rem;font-size:var(--sl-text-code-sm)}.sl-markdown-content :is(h1,h2,h3,h4,h5,h6) code{font-size:inherit}.sl-markdown-content pre:not(:where(.not-content *)){border:1px solid var(--sl-color-gray-5);padding:.75rem 1rem;font-size:var(--sl-text-code);tab-size:2}.sl-markdown-content pre code:not(:where(.not-content *)){all:unset;font-family:var(--__sl-font-mono)}.sl-markdown-content blockquote:not(:where(.not-content *)){border-inline-start:1px solid var(--sl-color-gray-5);padding-inline-start:1rem}.sl-markdown-content table:not(:where(.not-content *)){display:block;overflow:auto;border-spacing:0}.sl-markdown-content :is(th,td):not(:where(.not-content *)){border-bottom:1px solid var(--sl-color-gray-5);padding:.5rem 1rem;vertical-align:baseline}.sl-markdown-content :is(th:first-child,td:first-child):not(:where(.not-content *)){padding-inline-start:0}.sl-markdown-content :is(th:last-child,td:last-child):not(:where(.not-content *)){padding-inline-end:0}.sl-markdown-content th:not(:where(.not-content *)){color:var(--sl-color-white);font-weight:600}.sl-markdown-content th:not([align]):not(:where(.not-content *)){text-align:start}.sl-markdown-content .starlight-aside :is(th,td,hr,blockquote):not(:where(.not-content *)){border-color:var(--sl-color-gray-4)}@supports (border-color: color-mix(in srgb,var(--sl-color-asides-text-accent) 30%,transparent)){.sl-markdown-content .starlight-aside :is(th,td,hr,blockquote):not(:where(.not-content *)){border-color:color-mix(in srgb,var(--sl-color-asides-text-accent) 30%,transparent)}}@supports (border-color: color-mix(in srgb,var(--sl-color-asides-text-accent) 12%,transparent)){.sl-markdown-content .starlight-aside code:not(:where(.not-content *)){background-color:color-mix(in srgb,var(--sl-color-asides-text-accent) 12%,transparent)}}.sl-markdown-content hr:not(:where(.not-content *)){border:0;border-bottom:1px solid var(--sl-color-hairline)}.sl-markdown-content details:not(:where(.not-content *)){--sl-details-border-color: var(--sl-color-gray-5);--sl-details-border-color--hover: var(--sl-color-text-accent);border-inline-start:2px solid var(--sl-details-border-color);padding-inline-start:1rem}.sl-markdown-content details:not([open]):hover:not(:where(.not-content *)),.sl-markdown-content details:has(>summary:hover):not(:where(.not-content *)){border-color:var(--sl-details-border-color--hover)}.sl-markdown-content summary:not(:where(.not-content *)){color:var(--sl-color-white);cursor:pointer;display:block;font-weight:600;margin-inline-start:-.5rem;padding-inline-start:.5rem}.sl-markdown-content details[open]>summary:not(:where(.not-content *)){margin-bottom:1rem}.sl-markdown-content summary:not(:where(.not-content *))::marker,.sl-markdown-content summary:not(:where(.not-content *))::-webkit-details-marker{display:none}.sl-markdown-content summary:not(:where(.not-content *)):before{--sl-details-marker-size: 1.25rem;background-color:currentColor;content:"";display:inline-block;height:var(--sl-details-marker-size);width:var(--sl-details-marker-size);margin-inline:calc((var(--sl-details-marker-size) / 4) * -1) .25rem;vertical-align:middle;-webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M14.8 11.3 10.6 7a1 1 0 1 0-1.4 1.5l3.5 3.5-3.5 3.5a1 1 0 0 0 0 1.4 1 1 0 0 0 .7.3 1 1 0 0 0 .7-.3l4.2-4.2a1 1 0 0 0 0-1.4Z'/%3E%3C/svg%3E%0A");mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M14.8 11.3 10.6 7a1 1 0 1 0-1.4 1.5l3.5 3.5-3.5 3.5a1 1 0 0 0 0 1.4 1 1 0 0 0 .7.3 1 1 0 0 0 .7-.3l4.2-4.2a1 1 0 0 0 0-1.4Z'/%3E%3C/svg%3E%0A");-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}@media (prefers-reduced-motion: no-preference){.sl-markdown-content summary:not(:where(.not-content *)):before{transition:transform .2s ease-in-out}}.sl-markdown-content details[open]>summary:not(:where(.not-content *)):before{transform:rotate(90deg)}[dir=rtl] .sl-markdown-content summary:not(:where(.not-content *)):before,.sl-markdown-content [dir=rtl] summary:not(:where(.not-content *)):before{transform:rotate(180deg)}.sl-markdown-content summary:not(:where(.not-content *)) p:only-child{display:inline}.sl-markdown-content .starlight-aside details:not(:where(.not-content *)){--sl-details-border-color: var(--sl-color-asides-border);--sl-details-border-color--hover: var(--sl-color-asides-text-accent)}[data-mobile-menu-expanded]{overflow:hidden}@media (min-width: 50rem){[data-mobile-menu-expanded]{overflow:auto}}button:where(.astro-jif73yzw){position:fixed;top:calc((var(--sl-nav-height) - var(--sl-menu-button-size)) / 2);inset-inline-end:var(--sl-nav-pad-x);z-index:var(--sl-z-index-navbar);border:0;border-radius:50%;width:var(--sl-menu-button-size);height:var(--sl-menu-button-size);padding:.5rem;background-color:var(--sl-color-white);color:var(--sl-color-black);box-shadow:var(--sl-shadow-md);cursor:pointer}:where(.astro-jif73yzw)[aria-expanded=true] button:where(.astro-jif73yzw){background-color:var(--sl-color-gray-2);box-shadow:none}[data-theme=light] button:where(.astro-jif73yzw){background-color:var(--sl-color-black);color:var(--sl-color-white)}[data-theme=light] :where(.astro-jif73yzw)[aria-expanded=true] button:where(.astro-jif73yzw){background-color:var(--sl-color-gray-5)}.page:where(.astro-vrdttmbt){flex-direction:column;min-height:100vh}.header:where(.astro-vrdttmbt){z-index:var(--sl-z-index-navbar);position:fixed;inset-inline-start:0;inset-block-start:0;width:100%;height:var(--sl-nav-height);border-bottom:1px solid var(--sl-color-hairline-shade);padding:var(--sl-nav-pad-y) var(--sl-nav-pad-x);padding-inline-end:var(--sl-nav-pad-x);background-color:var(--sl-color-bg-nav)}[data-has-sidebar] .header:where(.astro-vrdttmbt){padding-inline-end:calc(var(--sl-nav-gap) + var(--sl-nav-pad-x) + var(--sl-menu-button-size))}.sidebar-pane:where(.astro-vrdttmbt){visibility:var(--sl-sidebar-visibility, hidden);position:fixed;z-index:var(--sl-z-index-menu);inset-block:var(--sl-nav-height) 0;inset-inline-start:0;width:100%;background-color:var(--sl-color-black);overflow-y:auto}[aria-expanded=true]~.sidebar-pane:where(.astro-vrdttmbt){--sl-sidebar-visibility: visible}.sidebar-content:where(.astro-vrdttmbt){height:100%;min-height:max-content;padding:1rem var(--sl-sidebar-pad-x) 0;flex-direction:column;gap:1rem}@media (min-width: 50rem){.sidebar-content:where(.astro-vrdttmbt):after{content:"";padding-bottom:1px}}.main-frame:where(.astro-vrdttmbt){padding-top:calc(var(--sl-nav-height) + var(--sl-mobile-toc-height));padding-inline-start:var(--sl-content-inline-start)}@media (min-width: 50rem){[data-has-sidebar] .header:where(.astro-vrdttmbt){padding-inline-end:var(--sl-nav-pad-x)}.sidebar-pane:where(.astro-vrdttmbt){--sl-sidebar-visibility: visible;width:var(--sl-sidebar-width);background-color:var(--sl-color-bg-sidebar);border-inline-end:1px solid var(--sl-color-hairline-shade)}}ul:where(.astro-g2bywc46){padding:0;list-style:none}a:where(.astro-g2bywc46){--pad-inline: .5rem;display:block;border-radius:.25rem;padding-block:.25rem;padding-inline:calc(1rem * var(--depth) + var(--pad-inline)) var(--pad-inline);line-height:1.25}a:where(.astro-g2bywc46)[aria-current=true]{color:var(--sl-color-text-accent)}.isMobile:where(.astro-g2bywc46) a:where(.astro-g2bywc46){--pad-inline: 1rem;display:flex;justify-content:space-between;gap:var(--pad-inline);border-top:1px solid var(--sl-color-gray-6);border-radius:0;padding-block:.5rem;color:var(--sl-color-text);font-size:var(--sl-text-sm);text-decoration:none;outline-offset:var(--sl-outline-offset-inside)}.isMobile:where(.astro-g2bywc46):first-child>li:where(.astro-g2bywc46):first-child>a:where(.astro-g2bywc46){border-top:0}.isMobile:where(.astro-g2bywc46) a:where(.astro-g2bywc46)[aria-current=true],.isMobile:where(.astro-g2bywc46) a:where(.astro-g2bywc46)[aria-current=true]:hover,.isMobile:where(.astro-g2bywc46) a:where(.astro-g2bywc46)[aria-current=true]:focus{color:var(--sl-color-white);background-color:unset}.isMobile:where(.astro-g2bywc46) a:where(.astro-g2bywc46)[aria-current=true]:after{content:"";width:1rem;background-color:var(--sl-color-text-accent);-webkit-mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxNCAxNCc+PHBhdGggZD0nTTEwLjkxNCA0LjIwNmEuNTgzLjU4MyAwIDAgMC0uODI4IDBMNS43NCA4LjU1NyAzLjkxNCA2LjcyNmEuNTk2LjU5NiAwIDAgMC0uODI4Ljg1N2wyLjI0IDIuMjRhLjU4My41ODMgMCAwIDAgLjgyOCAwbDQuNzYtNC43NmEuNTgzLjU4MyAwIDAgMCAwLS44NTdaJy8+PC9zdmc+Cg==);mask-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxNCAxNCc+PHBhdGggZD0nTTEwLjkxNCA0LjIwNmEuNTgzLjU4MyAwIDAgMC0uODI4IDBMNS43NCA4LjU1NyAzLjkxNCA2LjcyNmEuNTk2LjU5NiAwIDAgMC0uODI4Ljg1N2wyLjI0IDIuMjRhLjU4My41ODMgMCAwIDAgLjgyOCAwbDQuNzYtNC43NmEuNTgzLjU4MyAwIDAgMCAwLS44NTdaJy8+PC9zdmc+Cg==);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;flex-shrink:0}nav:where(.astro-doynk5tl){position:fixed;z-index:var(--sl-z-index-toc);top:calc(var(--sl-nav-height) - 1px);inset-inline:0;border-top:1px solid var(--sl-color-gray-5);background-color:var(--sl-color-bg-nav)}@media (min-width: 50rem){nav:where(.astro-doynk5tl){inset-inline-start:var(--sl-content-inline-start, 0)}}summary:where(.astro-doynk5tl){gap:.5rem;align-items:center;height:var(--sl-mobile-toc-height);border-bottom:1px solid var(--sl-color-hairline-shade);padding:.5rem 1rem;font-size:var(--sl-text-xs);outline-offset:var(--sl-outline-offset-inside)}summary:where(.astro-doynk5tl)::marker,summary:where(.astro-doynk5tl)::-webkit-details-marker{display:none}.toggle:where(.astro-doynk5tl){flex-shrink:0;gap:1rem;align-items:center;justify-content:space-between;border:1px solid var(--sl-color-gray-5);border-radius:.5rem;padding-block:.5rem;padding-inline-start:.75rem;padding-inline-end:.5rem;line-height:1;background-color:var(--sl-color-black);user-select:none;cursor:pointer}details:where(.astro-doynk5tl)[open] .toggle:where(.astro-doynk5tl){color:var(--sl-color-white);border-color:var(--sl-color-accent)}details:where(.astro-doynk5tl) .toggle:where(.astro-doynk5tl):hover{color:var(--sl-color-white);border-color:var(--sl-color-gray-2)}[dir=rtl] .caret:where(.astro-doynk5tl){transform:rotate(180deg)}details:where(.astro-doynk5tl)[open] .caret:where(.astro-doynk5tl){transform:rotate(90deg)}.display-current:where(.astro-doynk5tl){white-space:nowrap;text-overflow:ellipsis;overflow:hidden;color:var(--sl-color-white)}.dropdown:where(.astro-doynk5tl){--border-top: 1px;margin-top:calc(-1 * var(--border-top));border:var(--border-top) solid var(--sl-color-gray-6);border-top-color:var(--sl-color-hairline-shade);max-height:calc(85vh - var(--sl-nav-height) - var(--sl-mobile-toc-height));overflow-y:auto;background-color:var(--sl-color-black);box-shadow:var(--sl-shadow-md);overscroll-behavior:contain}.right-sidebar-panel:where(.astro-pb3aqygn){padding:1rem var(--sl-sidebar-pad-x)}.sl-container:where(.astro-pb3aqygn){width:calc(var(--sl-sidebar-width) - 2 * var(--sl-sidebar-pad-x))}.right-sidebar-panel:where(.astro-pb3aqygn) h2{color:var(--sl-color-white);font-size:var(--sl-text-h5);font-weight:600;line-height:var(--sl-line-height-headings);margin-bottom:.5rem}.right-sidebar-panel:where(.astro-pb3aqygn) :where(a){display:block;font-size:var(--sl-text-xs);text-decoration:none;color:var(--sl-color-gray-3);overflow-wrap:anywhere}.right-sidebar-panel:where(.astro-pb3aqygn) :where(a):hover{color:var(--sl-color-white)}@media (min-width: 72rem){.sl-container:where(.astro-pb3aqygn){max-width:calc(((100vw - var(--sl-sidebar-width) - 2 * var(--sl-content-pad-x) - 2 * var(--sl-sidebar-pad-x)) * .25))}}h1:where(.astro-j6tvhyss){margin-top:1rem;font-size:var(--sl-text-h1);line-height:var(--sl-line-height-headings);font-weight:600;color:var(--sl-color-white)}.social-icons:where(.astro-wu23bvmt){margin-inline-end:auto;gap:1rem;align-items:center;padding-block:1rem}.social-icons:where(.astro-wu23bvmt):empty{display:none}.mobile-preferences:where(.astro-wu23bvmt){justify-content:space-between;flex-wrap:wrap;border-top:1px solid var(--sl-color-gray-6);column-gap:1rem;padding:.5rem 0}sl-sidebar-state-persist:where(.astro-kku4brbg){display:contents}ul:where(.astro-3ii7xxms){--sl-sidebar-item-padding-inline: .5rem;list-style:none;padding:0}li:where(.astro-3ii7xxms){overflow-wrap:anywhere}ul:where(.astro-3ii7xxms) ul:where(.astro-3ii7xxms) li:where(.astro-3ii7xxms){margin-inline-start:var(--sl-sidebar-item-padding-inline);border-inline-start:1px solid var(--sl-color-hairline-light);padding-inline-start:var(--sl-sidebar-item-padding-inline)}.large:where(.astro-3ii7xxms){font-size:var(--sl-text-lg);font-weight:600;color:var(--sl-color-white)}.top-level:where(.astro-3ii7xxms)>li:where(.astro-3ii7xxms)+li:where(.astro-3ii7xxms){margin-top:.75rem}summary:where(.astro-3ii7xxms){display:flex;align-items:center;justify-content:space-between;padding:.2em var(--sl-sidebar-item-padding-inline);line-height:1.4;cursor:pointer;user-select:none}summary:where(.astro-3ii7xxms)::marker,summary:where(.astro-3ii7xxms)::-webkit-details-marker{display:none}.caret:where(.astro-3ii7xxms){transition:transform .2s ease-in-out;flex-shrink:0}[dir=rtl] .caret:where(.astro-3ii7xxms){transform:rotate(180deg)}:where(.astro-3ii7xxms)[open]>summary:where(.astro-3ii7xxms) .caret:where(.astro-3ii7xxms){transform:rotate(90deg)}a:where(.astro-3ii7xxms){display:block;border-radius:.25rem;text-decoration:none;color:var(--sl-color-gray-2);padding:.3em var(--sl-sidebar-item-padding-inline);line-height:1.4}a:where(.astro-3ii7xxms):hover,a:where(.astro-3ii7xxms):focus{color:var(--sl-color-white)}:where(.astro-3ii7xxms)[aria-current=page],:where(.astro-3ii7xxms)[aria-current=page]:hover,:where(.astro-3ii7xxms)[aria-current=page]:focus{font-weight:600;color:var(--sl-color-text-invert);background-color:var(--sl-color-text-accent)}a:where(.astro-3ii7xxms)>:where(.astro-3ii7xxms):not(:last-child),.group-label:where(.astro-3ii7xxms)>:where(.astro-3ii7xxms):not(:last-child){margin-inline-end:.25em}@media (min-width: 50rem){.top-level:where(.astro-3ii7xxms)>li:where(.astro-3ii7xxms)+li:where(.astro-3ii7xxms){margin-top:.5rem}.large:where(.astro-3ii7xxms){font-size:var(--sl-text-base)}a:where(.astro-3ii7xxms){font-size:var(--sl-text-sm)}}a:where(.astro-7q3lir66){clip:rect(0,0,0,0);position:fixed;top:.75rem;inset-inline-start:.75rem}a:where(.astro-7q3lir66):focus{clip:unset;z-index:var(--sl-z-index-skiplink);display:block;padding:.5rem 1rem;text-decoration:none;color:var(--sl-color-text-invert);background-color:var(--sl-color-text-accent);box-shadow:var(--sl-shadow-lg)}.main-pane:where(.astro-67yu43on){isolation:isolate}@media (min-width: 72rem){.right-sidebar-container:where(.astro-67yu43on){order:2;position:relative;width:calc(var(--sl-sidebar-width) + (100% - var(--sl-content-width) - var(--sl-sidebar-width)) / 2)}.right-sidebar:where(.astro-67yu43on){position:fixed;top:0;border-inline-start:1px solid var(--sl-color-hairline);padding-top:var(--sl-nav-height);width:100%;height:100vh;overflow-y:auto;scrollbar-width:none}.main-pane:where(.astro-67yu43on){width:100%}[data-has-sidebar][data-has-toc] .main-pane:where(.astro-67yu43on){--sl-content-margin-inline: auto 0;order:1;width:calc(var(--sl-content-width) + (100% - var(--sl-content-width) - var(--sl-sidebar-width)) / 2)}}.starlight-aside{padding:1rem;border-inline-start:.25rem solid var(--sl-color-asides-border);color:var(--sl-color-white)}.starlight-aside--note{--sl-color-asides-text-accent: var(--sl-color-blue-high);--sl-color-asides-border: var(--sl-color-blue);background-color:var(--sl-color-blue-low)}.starlight-aside--tip{--sl-color-asides-text-accent: var(--sl-color-purple-high);--sl-color-asides-border: var(--sl-color-purple);background-color:var(--sl-color-purple-low)}.starlight-aside--caution{--sl-color-asides-text-accent: var(--sl-color-orange-high);--sl-color-asides-border: var(--sl-color-orange);background-color:var(--sl-color-orange-low)}.starlight-aside--danger{--sl-color-asides-text-accent: var(--sl-color-red-high);--sl-color-asides-border: var(--sl-color-red);background-color:var(--sl-color-red-low)}.starlight-aside__title{display:flex;gap:.5rem;align-items:center;font-size:var(--sl-text-h5);font-weight:600;line-height:var(--sl-line-height-headings);color:var(--sl-color-asides-text-accent)}.starlight-aside__icon{font-size:1.333em;width:1em;height:1em}.starlight-aside__title+.starlight-aside__content{margin-top:.5rem}.starlight-aside__content a{color:var(--sl-color-asides-text-accent)}html:not([data-has-toc]){--sl-mobile-toc-height: 0rem}html:not([data-has-sidebar]){--sl-content-width: 67.5rem}html{scroll-padding-top:calc(1.5rem + var(--sl-nav-height) + var(--sl-mobile-toc-height))}main:where(.astro-bguv2lll){padding-bottom:3vh}@media (min-width: 50em){:where(.astro-bguv2lll)[data-has-sidebar]{--sl-content-inline-start: var(--sl-sidebar-width)}}@media (min-width: 72em){html{scroll-padding-top:calc(1.5rem + var(--sl-nav-height))}} diff --git a/docs/_astro/page.7qqag-5g.js b/docs/_astro/page.7qqag-5g.js new file mode 100644 index 0000000..12aa5b5 --- /dev/null +++ b/docs/_astro/page.7qqag-5g.js @@ -0,0 +1 @@ +const d=new Set,c=new WeakSet;let f=!0,h,l=!1;function v(e){l||(l=!0,f??=!1,h??="hover",g(),p(),w(),L())}function g(){for(const e of["touchstart","mousedown"])document.body.addEventListener(e,t=>{i(t.target,"tap")&&s(t.target.href,{ignoreSlowConnection:!0})},{passive:!0})}function p(){let e;document.body.addEventListener("focusin",n=>{i(n.target,"hover")&&t(n)},{passive:!0}),document.body.addEventListener("focusout",o,{passive:!0}),u(()=>{for(const n of document.getElementsByTagName("a"))c.has(n)||i(n,"hover")&&(c.add(n),n.addEventListener("mouseenter",t,{passive:!0}),n.addEventListener("mouseleave",o,{passive:!0}))});function t(n){const r=n.target.href;e&&clearTimeout(e),e=setTimeout(()=>{s(r)},80)}function o(){e&&(clearTimeout(e),e=0)}}function w(){let e;u(()=>{for(const t of document.getElementsByTagName("a"))c.has(t)||i(t,"viewport")&&(c.add(t),e??=y(),e.observe(t))})}function y(){const e=new WeakMap;return new IntersectionObserver((t,o)=>{for(const n of t){const r=n.target,a=e.get(r);n.isIntersecting?(a&&clearTimeout(a),e.set(r,setTimeout(()=>{o.unobserve(r),e.delete(r),s(r.href)},300))):a&&(clearTimeout(a),e.delete(r))}})}function L(){u(()=>{for(const e of document.getElementsByTagName("a"))i(e,"load")&&s(e.href)})}function s(e,t){e=e.replace(/#.*/,"");const o=t?.ignoreSlowConnection??!1;if(S(e,o))if(d.add(e),document.createElement("link").relList?.supports?.("prefetch")&&t?.with!=="fetch"){const n=document.createElement("link");n.rel="prefetch",n.setAttribute("href",e),document.head.append(n)}else fetch(e,{priority:"low"})}function S(e,t){if(!navigator.onLine||!t&&m())return!1;try{const o=new URL(e,location.href);return location.origin===o.origin&&(location.pathname!==o.pathname||location.search!==o.search)&&!d.has(e)}catch{}return!1}function i(e,t){if(e?.tagName!=="A")return!1;const o=e.dataset.astroPrefetch;return o==="false"?!1:t==="tap"&&(o!=null||f)&&m()?!0:o==null&&f||o===""?t===h:o===t}function m(){if("connection"in navigator){const e=navigator.connection;return e.saveData||/2g/.test(e.effectiveType)}return!1}function u(e){e();let t=!1;document.addEventListener("astro:page-load",()=>{if(!t){t=!0;return}e()})}v(); diff --git a/docs/_astro/platform9-logo.PaszzYBQ.svg b/docs/_astro/platform9-logo.PaszzYBQ.svg new file mode 100644 index 0000000..eb8a08a --- /dev/null +++ b/docs/_astro/platform9-logo.PaszzYBQ.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/_astro/print.BJ0teN4y.css b/docs/_astro/print.BJ0teN4y.css new file mode 100644 index 0000000..f14ca33 --- /dev/null +++ b/docs/_astro/print.BJ0teN4y.css @@ -0,0 +1 @@ +@media print{:root[data-theme]:is(:root){--sl-color-white: hsl(224, 10%, 10%);--sl-color-gray-1: hsl(224, 14%, 16%);--sl-color-gray-2: hsl(224, 10%, 23%);--sl-color-gray-3: hsl(224, 7%, 36%);--sl-color-gray-4: hsl(224, 6%, 56%);--sl-color-gray-5: hsl(224, 6%, 77%);--sl-color-gray-6: hsl(224, 20%, 94%);--sl-color-gray-7: hsl(224, 19%, 97%);--sl-color-black: hsl(0, 0%, 100%);--sl-color-orange-high: hsl(var(--sl-hue-orange), 80%, 25%);--sl-color-orange: hsl(var(--sl-hue-orange), 90%, 60%);--sl-color-orange-low: hsl(var(--sl-hue-orange), 90%, 88%);--sl-color-green-high: hsl(var(--sl-hue-green), 80%, 22%);--sl-color-green: hsl(var(--sl-hue-green), 90%, 46%);--sl-color-green-low: hsl(var(--sl-hue-green), 85%, 90%);--sl-color-blue-high: hsl(var(--sl-hue-blue), 80%, 30%);--sl-color-blue: hsl(var(--sl-hue-blue), 90%, 60%);--sl-color-blue-low: hsl(var(--sl-hue-blue), 88%, 90%);--sl-color-purple-high: hsl(var(--sl-hue-purple), 90%, 30%);--sl-color-purple: hsl(var(--sl-hue-purple), 90%, 60%);--sl-color-purple-low: hsl(var(--sl-hue-purple), 80%, 90%);--sl-color-red-high: hsl(var(--sl-hue-red), 80%, 30%);--sl-color-red: hsl(var(--sl-hue-red), 90%, 60%);--sl-color-red-low: hsl(var(--sl-hue-red), 80%, 90%);--sl-color-accent-high: hsl(234, 80%, 30%);--sl-color-accent: hsl(234, 90%, 60%);--sl-color-accent-low: hsl(234, 88%, 90%);--sl-color-text-accent: var(--sl-color-accent);--sl-color-text-invert: var(--sl-color-black);--sl-color-bg-nav: var(--sl-color-gray-7);--sl-color-bg-sidebar: var(--sl-color-bg);--sl-color-bg-inline-code: var(--sl-color-gray-6);--sl-color-bg-accent: var(--sl-color-accent);--sl-color-hairline-light: var(--sl-color-gray-6);--sl-color-hairline-shade: var(--sl-color-gray-6);--sl-color-backdrop-overlay: hsla(225, 9%, 36%, .66);--sl-shadow-sm: none;--sl-shadow-md: none;--sl-shadow-lg: none}.print\:hidden{display:none!important}.print\:flex{display:flex!important}.print\:block{display:block!important}main{padding-bottom:0!important}main>.content-panel{padding-block-start:0!important}.content-panel+.content-panel{border:0!important}.page>header{position:relative!important}.page>.main-frame{padding-top:0;padding-inline-start:0}.main-pane{--sl-sidebar-width: 0px !important;--sl-content-width: 100% !important}.sl-banner{--sl-color-banner-text: var(--sl-color-white) !important;background-color:transparent!important}.sl-markdown-content :is(h1,h2,h3,h4,h5,h6){break-after:avoid}.sl-markdown-content :is(p,li){orphans:2;widows:2}.sl-markdown-content pre{overflow-x:hidden!important;white-space:pre-wrap!important}.sl-markdown-content .expressive-code,.sl-markdown-content figure,.sl-markdown-content pre{break-inside:avoid}.expressive-code .frame.is-terminal .header:before{box-shadow:inset 99rem 99rem var(--sl-color-gray-5)}.expressive-code .frame.has-title:not(.is-terminal) .header{background:transparent!important;border-bottom:1px solid var(--sl-color-gray-6)!important}.expressive-code .frame.has-title:not(.is-terminal) .title{background:transparent!important}.expressive-code .frame.has-title:not(.is-terminal) .title:after{border-top:0!important}.expressive-code .copy{display:none!important}.sl-markdown-content code:not(:where(.not-content *)){background-color:transparent!important;padding:0!important;margin-block:unset!important;font-size:.9375em!important}.sl-badge{background:transparent!important;color:var(--sl-color-white)!important}starlight-file-tree{break-inside:avoid}starlight-file-tree .highlight{outline:3px solid var(--sl-color-accent-low);color:var(--sl-color-text)!important;background-color:transparent!important}.starlight-aside{break-inside:avoid}.sl-link-button.primary{background:transparent!important;border-color:var(--sl-color-white)!important;color:var(--sl-color-white)!important}starlight-tabs{break-inside:avoid}.sl-steps>li:after{box-shadow:inset 99rem 99rem var(--sl-color-hairline-light)}} diff --git a/docs/_astro/types.d.ts b/docs/_astro/types.d.ts new file mode 100644 index 0000000..03d7cc4 --- /dev/null +++ b/docs/_astro/types.d.ts @@ -0,0 +1,2 @@ +/// +/// \ No newline at end of file diff --git a/docs/_astro/ui-core.DklwHuoF.js b/docs/_astro/ui-core.DklwHuoF.js new file mode 100644 index 0000000..e82cbd4 --- /dev/null +++ b/docs/_astro/ui-core.DklwHuoF.js @@ -0,0 +1,2 @@ +import{_ as is}from"./Search.astro_astro_type_script_index_0_lang.18-vQxR-.js";var os=Object.defineProperty,F=(e,u)=>{for(var t in u)os(e,t,{get:u[t],enumerable:!0})};function P(){}function hu(e){return e()}function ye(){return Object.create(null)}function W(e){e.forEach(hu)}function Cu(e){return typeof e=="function"}function fe(e,u){return e!=e?u==u:e!==u||e&&typeof e=="object"||typeof e=="function"}var oe;function ce(e,u){return oe||(oe=document.createElement("a")),oe.href=u,e===oe.href}function _s(e){return Object.keys(e).length===0}function B(e,u){e.appendChild(u)}function T(e,u,t){e.insertBefore(u,t||null)}function p(e){e.parentNode&&e.parentNode.removeChild(e)}function ie(e,u){for(let t=0;te.removeEventListener(u,t,r)}function g(e,u,t){t==null?e.removeAttribute(u):e.getAttribute(u)!==t&&e.setAttribute(u,t)}function fs(e){return Array.from(e.childNodes)}function j(e,u){u=""+u,e.data!==u&&(e.data=u)}function ze(e,u){e.value=u??""}function K(e,u,t){e.classList[t?"add":"remove"](u)}var Es=class{constructor(e=!1){this.is_svg=!1,this.is_svg=e,this.e=this.n=null}c(e){this.h(e)}m(e,u,t=null){this.e||(this.is_svg?this.e=cs(u.nodeName):this.e=v(u.nodeType===11?"TEMPLATE":u.nodeName),this.t=u.tagName!=="TEMPLATE"?u:u.content,this.c(e)),this.i(t)}h(e){this.e.innerHTML=e,this.n=Array.from(this.e.nodeName==="TEMPLATE"?this.e.content.childNodes:this.e.childNodes)}i(e){for(let u=0;ue.indexOf(r)===-1?u.push(r):t.push(r)),t.forEach(r=>r()),$=u}var _e=new Set,J;function le(){J={r:0,c:[],p:J}}function ne(){J.r||W(J.c),J=J.p}function z(e,u){e&&e.i&&(_e.delete(e),e.i(u))}function U(e,u,t,r){if(e&&e.o){if(_e.has(e))return;_e.add(e),J.c.push(()=>{_e.delete(e),r&&(t&&e.d(1),r())}),e.o(u)}else r&&r()}function Bs(e,u){U(e,1,1,()=>{u.delete(e.key)})}function vs(e,u,t,r,s,a,l,n,i,f,o,h){let C=e.length,E=a.length,c=C;const _={};for(;c--;)_[e[c].key]=c;const d=[],R=new Map,A=new Map,k=[];for(c=E;c--;){const D=h(s,a,c),S=t(D);let w=l.get(S);w?k.push(()=>w.p(D,u)):(w=f(S,D),w.c()),R.set(S,d[c]=w),S in _&&A.set(S,Math.abs(c-_[S]))}const y=new Set,q=new Set;function L(D){z(D,1),D.m(n,o),l.set(D.key,D),o=D.first,E--}for(;C&&E;){const D=d[E-1],S=e[C-1],w=D.key,x=S.key;D===S?(o=D.first,C--,E--):R.has(x)?!l.has(w)||y.has(w)?L(D):q.has(x)?C--:A.get(w)>A.get(x)?(q.add(w),L(D)):(y.add(x),C--):(i(S,l),C--)}for(;C--;){const D=e[C];R.has(D.key)||i(D,l)}for(;E;)L(d[E-1]);return W(k),d}function ps(e,u,t){const r=e.$$.props[u];r!==void 0&&(e.$$.bound[r]=t,t(e.$$.ctx[r]))}function ke(e){e&&e.c()}function Ee(e,u,t,r){const{fragment:s,after_update:a}=e.$$;s&&s.m(u,t),r||Te(()=>{const l=e.$$.on_mount.map(hu).filter(Cu);e.$$.on_destroy?e.$$.on_destroy.push(...l):W(l),e.$$.on_mount=[]}),a.forEach(Te)}function de(e,u){const t=e.$$;t.fragment!==null&&(Rs(t.after_update),W(t.on_destroy),t.fragment&&t.fragment.d(u),t.on_destroy=t.fragment=null,t.ctx=[])}function Ts(e,u){e.$$.dirty[0]===-1&&(X.push(e),ms(),e.$$.dirty.fill(0)),e.$$.dirty[u/31|0]|=1<{const c=E.length?E[0]:C;return f.ctx&&s(f.ctx[h],f.ctx[h]=c)&&(!f.skip_bound&&f.bound[h]&&f.bound[h](c),o&&Ts(e,h)),C}):[],f.update(),o=!0,W(f.before_update),f.fragment=r?r(f.ctx):!1,u.target){if(u.hydrate){const h=fs(u.target);f.fragment&&f.fragment.l(h),h.forEach(p)}else f.fragment&&f.fragment.c();u.intro&&z(e.$$.fragment),Ee(e,u.target,u.anchor,u.customElement),gu()}re(i)}var Ce=class{$destroy(){de(this,1),this.$destroy=P}$on(e,u){if(!Cu(u))return P;const t=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return t.push(u),()=>{const r=t.indexOf(u);r!==-1&&t.splice(r,1)}}$set(e){this.$$set&&!_s(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}};function O(e){const u=typeof e=="string"?e.charCodeAt(0):e;return u>=97&&u<=122||u>=65&&u<=90}function Q(e){const u=typeof e=="string"?e.charCodeAt(0):e;return u>=48&&u<=57}function V(e){return O(e)||Q(e)}var Fs=["art-lojban","cel-gaulish","no-bok","no-nyn","zh-guoyu","zh-hakka","zh-min","zh-min-nan","zh-xiang"],je={"en-gb-oed":"en-GB-oxendict","i-ami":"ami","i-bnn":"bnn","i-default":null,"i-enochian":null,"i-hak":"hak","i-klingon":"tlh","i-lux":"lb","i-mingo":null,"i-navajo":"nv","i-pwn":"pwn","i-tao":"tao","i-tay":"tay","i-tsu":"tsu","sgn-be-fr":"sfb","sgn-be-nl":"vgt","sgn-ch-de":"sgg","art-lojban":"jbo","cel-gaulish":null,"no-bok":"nb","no-nyn":"nn","zh-guoyu":"cmn","zh-hakka":"hak","zh-min":null,"zh-min-nan":"nan","zh-xiang":"hsn"},ks={}.hasOwnProperty;function Au(e,u={}){const t=Oe(),r=String(e),s=r.toLowerCase();let a=0;if(e==null)throw new Error("Expected string, got `"+e+"`");if(ks.call(je,s)){const n=je[s];return(u.normalize===void 0||u.normalize===null||u.normalize)&&typeof n=="string"?Au(n):(t[Fs.includes(s)?"regular":"irregular"]=r,t)}for(;O(s.charCodeAt(a))&&a<9;)a++;if(a>1&&a<9){if(t.language=r.slice(0,a),a<4){let n=0;for(;s.charCodeAt(a)===45&&O(s.charCodeAt(a+1))&&O(s.charCodeAt(a+2))&&O(s.charCodeAt(a+3))&&!O(s.charCodeAt(a+4));){if(n>2)return l(a,3,"Too many extended language subtags, expected at most 3 subtags");t.extendedLanguageSubtags.push(r.slice(a+1,a+4)),a+=4,n++}}for(s.charCodeAt(a)===45&&O(s.charCodeAt(a+1))&&O(s.charCodeAt(a+2))&&O(s.charCodeAt(a+3))&&O(s.charCodeAt(a+4))&&!O(s.charCodeAt(a+5))&&(t.script=r.slice(a+1,a+5),a+=5),s.charCodeAt(a)===45&&(O(s.charCodeAt(a+1))&&O(s.charCodeAt(a+2))&&!O(s.charCodeAt(a+3))?(t.region=r.slice(a+1,a+3),a+=3):Q(s.charCodeAt(a+1))&&Q(s.charCodeAt(a+2))&&Q(s.charCodeAt(a+3))&&!Q(s.charCodeAt(a+4))&&(t.region=r.slice(a+1,a+4),a+=4));s.charCodeAt(a)===45;){const n=a+1;let i=n;for(;V(s.charCodeAt(i));){if(i-n>7)return l(i,1,"Too long variant, expected at most 8 characters");i++}if(i-n>4||i-n>3&&Q(s.charCodeAt(n)))t.variants.push(r.slice(n,i)),a=i;else break}for(;s.charCodeAt(a)===45&&!(s.charCodeAt(a+1)===120||!V(s.charCodeAt(a+1))||s.charCodeAt(a+2)!==45||!V(s.charCodeAt(a+3)));){let n=a+2,i=0;for(;s.charCodeAt(n)===45&&V(s.charCodeAt(n+1))&&V(s.charCodeAt(n+2));){const f=n+1;for(n=f+2,i++;V(s.charCodeAt(n));){if(n-f>7)return l(n,2,"Too long extension, expected at most 8 characters");n++}}if(!i)return l(n,4,"Empty extension, extensions must have at least 2 characters of content");t.extensions.push({singleton:r.charAt(a+1),extensions:r.slice(a+3,n).split("-")}),a=n}}else a=0;if(a===0&&s.charCodeAt(a)===120||s.charCodeAt(a)===45&&s.charCodeAt(a+1)===120){a=a?a+2:1;let n=a;for(;s.charCodeAt(n)===45&&V(s.charCodeAt(n+1));){const i=a+1;for(n=i;V(s.charCodeAt(n));){if(n-i>7)return l(n,5,"Too long private-use area, expected at most 8 characters");n++}t.privateuse.push(r.slice(a+1,n)),a=n}}if(a!==r.length)return l(a,6,"Found superfluous content after tag");return t;function l(n,i,f){return u.warning&&u.warning(f,i,n),u.forgiving?t:Oe()}}function Oe(){return{language:null,extendedLanguageSubtags:[],script:null,region:null,variants:[],extensions:[],privateuse:[],irregular:null,regular:null}}function Ue(e,u,t){const r=e.slice();return r[8]=u[t][0],r[9]=u[t][1],r}function Ds(e){let u,t,r,s,a,l=e[0]&&Ie();return{c(){l&&l.c(),u=b(),t=v("div"),r=v("p"),r.textContent=`${e[3](30)}`,s=b(),a=v("p"),a.textContent=`${e[3](40)}`,g(r,"class","pagefind-ui__result-title pagefind-ui__loading svelte-j9e30"),g(a,"class","pagefind-ui__result-excerpt pagefind-ui__loading svelte-j9e30"),g(t,"class","pagefind-ui__result-inner svelte-j9e30")},m(n,i){l&&l.m(n,i),T(n,u,i),T(n,t,i),B(t,r),B(t,s),B(t,a)},p(n,i){n[0]?l||(l=Ie(),l.c(),l.m(u.parentNode,u)):l&&(l.d(1),l=null)},d(n){l&&l.d(n),n&&p(u),n&&p(t)}}}function bs(e){let u,t,r,s,a=e[1].meta?.title+"",l,n,i,f,o=e[1].excerpt+"",h,C=e[0]&&Pe(e),E=e[2].length&&xe(e);return{c(){C&&C.c(),u=b(),t=v("div"),r=v("p"),s=v("a"),l=M(a),i=b(),f=v("p"),h=b(),E&&E.c(),g(s,"class","pagefind-ui__result-link svelte-j9e30"),g(s,"href",n=e[1].meta?.url||e[1].url),g(r,"class","pagefind-ui__result-title svelte-j9e30"),g(f,"class","pagefind-ui__result-excerpt svelte-j9e30"),g(t,"class","pagefind-ui__result-inner svelte-j9e30")},m(c,_){C&&C.m(c,_),T(c,u,_),T(c,t,_),B(t,r),B(r,s),B(s,l),B(t,i),B(t,f),f.innerHTML=o,B(t,h),E&&E.m(t,null)},p(c,_){c[0]?C?C.p(c,_):(C=Pe(c),C.c(),C.m(u.parentNode,u)):C&&(C.d(1),C=null),_&2&&a!==(a=c[1].meta?.title+"")&&j(l,a),_&2&&n!==(n=c[1].meta?.url||c[1].url)&&g(s,"href",n),_&2&&o!==(o=c[1].excerpt+"")&&(f.innerHTML=o),c[2].length?E?E.p(c,_):(E=xe(c),E.c(),E.m(t,null)):E&&(E.d(1),E=null)},d(c){C&&C.d(c),c&&p(u),c&&p(t),E&&E.d()}}}function Ie(e){let u;return{c(){u=v("div"),g(u,"class","pagefind-ui__result-thumb pagefind-ui__loading svelte-j9e30")},m(t,r){T(t,u,r)},d(t){t&&p(u)}}}function Pe(e){let u,t=e[1].meta.image&&Le(e);return{c(){u=v("div"),t&&t.c(),g(u,"class","pagefind-ui__result-thumb svelte-j9e30")},m(r,s){T(r,u,s),t&&t.m(u,null)},p(r,s){r[1].meta.image?t?t.p(r,s):(t=Le(r),t.c(),t.m(u,null)):t&&(t.d(1),t=null)},d(r){r&&p(u),t&&t.d()}}}function Le(e){let u,t,r;return{c(){u=v("img"),g(u,"class","pagefind-ui__result-image svelte-j9e30"),ce(u.src,t=e[1].meta?.image)||g(u,"src",t),g(u,"alt",r=e[1].meta?.image_alt||e[1].meta?.title)},m(s,a){T(s,u,a)},p(s,a){a&2&&!ce(u.src,t=s[1].meta?.image)&&g(u,"src",t),a&2&&r!==(r=s[1].meta?.image_alt||s[1].meta?.title)&&g(u,"alt",r)},d(s){s&&p(u)}}}function xe(e){let u,t=e[2],r=[];for(let s=0;se.toLocaleUpperCase();function Ss(e,u,t){let{show_images:r=!0}=u,{process_result:s=null}=u,{result:a={data:async()=>{}}}=u;const l=["title","image","image_alt","url"];let n,i=[];const f=async h=>{t(1,n=await h.data()),t(1,n=s?.(n)??n),t(2,i=Object.entries(n.meta).filter(([C])=>!l.includes(C)))},o=(h=30)=>". ".repeat(Math.floor(10+Math.random()*h));return e.$$set=h=>{"show_images"in h&&t(0,r=h.show_images),"process_result"in h&&t(4,s=h.process_result),"result"in h&&t(5,a=h.result)},e.$$.update=()=>{e.$$.dirty&32&&f(a)},[r,n,i,o,s,a]}var Hs=class extends Ce{constructor(e){super(),he(this,e,Ss,Ms,fe,{show_images:0,process_result:4,result:5})}},ws=Hs;function Ke(e,u,t){const r=e.slice();return r[11]=u[t][0],r[12]=u[t][1],r}function Ge(e,u,t){const r=e.slice();return r[15]=u[t],r}function Ns(e){let u,t,r,s,a,l=e[0]&&Je();return{c(){l&&l.c(),u=b(),t=v("div"),r=v("p"),r.textContent=`${e[5](30)}`,s=b(),a=v("p"),a.textContent=`${e[5](40)}`,g(r,"class","pagefind-ui__result-title pagefind-ui__loading svelte-4xnkmf"),g(a,"class","pagefind-ui__result-excerpt pagefind-ui__loading svelte-4xnkmf"),g(t,"class","pagefind-ui__result-inner svelte-4xnkmf")},m(n,i){l&&l.m(n,i),T(n,u,i),T(n,t,i),B(t,r),B(t,s),B(t,a)},p(n,i){n[0]?l||(l=Je(),l.c(),l.m(u.parentNode,u)):l&&(l.d(1),l=null)},d(n){l&&l.d(n),n&&p(u),n&&p(t)}}}function ys(e){let u,t,r,s,a=e[1].meta?.title+"",l,n,i,f,o,h=e[0]&&We(e),C=e[4]&&Ye(e),E=e[3],c=[];for(let d=0;de.toLocaleUpperCase();function js(e,u,t){let{show_images:r=!0}=u,{process_result:s=null}=u,{result:a={data:async()=>{}}}=u;const l=["title","image","image_alt","url"];let n,i=[],f=[],o=!1;const h=(c,_)=>{if(c.length<=_)return c;const d=[...c].sort((R,A)=>A.locations.length-R.locations.length).slice(0,3).map(R=>R.url);return c.filter(R=>d.includes(R.url))},C=async c=>{t(1,n=await c.data()),t(1,n=s?.(n)??n),t(2,i=Object.entries(n.meta).filter(([_])=>!l.includes(_))),Array.isArray(n.sub_results)&&(t(4,o=n.sub_results?.[0]?.url===(n.meta?.url||n.url)),o?t(3,f=h(n.sub_results.slice(1),3)):t(3,f=h([...n.sub_results],3)))},E=(c=30)=>". ".repeat(Math.floor(10+Math.random()*c));return e.$$set=c=>{"show_images"in c&&t(0,r=c.show_images),"process_result"in c&&t(6,s=c.process_result),"result"in c&&t(7,a=c.result)},e.$$.update=()=>{e.$$.dirty&128&&C(a)},[r,n,i,f,o,E,s,a]}var Os=class extends Ce{constructor(e){super(),he(this,e,js,zs,fe,{show_images:0,process_result:6,result:7})}},Us=Os;function uu(e,u,t){const r=e.slice();return r[10]=u[t][0],r[11]=u[t][1],r[12]=u,r[13]=t,r}function tu(e,u,t){const r=e.slice();return r[14]=u[t][0],r[15]=u[t][1],r[16]=u,r[17]=t,r}function ru(e){let u,t,r=e[4]("filters_label",e[5],e[6])+"",s,a,l=Object.entries(e[1]),n=[];for(let i=0;ie.toLocaleUpperCase(),iu=e=>e.toLowerCase();function Ps(e,u,t){let{available_filters:r=null}=u,{show_empty_filters:s=!0}=u,{open_filters:a=[]}=u,{translate:l=()=>""}=u,{automatic_translations:n={}}=u,{translations:i={}}=u,{selected_filters:f={}}=u,o=!1,h=!1;function C(E,c){f[`${E}:${c}`]=this.checked,t(0,f)}return e.$$set=E=>{"available_filters"in E&&t(1,r=E.available_filters),"show_empty_filters"in E&&t(2,s=E.show_empty_filters),"open_filters"in E&&t(3,a=E.open_filters),"translate"in E&&t(4,l=E.translate),"automatic_translations"in E&&t(5,n=E.automatic_translations),"translations"in E&&t(6,i=E.translations),"selected_filters"in E&&t(0,f=E.selected_filters)},e.$$.update=()=>{if(e.$$.dirty&258&&r&&!o){t(8,o=!0);let E=Object.entries(r||{});E.length===1&&Object.entries(E[0][1])?.length<=6&&t(7,h=!0)}},[f,r,s,a,l,n,i,h,o,C]}var Ls=class extends Ce{constructor(e){super(),he(this,e,Ps,Is,fe,{available_filters:1,show_empty_filters:2,open_filters:3,translate:4,automatic_translations:5,translations:6,selected_filters:0})}},xs=Ls,Ru={};F(Ru,{comments:()=>vu,default:()=>qs,direction:()=>pu,strings:()=>Tu,thanks_to:()=>Bu});var Bu="Jan Claasen ",vu="",pu="ltr",Tu={placeholder:"Soek",clear_search:"Opruim",load_more:"Laai nog resultate",search_label:"Soek hierdie webwerf",filters_label:"Filters",zero_results:"Geen resultate vir [SEARCH_TERM]",many_results:"[COUNT] resultate vir [SEARCH_TERM]",one_result:"[COUNT] resultate vir [SEARCH_TERM]",alt_search:"Geen resultate vir [SEARCH_TERM]. Toon resultate vir [DIFFERENT_TERM] in plaas daarvan",search_suggestion:"Geen resultate vir [SEARCH_TERM]. Probeer eerder een van die volgende terme:",searching:"Soek vir [SEARCH_TERM]"},qs={thanks_to:Bu,comments:vu,direction:pu,strings:Tu},Fu={};F(Fu,{comments:()=>Du,default:()=>Vs,direction:()=>bu,strings:()=>Mu,thanks_to:()=>ku});var ku="Jermanuts",Du="",bu="rtl",Mu={placeholder:"بحث",clear_search:"امسح",load_more:"حمِّل المزيد من النتائج",search_label:"ابحث في هذا الموقع",filters_label:"تصفيات",zero_results:"لا توجد نتائج ل [SEARCH_TERM]",many_results:"[COUNT] نتائج ل [SEARCH_TERM]",one_result:"[COUNT] نتيجة ل [SEARCH_TERM]",alt_search:"لا توجد نتائج ل [SEARCH_TERM]. يعرض النتائج ل [DIFFERENT_TERM] بدلاً من ذلك",search_suggestion:"لا توجد نتائج ل [SEARCH_TERM]. جرب أحد عمليات البحث التالية:",searching:"يبحث عن [SEARCH_TERM]..."},Vs={thanks_to:ku,comments:Du,direction:bu,strings:Mu},Su={};F(Su,{comments:()=>wu,default:()=>Ks,direction:()=>Nu,strings:()=>yu,thanks_to:()=>Hu});var Hu="Maruf Alom ",wu="",Nu="ltr",yu={placeholder:"অনুসন্ধান করুন",clear_search:"মুছে ফেলুন",load_more:"আরো ফলাফল দেখুন",search_label:"এই ওয়েবসাইটে অনুসন্ধান করুন",filters_label:"ফিল্টার",zero_results:"[SEARCH_TERM] এর জন্য কিছু খুঁজে পাওয়া যায়নি",many_results:"[COUNT]-টি ফলাফল পাওয়া গিয়েছে [SEARCH_TERM] এর জন্য",one_result:"[COUNT]-টি ফলাফল পাওয়া গিয়েছে [SEARCH_TERM] এর জন্য",alt_search:"কোন কিছু খুঁজে পাওয়া যায়নি [SEARCH_TERM] এর জন্য. পরিবর্তে [DIFFERENT_TERM] এর জন্য দেখানো হচ্ছে",search_suggestion:"কোন কিছু খুঁজে পাওয়া যায়নি [SEARCH_TERM] এর বিষয়ে. নিন্মের বিষয়বস্তু খুঁজে দেখুন:",searching:"অনুসন্ধান চলছে [SEARCH_TERM]..."},Ks={thanks_to:Hu,comments:wu,direction:Nu,strings:yu},zu={};F(zu,{comments:()=>Ou,default:()=>Gs,direction:()=>Uu,strings:()=>Iu,thanks_to:()=>ju});var ju="Pablo Villaverde ",Ou="",Uu="ltr",Iu={placeholder:"Cerca",clear_search:"Netejar",load_more:"Veure més resultats",search_label:"Cerca en aquest lloc",filters_label:"Filtres",zero_results:"No es van trobar resultats per [SEARCH_TERM]",many_results:"[COUNT] resultats trobats per [SEARCH_TERM]",one_result:"[COUNT] resultat trobat per [SEARCH_TERM]",alt_search:"No es van trobar resultats per [SEARCH_TERM]. Mostrant al seu lloc resultats per [DIFFERENT_TERM]",search_suggestion:"No es van trobar resultats per [SEARCH_TERM]. Proveu una de les cerques següents:",searching:"Cercant [SEARCH_TERM]..."},Gs={thanks_to:ju,comments:Ou,direction:Uu,strings:Iu},Pu={};F(Pu,{comments:()=>xu,default:()=>Js,direction:()=>qu,strings:()=>Vu,thanks_to:()=>Lu});var Lu="Dalibor Hon ",xu="",qu="ltr",Vu={placeholder:"Hledat",clear_search:"Smazat",load_more:"Načíst další výsledky",search_label:"Prohledat tuto stránku",filters_label:"Filtry",zero_results:"Žádné výsledky pro [SEARCH_TERM]",many_results:"[COUNT] výsledků pro [SEARCH_TERM]",one_result:"[COUNT] výsledek pro [SEARCH_TERM]",alt_search:"Žádné výsledky pro [SEARCH_TERM]. Zobrazují se výsledky pro [DIFFERENT_TERM]",search_suggestion:"Žádné výsledky pro [SEARCH_TERM]. Související výsledky hledání:",searching:"Hledám [SEARCH_TERM]..."},Js={thanks_to:Lu,comments:xu,direction:qu,strings:Vu},Ku={};F(Ku,{comments:()=>Ju,default:()=>Ws,direction:()=>Wu,strings:()=>Zu,thanks_to:()=>Gu});var Gu="Jonas Smedegaard ",Ju="",Wu="ltr",Zu={placeholder:"Søg",clear_search:"Nulstil",load_more:"Indlæs flere resultater",search_label:"Søg på dette website",filters_label:"Filtre",zero_results:"Ingen resultater for [SEARCH_TERM]",many_results:"[COUNT] resultater for [SEARCH_TERM]",one_result:"[COUNT] resultat for [SEARCH_TERM]",alt_search:"Ingen resultater for [SEARCH_TERM]. Viser resultater for [DIFFERENT_TERM] i stedet",search_suggestion:"Ingen resultater for [SEARCH_TERM]. Prøv et af disse søgeord i stedet:",searching:"Søger efter [SEARCH_TERM]..."},Ws={thanks_to:Gu,comments:Ju,direction:Wu,strings:Zu},Yu={};F(Yu,{comments:()=>Qu,default:()=>Zs,direction:()=>$u,strings:()=>et,thanks_to:()=>Xu});var Xu="Jan Claasen ",Qu="",$u="ltr",et={placeholder:"Suche",clear_search:"Löschen",load_more:"Mehr Ergebnisse laden",search_label:"Suche diese Seite",filters_label:"Filter",zero_results:"Keine Ergebnisse für [SEARCH_TERM]",many_results:"[COUNT] Ergebnisse für [SEARCH_TERM]",one_result:"[COUNT] Ergebnis für [SEARCH_TERM]",alt_search:"Keine Ergebnisse für [SEARCH_TERM]. Stattdessen werden Ergebnisse für [DIFFERENT_TERM] angezeigt",search_suggestion:"Keine Ergebnisse für [SEARCH_TERM]. Versuchen Sie eine der folgenden Suchen:",searching:"Suche für [SEARCH_TERM]"},Zs={thanks_to:Xu,comments:Qu,direction:$u,strings:et},ut={};F(ut,{comments:()=>rt,default:()=>Ys,direction:()=>st,strings:()=>at,thanks_to:()=>tt});var tt="Liam Bigelow ",rt="",st="ltr",at={placeholder:"Search",clear_search:"Clear",load_more:"Load more results",search_label:"Search this site",filters_label:"Filters",zero_results:"No results for [SEARCH_TERM]",many_results:"[COUNT] results for [SEARCH_TERM]",one_result:"[COUNT] result for [SEARCH_TERM]",alt_search:"No results for [SEARCH_TERM]. Showing results for [DIFFERENT_TERM] instead",search_suggestion:"No results for [SEARCH_TERM]. Try one of the following searches:",searching:"Searching for [SEARCH_TERM]..."},Ys={thanks_to:tt,comments:rt,direction:st,strings:at},lt={};F(lt,{comments:()=>it,default:()=>Xs,direction:()=>ot,strings:()=>_t,thanks_to:()=>nt});var nt="Pablo Villaverde ",it="",ot="ltr",_t={placeholder:"Buscar",clear_search:"Limpiar",load_more:"Ver más resultados",search_label:"Buscar en este sitio",filters_label:"Filtros",zero_results:"No se encontraron resultados para [SEARCH_TERM]",many_results:"[COUNT] resultados encontrados para [SEARCH_TERM]",one_result:"[COUNT] resultado encontrado para [SEARCH_TERM]",alt_search:"No se encontraron resultados para [SEARCH_TERM]. Mostrando en su lugar resultados para [DIFFERENT_TERM]",search_suggestion:"No se encontraron resultados para [SEARCH_TERM]. Prueba una de las siguientes búsquedas:",searching:"Buscando [SEARCH_TERM]..."},Xs={thanks_to:nt,comments:it,direction:ot,strings:_t},ct={};F(ct,{comments:()=>Et,default:()=>Qs,direction:()=>dt,strings:()=>ht,thanks_to:()=>ft});var ft="Ali Khaleqi Yekta ",Et="",dt="rtl",ht={placeholder:"جستجو",clear_search:"پاکسازی",load_more:"بارگذاری نتایج بیشتر",search_label:"جستجو در سایت",filters_label:"فیلترها",zero_results:"نتیجه‌ای برای [SEARCH_TERM] یافت نشد",many_results:"[COUNT] نتیجه برای [SEARCH_TERM] یافت شد",one_result:"[COUNT] نتیجه برای [SEARCH_TERM] یافت شد",alt_search:"نتیجه‌ای برای [SEARCH_TERM] یافت نشد. در عوض نتایج برای [DIFFERENT_TERM] نمایش داده می‌شود",search_suggestion:"نتیجه‌ای برای [SEARCH_TERM] یافت نشد. یکی از جستجوهای زیر را امتحان کنید:",searching:"در حال جستجوی [SEARCH_TERM]..."},Qs={thanks_to:ft,comments:Et,direction:dt,strings:ht},Ct={};F(Ct,{comments:()=>gt,default:()=>$s,direction:()=>At,strings:()=>Rt,thanks_to:()=>mt});var mt="Valtteri Laitinen ",gt="",At="ltr",Rt={placeholder:"Haku",clear_search:"Tyhjennä",load_more:"Lataa lisää tuloksia",search_label:"Hae tältä sivustolta",filters_label:"Suodattimet",zero_results:"Ei tuloksia haulle [SEARCH_TERM]",many_results:"[COUNT] tulosta haulle [SEARCH_TERM]",one_result:"[COUNT] tulos haulle [SEARCH_TERM]",alt_search:"Ei tuloksia haulle [SEARCH_TERM]. Näytetään tulokset sen sijaan haulle [DIFFERENT_TERM]",search_suggestion:"Ei tuloksia haulle [SEARCH_TERM]. Kokeile jotain seuraavista:",searching:"Haetaan [SEARCH_TERM]..."},$s={thanks_to:mt,comments:gt,direction:At,strings:Rt},Bt={};F(Bt,{comments:()=>pt,default:()=>ea,direction:()=>Tt,strings:()=>Ft,thanks_to:()=>vt});var vt="Nicolas Friedli ",pt="",Tt="ltr",Ft={placeholder:"Rechercher",clear_search:"Nettoyer",load_more:"Charger plus de résultats",search_label:"Recherche sur ce site",filters_label:"Filtres",zero_results:"Pas de résultat pour [SEARCH_TERM]",many_results:"[COUNT] résultats pour [SEARCH_TERM]",one_result:"[COUNT] résultat pour [SEARCH_TERM]",alt_search:"Pas de résultat pour [SEARCH_TERM]. Montre les résultats pour [DIFFERENT_TERM] à la place",search_suggestion:"Pas de résultat pour [SEARCH_TERM]. Essayer une des recherches suivantes:",searching:"Recherche [SEARCH_TERM]..."},ea={thanks_to:vt,comments:pt,direction:Tt,strings:Ft},kt={};F(kt,{comments:()=>bt,default:()=>ua,direction:()=>Mt,strings:()=>St,thanks_to:()=>Dt});var Dt="Pablo Villaverde ",bt="",Mt="ltr",St={placeholder:"Buscar",clear_search:"Limpar",load_more:"Ver máis resultados",search_label:"Buscar neste sitio",filters_label:"Filtros",zero_results:"Non se atoparon resultados para [SEARCH_TERM]",many_results:"[COUNT] resultados atopados para [SEARCH_TERM]",one_result:"[COUNT] resultado atopado para [SEARCH_TERM]",alt_search:"Non se atoparon resultados para [SEARCH_TERM]. Amosando no seu lugar resultados para [DIFFERENT_TERM]",search_suggestion:"Non se atoparon resultados para [SEARCH_TERM]. Probe unha das seguintes pesquisas:",searching:"Buscando [SEARCH_TERM]..."},ua={thanks_to:Dt,comments:bt,direction:Mt,strings:St},Ht={};F(Ht,{comments:()=>Nt,default:()=>ta,direction:()=>yt,strings:()=>zt,thanks_to:()=>wt});var wt="Nir Tamir ",Nt="",yt="rtl",zt={placeholder:"חיפוש",clear_search:"ניקוי",load_more:"עוד תוצאות",search_label:"חיפוש באתר זה",filters_label:"מסננים",zero_results:"לא נמצאו תוצאות עבור [SEARCH_TERM]",many_results:"נמצאו [COUNT] תוצאות עבור [SEARCH_TERM]",one_result:"נמצאה תוצאה אחת עבור [SEARCH_TERM]",alt_search:"לא נמצאו תוצאות עבור [SEARCH_TERM]. מוצגות תוצאות עבור [DIFFERENT_TERM]",search_suggestion:"לא נמצאו תוצאות עבור [SEARCH_TERM]. נסו אחד מהחיפושים הבאים:",searching:"מחפש את [SEARCH_TERM]..."},ta={thanks_to:wt,comments:Nt,direction:yt,strings:zt},jt={};F(jt,{comments:()=>Ut,default:()=>ra,direction:()=>It,strings:()=>Pt,thanks_to:()=>Ot});var Ot="Amit Yadav ",Ut="",It="ltr",Pt={placeholder:"खोजें",clear_search:"साफ करें",load_more:"और अधिक परिणाम लोड करें",search_label:"इस साइट में खोजें",filters_label:"फ़िल्टर",zero_results:"कोई परिणाम [SEARCH_TERM] के लिए नहीं मिला",many_results:"[COUNT] परिणाम [SEARCH_TERM] के लिए मिले",one_result:"[COUNT] परिणाम [SEARCH_TERM] के लिए मिला",alt_search:"[SEARCH_TERM] के लिए कोई परिणाम नहीं मिला। इसके बजाय [DIFFERENT_TERM] के लिए परिणाम दिखा रहा है",search_suggestion:"[SEARCH_TERM] के लिए कोई परिणाम नहीं मिला। निम्नलिखित खोजों में से कोई एक आज़माएं:",searching:"[SEARCH_TERM] की खोज की जा रही है..."},ra={thanks_to:Ot,comments:Ut,direction:It,strings:Pt},Lt={};F(Lt,{comments:()=>qt,default:()=>sa,direction:()=>Vt,strings:()=>Kt,thanks_to:()=>xt});var xt="Diomed ",qt="",Vt="ltr",Kt={placeholder:"Traži",clear_search:"Očisti",load_more:"Učitaj više rezultata",search_label:"Pretraži ovu stranicu",filters_label:"Filteri",zero_results:"Nema rezultata za [SEARCH_TERM]",many_results:"[COUNT] rezultata za [SEARCH_TERM]",one_result:"[COUNT] rezultat za [SEARCH_TERM]",alt_search:"Nema rezultata za [SEARCH_TERM]. Prikazujem rezultate za [DIFFERENT_TERM]",search_suggestion:"Nema rezultata za [SEARCH_TERM]. Pokušaj s jednom od ovih pretraga:",searching:"Pretražujem [SEARCH_TERM]..."},sa={thanks_to:xt,comments:qt,direction:Vt,strings:Kt},Gt={};F(Gt,{comments:()=>Wt,default:()=>aa,direction:()=>Zt,strings:()=>Yt,thanks_to:()=>Jt});var Jt="Adam Laki ",Wt="",Zt="ltr",Yt={placeholder:"Keresés",clear_search:"Törlés",load_more:"További találatok betöltése",search_label:"Keresés az oldalon",filters_label:"Szűrés",zero_results:"Nincs találat a(z) [SEARCH_TERM] kifejezésre",many_results:"[COUNT] db találat a(z) [SEARCH_TERM] kifejezésre",one_result:"[COUNT] db találat a(z) [SEARCH_TERM] kifejezésre",alt_search:"Nincs találat a(z) [SEARCH_TERM] kifejezésre. Találatok mutatása inkább a(z) [DIFFERENT_TERM] kifejezésre",search_suggestion:"Nincs találat a(z) [SEARCH_TERM] kifejezésre. Próbáld meg a következő keresések egyikét:",searching:"Keresés a(z) [SEARCH_TERM] kifejezésre..."},aa={thanks_to:Jt,comments:Wt,direction:Zt,strings:Yt},Xt={};F(Xt,{comments:()=>$t,default:()=>la,direction:()=>er,strings:()=>ur,thanks_to:()=>Qt});var Qt="Nixentric",$t="",er="ltr",ur={placeholder:"Cari",clear_search:"Bersihkan",load_more:"Muat lebih banyak hasil",search_label:"Telusuri situs ini",filters_label:"Filter",zero_results:"[SEARCH_TERM] tidak ditemukan",many_results:"Ditemukan [COUNT] hasil untuk [SEARCH_TERM]",one_result:"Ditemukan [COUNT] hasil untuk [SEARCH_TERM]",alt_search:"[SEARCH_TERM] tidak ditemukan. Menampilkan hasil [DIFFERENT_TERM] sebagai gantinya",search_suggestion:"[SEARCH_TERM] tidak ditemukan. Coba salah satu pencarian berikut ini:",searching:"Mencari [SEARCH_TERM]..."},la={thanks_to:Qt,comments:$t,direction:er,strings:ur},tr={};F(tr,{comments:()=>sr,default:()=>na,direction:()=>ar,strings:()=>lr,thanks_to:()=>rr});var rr="Cosette Bruhns Alonso, Andrew Janco ",sr="",ar="ltr",lr={placeholder:"Cerca",clear_search:"Cancella la cronologia",load_more:"Mostra più risultati",search_label:"Cerca nel sito",filters_label:"Filtri di ricerca",zero_results:"Nessun risultato per [SEARCH_TERM]",many_results:"[COUNT] risultati per [SEARCH_TERM]",one_result:"[COUNT] risultato per [SEARCH_TERM]",alt_search:"Nessun risultato per [SEARCH_TERM]. Mostrando risultati per [DIFFERENT_TERM] come alternativa.",search_suggestion:"Nessun risultato per [SEARCH_TERM]. Prova una delle seguenti ricerche:",searching:"Cercando [SEARCH_TERM]..."},na={thanks_to:rr,comments:sr,direction:ar,strings:lr},nr={};F(nr,{comments:()=>or,default:()=>ia,direction:()=>_r,strings:()=>cr,thanks_to:()=>ir});var ir="Tate",or="",_r="ltr",cr={placeholder:"検索",clear_search:"クリア",load_more:"次を読み込む",search_label:"このサイトを検索",filters_label:"フィルタ",zero_results:"[SEARCH_TERM]の検索に一致する情報はありませんでした",many_results:"[SEARCH_TERM]の[COUNT]件の検索結果",one_result:"[SEARCH_TERM]の[COUNT]件の検索結果",alt_search:"[SEARCH_TERM]の検索に一致する情報はありませんでした。[DIFFERENT_TERM]の検索結果を表示しています",search_suggestion:"[SEARCH_TERM]の検索に一致する情報はありませんでした。次のいずれかの検索を試してください",searching:"[SEARCH_TERM]を検索しています"},ia={thanks_to:ir,comments:or,direction:_r,strings:cr},fr={};F(fr,{comments:()=>dr,default:()=>oa,direction:()=>hr,strings:()=>Cr,thanks_to:()=>Er});var Er="Seokho Son ",dr="",hr="ltr",Cr={placeholder:"검색어",clear_search:"비우기",load_more:"검색 결과 더 보기",search_label:"사이트 검색",filters_label:"필터",zero_results:"[SEARCH_TERM]에 대한 결과 없음",many_results:"[SEARCH_TERM]에 대한 결과 [COUNT]건",one_result:"[SEARCH_TERM]에 대한 결과 [COUNT]건",alt_search:"[SEARCH_TERM]에 대한 결과 없음. [DIFFERENT_TERM]에 대한 결과",search_suggestion:"[SEARCH_TERM]에 대한 결과 없음. 추천 검색어: ",searching:"[SEARCH_TERM] 검색 중..."},oa={thanks_to:Er,comments:dr,direction:hr,strings:Cr},mr={};F(mr,{comments:()=>Ar,default:()=>_a,direction:()=>Rr,strings:()=>Br,thanks_to:()=>gr});var gr="",Ar="",Rr="ltr",Br={placeholder:"Rapu",clear_search:"Whakakore",load_more:"Whakauta ētahi otinga kē",search_label:"Rapu",filters_label:"Tātari",zero_results:"Otinga kore ki [SEARCH_TERM]",many_results:"[COUNT] otinga ki [SEARCH_TERM]",one_result:"[COUNT] otinga ki [SEARCH_TERM]",alt_search:"Otinga kore ki [SEARCH_TERM]. Otinga kē ki [DIFFERENT_TERM]",search_suggestion:"Otinga kore ki [SEARCH_TERM]. whakamātau ki ngā mea atu:",searching:"Rapu ki [SEARCH_TERM]..."},_a={thanks_to:gr,comments:Ar,direction:Rr,strings:Br},vr={};F(vr,{comments:()=>Tr,default:()=>ca,direction:()=>Fr,strings:()=>kr,thanks_to:()=>pr});var pr="Paul van Brouwershaven",Tr="",Fr="ltr",kr={placeholder:"Zoeken",clear_search:"Reset",load_more:"Meer resultaten laden",search_label:"Doorzoek deze site",filters_label:"Filters",zero_results:"Geen resultaten voor [SEARCH_TERM]",many_results:"[COUNT] resultaten voor [SEARCH_TERM]",one_result:"[COUNT] resultaat voor [SEARCH_TERM]",alt_search:"Geen resultaten voor [SEARCH_TERM]. In plaats daarvan worden resultaten voor [DIFFERENT_TERM] weergegeven",search_suggestion:"Geen resultaten voor [SEARCH_TERM]. Probeer een van de volgende zoekopdrachten:",searching:"Zoeken naar [SEARCH_TERM]..."},ca={thanks_to:pr,comments:Tr,direction:Fr,strings:kr},Dr={};F(Dr,{comments:()=>Mr,default:()=>fa,direction:()=>Sr,strings:()=>Hr,thanks_to:()=>br});var br="Christopher Wingate",Mr="",Sr="ltr",Hr={placeholder:"Søk",clear_search:"Fjern",load_more:"Last flere resultater",search_label:"Søk på denne siden",filters_label:"Filtre",zero_results:"Ingen resultater for [SEARCH_TERM]",many_results:"[COUNT] resultater for [SEARCH_TERM]",one_result:"[COUNT] resultat for [SEARCH_TERM]",alt_search:"Ingen resultater for [SEARCH_TERM]. Viser resultater for [DIFFERENT_TERM] i stedet",search_suggestion:"Ingen resultater for [SEARCH_TERM]. Prøv en av disse søkeordene i stedet:",searching:"Søker etter [SEARCH_TERM]"},fa={thanks_to:br,comments:Mr,direction:Sr,strings:Hr},wr={};F(wr,{comments:()=>yr,default:()=>Ea,direction:()=>zr,strings:()=>jr,thanks_to:()=>Nr});var Nr="",yr="",zr="ltr",jr={placeholder:"Szukaj",clear_search:"Wyczyść",load_more:"Załaduj więcej",search_label:"Przeszukaj tę stronę",filters_label:"Filtry",zero_results:"Brak wyników dla [SEARCH_TERM]",many_results:"[COUNT] wyników dla [SEARCH_TERM]",one_result:"[COUNT] wynik dla [SEARCH_TERM]",alt_search:"Brak wyników dla [SEARCH_TERM]. Wyświetlam wyniki dla [DIFFERENT_TERM]",search_suggestion:"Brak wyników dla [SEARCH_TERM]. Pokrewne wyniki wyszukiwania:",searching:"Szukam [SEARCH_TERM]..."},Ea={thanks_to:Nr,comments:yr,direction:zr,strings:jr},Or={};F(Or,{comments:()=>Ir,default:()=>da,direction:()=>Pr,strings:()=>Lr,thanks_to:()=>Ur});var Ur="Jonatah",Ir="",Pr="ltr",Lr={placeholder:"Pesquisar",clear_search:"Limpar",load_more:"Ver mais resultados",search_label:"Pesquisar",filters_label:"Filtros",zero_results:"Nenhum resultado encontrado para [SEARCH_TERM]",many_results:"[COUNT] resultados encontrados para [SEARCH_TERM]",one_result:"[COUNT] resultado encontrado para [SEARCH_TERM]",alt_search:"Nenhum resultado encontrado para [SEARCH_TERM]. Exibindo resultados para [DIFFERENT_TERM]",search_suggestion:"Nenhum resultado encontrado para [SEARCH_TERM]. Tente uma das seguintes pesquisas:",searching:"Pesquisando por [SEARCH_TERM]..."},da={thanks_to:Ur,comments:Ir,direction:Pr,strings:Lr},xr={};F(xr,{comments:()=>Vr,default:()=>ha,direction:()=>Kr,strings:()=>Gr,thanks_to:()=>qr});var qr="Bogdan Mateescu ",Vr="",Kr="ltr",Gr={placeholder:"Căutare",clear_search:"Ştergeţi",load_more:"Încărcați mai multe rezultate",search_label:"Căutați în acest site",filters_label:"Filtre",zero_results:"Niciun rezultat pentru [SEARCH_TERM]",many_results:"[COUNT] rezultate pentru [SEARCH_TERM]",one_result:"[COUNT] rezultat pentru [SEARCH_TERM]",alt_search:"Niciun rezultat pentru [SEARCH_TERM]. Se afișează în schimb rezultatele pentru [DIFFERENT_TERM]",search_suggestion:"Niciun rezultat pentru [SEARCH_TERM]. Încercați una dintre următoarele căutări:",searching:"Se caută după: [SEARCH_TERM]..."},ha={thanks_to:qr,comments:Vr,direction:Kr,strings:Gr},Jr={};F(Jr,{comments:()=>Zr,default:()=>Ca,direction:()=>Yr,strings:()=>Xr,thanks_to:()=>Wr});var Wr="Aleksandr Gordeev",Zr="",Yr="ltr",Xr={placeholder:"Поиск",clear_search:"Очистить поле",load_more:"Загрузить еще",search_label:"Поиск по сайту",filters_label:"Фильтры",zero_results:"Ничего не найдено по запросу: [SEARCH_TERM]",many_results:"[COUNT] результатов по запросу: [SEARCH_TERM]",one_result:"[COUNT] результат по запросу: [SEARCH_TERM]",alt_search:"Ничего не найдено по запросу: [SEARCH_TERM]. Показаны результаты по запросу: [DIFFERENT_TERM]",search_suggestion:"Ничего не найдено по запросу: [SEARCH_TERM]. Попробуйте один из следующих вариантов",searching:"Поиск по запросу: [SEARCH_TERM]"},Ca={thanks_to:Wr,comments:Zr,direction:Yr,strings:Xr},Qr={};F(Qr,{comments:()=>e0,default:()=>ma,direction:()=>u0,strings:()=>t0,thanks_to:()=>$r});var $r="Andrija Sagicc",e0="",u0="ltr",t0={placeholder:"Претрага",clear_search:"Брисање",load_more:"Приказ више резултата",search_label:"Претрага сајта",filters_label:"Филтери",zero_results:"Нема резултата за [SEARCH_TERM]",many_results:"[COUNT] резултата за [SEARCH_TERM]",one_result:"[COUNT] резултата за [SEARCH_TERM]",alt_search:"Нема резултата за [SEARCH_TERM]. Приказ додатник резултата за [DIFFERENT_TERM]",search_suggestion:"Нема резултата за [SEARCH_TERM]. Покушајте са неком од следећих претрага:",searching:"Претрага термина [SEARCH_TERM]..."},ma={thanks_to:$r,comments:e0,direction:u0,strings:t0},r0={};F(r0,{comments:()=>a0,default:()=>ga,direction:()=>l0,strings:()=>n0,thanks_to:()=>s0});var s0="Montazar Al-Jaber ",a0="",l0="ltr",n0={placeholder:"Sök",clear_search:"Rensa",load_more:"Visa fler träffar",search_label:"Sök på denna sida",filters_label:"Filter",zero_results:"[SEARCH_TERM] gav inga träffar",many_results:"[SEARCH_TERM] gav [COUNT] träffar",one_result:"[SEARCH_TERM] gav [COUNT] träff",alt_search:"[SEARCH_TERM] gav inga träffar. Visar resultat för [DIFFERENT_TERM] istället",search_suggestion:"[SEARCH_TERM] gav inga träffar. Försök igen med en av följande sökord:",searching:"Söker efter [SEARCH_TERM]..."},ga={thanks_to:s0,comments:a0,direction:l0,strings:n0},i0={};F(i0,{comments:()=>_0,default:()=>Aa,direction:()=>c0,strings:()=>f0,thanks_to:()=>o0});var o0="Anonymous",_0="",c0="ltr",f0={placeholder:"Tafuta",clear_search:"Futa",load_more:"Pakia matokeo zaidi",search_label:"Tafuta tovuti hii",filters_label:"Vichujio",zero_results:"Hakuna matokeo ya [SEARCH_TERM]",many_results:"Matokeo [COUNT] ya [SEARCH_TERM]",one_result:"Tokeo [COUNT] la [SEARCH_TERM]",alt_search:"Hakuna mayokeo ya [SEARCH_TERM]. Badala yake, inaonyesha matokeo ya [DIFFERENT_TERM]",search_suggestion:"Hakuna matokeo ya [SEARCH_TERM]. Jaribu mojawapo ya utafutaji ufuatao:",searching:"Kutafuta [SEARCH_TERM]..."},Aa={thanks_to:o0,comments:_0,direction:c0,strings:f0},E0={};F(E0,{comments:()=>h0,default:()=>Ra,direction:()=>C0,strings:()=>m0,thanks_to:()=>d0});var d0="",h0="",C0="ltr",m0={placeholder:"தேடுக",clear_search:"அழிக்குக",load_more:"மேலும் முடிவுகளைக் காட்டுக",search_label:"இந்த தளத்தில் தேடுக",filters_label:"வடிகட்டல்கள்",zero_results:"[SEARCH_TERM] க்கான முடிவுகள் இல்லை",many_results:"[SEARCH_TERM] க்கான [COUNT] முடிவுகள்",one_result:"[SEARCH_TERM] க்கான முடிவு",alt_search:"[SEARCH_TERM] இத்தேடலுக்கான முடிவுகள் இல்லை, இந்த தேடல்களுக்கான ஒத்த முடிவுகள் [DIFFERENT_TERM]",search_suggestion:"[SEARCH_TERM] இத் தேடலுக்கான முடிவுகள் இல்லை.இதற்கு பதிலீடான தேடல்களை தேடுக:",searching:"[SEARCH_TERM] தேடப்படுகின்றது"},Ra={thanks_to:d0,comments:h0,direction:C0,strings:m0},g0={};F(g0,{comments:()=>R0,default:()=>Ba,direction:()=>B0,strings:()=>v0,thanks_to:()=>A0});var A0="Taylan Özgür Bildik",R0="",B0="ltr",v0={placeholder:"Araştır",clear_search:"Temizle",load_more:"Daha fazla sonuç",search_label:"Site genelinde arama",filters_label:"Filtreler",zero_results:"[SEARCH_TERM] için sonuç yok",many_results:"[SEARCH_TERM] için [COUNT] sonuç bulundu",one_result:"[SEARCH_TERM] için [COUNT] sonuç bulundu",alt_search:"[SEARCH_TERM] için sonuç yok. Bunun yerine [DIFFERENT_TERM] için sonuçlar gösteriliyor",search_suggestion:"[SEARCH_TERM] için sonuç yok. Alternatif olarak aşağıdaki kelimelerden birini deneyebilirsiniz:",searching:"[SEARCH_TERM] araştırılıyor..."},Ba={thanks_to:A0,comments:R0,direction:B0,strings:v0},p0={};F(p0,{comments:()=>F0,default:()=>va,direction:()=>k0,strings:()=>D0,thanks_to:()=>T0});var T0="Vladyslav Lyshenko ",F0="",k0="ltr",D0={placeholder:"Пошук",clear_search:"Очистити поле",load_more:"Завантажити ще",search_label:"Пошук по сайту",filters_label:"Фільтри",zero_results:"Нічого не знайдено за запитом: [SEARCH_TERM]",many_results:"[COUNT] результатів на запит: [SEARCH_TERM]",one_result:"[COUNT] результат за запитом: [SEARCH_TERM]",alt_search:"Нічого не знайдено на запит: [SEARCH_TERM]. Показано результати на запит: [DIFFERENT_TERM]",search_suggestion:"Нічого не знайдено на запит: [SEARCH_TERM]. Спробуйте один із таких варіантів",searching:"Пошук за запитом: [SEARCH_TERM]"},va={thanks_to:T0,comments:F0,direction:k0,strings:D0},b0={};F(b0,{comments:()=>S0,default:()=>pa,direction:()=>H0,strings:()=>w0,thanks_to:()=>M0});var M0="Long Nhat Nguyen",S0="",H0="ltr",w0={placeholder:"Tìm kiếm",clear_search:"Xóa",load_more:"Nhiều kết quả hơn",search_label:"Tìm kiếm trong trang này",filters_label:"Bộ lọc",zero_results:"Không tìm thấy kết quả cho [SEARCH_TERM]",many_results:"[COUNT] kết quả cho [SEARCH_TERM]",one_result:"[COUNT] kết quả cho [SEARCH_TERM]",alt_search:"Không tìm thấy kết quả cho [SEARCH_TERM]. Kiểm thị kết quả thay thế với [DIFFERENT_TERM]",search_suggestion:"Không tìm thấy kết quả cho [SEARCH_TERM]. Thử một trong các tìm kiếm:",searching:"Đang tìm kiếm cho [SEARCH_TERM]..."},pa={thanks_to:M0,comments:S0,direction:H0,strings:w0},N0={};F(N0,{comments:()=>z0,default:()=>Ta,direction:()=>j0,strings:()=>O0,thanks_to:()=>y0});var y0="Amber Song",z0="",j0="ltr",O0={placeholder:"搜索",clear_search:"清除",load_more:"加载更多结果",search_label:"站内搜索",filters_label:"筛选",zero_results:"未找到 [SEARCH_TERM] 的相关结果",many_results:"找到 [COUNT] 个 [SEARCH_TERM] 的相关结果",one_result:"找到 [COUNT] 个 [SEARCH_TERM] 的相关结果",alt_search:"未找到 [SEARCH_TERM] 的相关结果。改为显示 [DIFFERENT_TERM] 的相关结果",search_suggestion:"未找到 [SEARCH_TERM] 的相关结果。请尝试以下搜索。",searching:"正在搜索 [SEARCH_TERM]..."},Ta={thanks_to:y0,comments:z0,direction:j0,strings:O0},U0={};F(U0,{comments:()=>P0,default:()=>Fa,direction:()=>L0,strings:()=>x0,thanks_to:()=>I0});var I0="Amber Song",P0="",L0="ltr",x0={placeholder:"搜索",clear_search:"清除",load_more:"加載更多結果",search_label:"站內搜索",filters_label:"篩選",zero_results:"未找到 [SEARCH_TERM] 的相關結果",many_results:"找到 [COUNT] 個 [SEARCH_TERM] 的相關結果",one_result:"找到 [COUNT] 個 [SEARCH_TERM] 的相關結果",alt_search:"未找到 [SEARCH_TERM] 的相關結果。改為顯示 [DIFFERENT_TERM] 的相關結果",search_suggestion:"未找到 [SEARCH_TERM] 的相關結果。請嘗試以下搜索。",searching:"正在搜索 [SEARCH_TERM]..."},Fa={thanks_to:I0,comments:P0,direction:L0,strings:x0},q0={};F(q0,{comments:()=>K0,default:()=>ka,direction:()=>G0,strings:()=>J0,thanks_to:()=>V0});var V0="Amber Song",K0="",G0="ltr",J0={placeholder:"搜索",clear_search:"清除",load_more:"加载更多结果",search_label:"站内搜索",filters_label:"筛选",zero_results:"未找到 [SEARCH_TERM] 的相关结果",many_results:"找到 [COUNT] 个 [SEARCH_TERM] 的相关结果",one_result:"找到 [COUNT] 个 [SEARCH_TERM] 的相关结果",alt_search:"未找到 [SEARCH_TERM] 的相关结果。改为显示 [DIFFERENT_TERM] 的相关结果",search_suggestion:"未找到 [SEARCH_TERM] 的相关结果。请尝试以下搜索。",searching:"正在搜索 [SEARCH_TERM]..."},ka={thanks_to:V0,comments:K0,direction:G0,strings:J0},Da=[Ru,Fu,Su,zu,Pu,Ku,Yu,ut,lt,ct,Ct,Bt,kt,Ht,jt,Lt,Gt,Xt,tr,nr,fr,mr,vr,Dr,wr,Or,xr,Jr,Qr,r0,i0,E0,g0,p0,b0,N0,U0,q0],ba=Da,Ma=["../../translations/af.json","../../translations/ar.json","../../translations/bn.json","../../translations/ca.json","../../translations/cs.json","../../translations/da.json","../../translations/de.json","../../translations/en.json","../../translations/es.json","../../translations/fa.json","../../translations/fi.json","../../translations/fr.json","../../translations/gl.json","../../translations/he.json","../../translations/hi.json","../../translations/hr.json","../../translations/hu.json","../../translations/id.json","../../translations/it.json","../../translations/ja.json","../../translations/ko.json","../../translations/mi.json","../../translations/nl.json","../../translations/no.json","../../translations/pl.json","../../translations/pt.json","../../translations/ro.json","../../translations/ru.json","../../translations/sr.json","../../translations/sv.json","../../translations/sw.json","../../translations/ta.json","../../translations/tr.json","../../translations/uk.json","../../translations/vi.json","../../translations/zh-cn.json","../../translations/zh-tw.json","../../translations/zh.json"];function ou(e,u,t){const r=e.slice();return r[51]=u[t],r}function _u(e){let u,t,r;function s(l){e[37](l)}let a={show_empty_filters:e[5],open_filters:e[6],available_filters:e[18],translate:e[20],automatic_translations:e[19],translations:e[7]};return e[0]!==void 0&&(a.selected_filters=e[0]),u=new xs({props:a}),ae.push(()=>ps(u,"selected_filters",s)),{c(){ke(u.$$.fragment)},m(l,n){Ee(u,l,n),r=!0},p(l,n){const i={};n[0]&32&&(i.show_empty_filters=l[5]),n[0]&64&&(i.open_filters=l[6]),n[0]&262144&&(i.available_filters=l[18]),n[0]&524288&&(i.automatic_translations=l[19]),n[0]&128&&(i.translations=l[7]),!t&&n[0]&1&&(t=!0,i.selected_filters=l[0],gs(()=>t=!1)),u.$set(i)},i(l){r||(z(u.$$.fragment,l),r=!0)},o(l){U(u.$$.fragment,l),r=!1},d(l){de(u,l)}}}function cu(e){let u,t,r,s;const a=[Ha,Sa],l=[];function n(i,f){return i[14]?0:1}return t=n(e),r=l[t]=a[t](e),{c(){u=v("div"),r.c(),g(u,"class","pagefind-ui__results-area svelte-e9gkc3")},m(i,f){T(i,u,f),l[t].m(u,null),s=!0},p(i,f){let o=t;t=n(i),t===o?l[t].p(i,f):(le(),U(l[o],1,1,()=>{l[o]=null}),ne(),r=l[t],r?r.p(i,f):(r=l[t]=a[t](i),r.c()),z(r,1),r.m(u,null))},i(i){s||(z(r),s=!0)},o(i){U(r),s=!1},d(i){i&&p(u),l[t].d()}}}function Sa(e){let u,t,r,s=[],a=new Map,l,n,i;function f(_,d){return _[13].results.length===0?ya:_[13].results.length===1?Na:wa}let o=f(e),h=o(e),C=e[13].results.slice(0,e[17]);const E=_=>_[51].id;for(let _=0;_e[17]&&Eu(e);return{c(){u=v("p"),h.c(),t=b(),r=v("ol");for(let _=0;__[17]?c?c.p(_,d):(c=Eu(_),c.c(),c.m(n.parentNode,n)):c&&(c.d(1),c=null)},i(_){if(!i){for(let d=0;d{i[C]=null}),ne(),s=i[r],s?s.p(u,h):(s=i[r]=n[r](u),s.c()),z(s,1),s.m(a.parentNode,a))},i(o){l||(z(s),l=!0)},o(o){U(s),l=!1},d(o){o&&p(t),i[r].d(o),o&&p(a)}}}function Eu(e){let u,t=e[20]("load_more",e[19],e[7])+"",r,s,a;return{c(){u=v("button"),r=M(t),g(u,"type","button"),g(u,"class","pagefind-ui__button svelte-e9gkc3")},m(l,n){T(l,u,n),B(u,r),s||(a=G(u,"click",e[22]),s=!0)},p(l,n){n[0]&524416&&t!==(t=l[20]("load_more",l[19],l[7])+"")&&j(r,t)},d(l){l&&p(u),s=!1,a()}}}function du(e){let u,t=e[20]("searching",e[19],e[7]).replace(/\[SEARCH_TERM\]/,e[16])+"",r;return{c(){u=v("p"),r=M(t),g(u,"class","pagefind-ui__message svelte-e9gkc3")},m(s,a){T(s,u,a),B(u,r)},p(s,a){a[0]&589952&&t!==(t=s[20]("searching",s[19],s[7]).replace(/\[SEARCH_TERM\]/,s[16])+"")&&j(r,t)},d(s){s&&p(u)}}}function Oa(e){let u,t,r,s,a,l,n=e[20]("clear_search",e[19],e[7])+"",i,f,o,h,C,E,c,_,d=e[12]&&_u(e),R=e[15]&&cu(e);return{c(){u=v("div"),t=v("form"),r=v("input"),a=b(),l=v("button"),i=M(n),f=b(),o=v("div"),d&&d.c(),h=b(),R&&R.c(),g(r,"class","pagefind-ui__search-input svelte-e9gkc3"),g(r,"type","text"),g(r,"placeholder",s=e[20]("placeholder",e[19],e[7])),g(r,"autocapitalize","none"),g(r,"enterkeyhint","search"),r.autofocus=e[8],g(l,"class","pagefind-ui__search-clear svelte-e9gkc3"),K(l,"pagefind-ui__suppressed",!e[9]),g(o,"class","pagefind-ui__drawer svelte-e9gkc3"),K(o,"pagefind-ui__hidden",!e[15]),g(t,"class","pagefind-ui__form svelte-e9gkc3"),g(t,"role","search"),g(t,"aria-label",C=e[20]("search_label",e[19],e[7])),g(t,"action","javascript:void(0);"),g(u,"class","pagefind-ui svelte-e9gkc3"),K(u,"pagefind-ui--reset",e[1])},m(A,k){T(A,u,k),B(u,t),B(t,r),ze(r,e[9]),e[34](r),B(t,a),B(t,l),B(l,i),e[35](l),B(t,f),B(t,o),d&&d.m(o,null),B(o,h),R&&R.m(o,null),E=!0,e[8]&&r.focus(),c||(_=[G(r,"focus",e[21]),G(r,"keydown",e[32]),G(r,"input",e[33]),G(l,"click",e[36]),G(t,"submit",Ua)],c=!0)},p(A,k){(!E||k[0]&524416&&s!==(s=A[20]("placeholder",A[19],A[7])))&&g(r,"placeholder",s),(!E||k[0]&256)&&(r.autofocus=A[8]),k[0]&512&&r.value!==A[9]&&ze(r,A[9]),(!E||k[0]&524416)&&n!==(n=A[20]("clear_search",A[19],A[7])+"")&&j(i,n),(!E||k[0]&512)&&K(l,"pagefind-ui__suppressed",!A[9]),A[12]?d?(d.p(A,k),k[0]&4096&&z(d,1)):(d=_u(A),d.c(),z(d,1),d.m(o,h)):d&&(le(),U(d,1,1,()=>{d=null}),ne()),A[15]?R?(R.p(A,k),k[0]&32768&&z(R,1)):(R=cu(A),R.c(),z(R,1),R.m(o,null)):R&&(le(),U(R,1,1,()=>{R=null}),ne()),(!E||k[0]&32768)&&K(o,"pagefind-ui__hidden",!A[15]),(!E||k[0]&524416&&C!==(C=A[20]("search_label",A[19],A[7])))&&g(t,"aria-label",C),(!E||k[0]&2)&&K(u,"pagefind-ui--reset",A[1])},i(A){E||(z(d),z(R),E=!0)},o(A){U(d),U(R),E=!1},d(A){A&&p(u),e[34](null),e[35](null),d&&d.d(),R&&R.d(),c=!1,W(_)}}}var Ua=e=>e.preventDefault();function Ia(e,u,t){const r={},s=Ma.map(m=>m.match(/([^\/]+)\.json$/)[1]);for(let m=0;mN[m]??H[m]??"";ds(()=>{let m=document?.querySelector?.("html")?.getAttribute?.("lang")||"en",H=Au(m.toLocaleLowerCase());t(19,He=r[`${H.language}-${H.script}-${H.region}`]||r[`${H.language}-${H.region}`]||r[`${H.language}`]||r.en)}),hs(()=>{S?.destroy?.(),S=null});const we=async()=>{if(!me&&(t(12,me=!0),!S)){let m;try{m=await is(()=>import(`${a}pagefind.js`),[])}catch(N){console.error(N),console.error([`Pagefind couldn't be loaded from ${this.options.bundlePath}pagefind.js`,"You can configure this by passing a bundlePath option to PagefindUI"].join(` +`)),document?.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"?console.error(`[DEBUG: Loaded from ${document.currentScript.src??"bad script location"}]`):console.error("no known script location")}o||t(24,o=f?12:30);let H={...d||{},excerptLength:o};await m.options(H);for(const N of R){if(!N.bundlePath)throw new Error("mergeIndex requires a bundlePath parameter");const I=N.bundlePath;delete N.bundlePath,await m.mergeIndex(I,N)}S=m,Y0()}},Y0=async()=>{S&&(Se=await S.filters(),(!ue||!Object.keys(ue).length)&&t(18,ue=Se))},X0=m=>{let H={};return Object.entries(m).filter(([,N])=>N).forEach(([N])=>{let[I,Z]=N.split(/:(.*)$/);H[I]=H[I]||[],H[I].push(Z)}),H};let te;const Q0=async(m,H)=>{if(!m){t(15,Ae=!1),te&&clearTimeout(te);return}const N=X0(H),I=()=>$0(m,N);_>0&&m?(te&&clearTimeout(te),te=setTimeout(I,_),await Ne(),S.preload(m,{filters:N})):I(),es()},Ne=async()=>{for(;!S;)we(),await new Promise(m=>setTimeout(m,50))},$0=async(m,H)=>{t(16,Me=m||""),typeof C=="function"&&(m=C(m)),t(14,ge=!0),t(15,Ae=!0),await Ne();const N=++be,I={filters:H};q&&typeof q=="object"&&(I.sort=q);const Z=await S.search(m,I);be===N&&(Z.filters&&Object.keys(Z.filters)?.length&&t(18,ue=Z.filters),t(13,De=Z),t(14,ge=!1),t(17,Re=l))},es=()=>{const m=x.offsetWidth;m!=W0&&t(10,w.style.paddingRight=`${m+2}px`,w)},us=m=>{m?.preventDefault(),t(17,Re+=l)},ts=m=>{m.key==="Escape"&&(t(9,D=""),w.blur()),m.key==="Enter"&&m.preventDefault()};function rs(){D=this.value,t(9,D),t(23,A)}function ss(m){ae[m?"unshift":"push"](()=>{w=m,t(10,w)})}function as(m){ae[m?"unshift":"push"](()=>{x=m,t(11,x)})}const ls=()=>{t(9,D=""),w.blur()};function ns(m){L=m,t(0,L)}return e.$$set=m=>{"base_path"in m&&t(25,a=m.base_path),"page_size"in m&&t(26,l=m.page_size),"reset_styles"in m&&t(1,n=m.reset_styles),"show_images"in m&&t(2,i=m.show_images),"show_sub_results"in m&&t(3,f=m.show_sub_results),"excerpt_length"in m&&t(24,o=m.excerpt_length),"process_result"in m&&t(4,h=m.process_result),"process_term"in m&&t(27,C=m.process_term),"show_empty_filters"in m&&t(5,E=m.show_empty_filters),"open_filters"in m&&t(6,c=m.open_filters),"debounce_timeout_ms"in m&&t(28,_=m.debounce_timeout_ms),"pagefind_options"in m&&t(29,d=m.pagefind_options),"merge_index"in m&&t(30,R=m.merge_index),"trigger_search_term"in m&&t(23,A=m.trigger_search_term),"translations"in m&&t(7,k=m.translations),"autofocus"in m&&t(8,y=m.autofocus),"sort"in m&&t(31,q=m.sort),"selected_filters"in m&&t(0,L=m.selected_filters)},e.$$.update=()=>{e.$$.dirty[0]&8388608&&A&&(t(9,D=A),t(23,A="")),e.$$.dirty[0]&513&&Q0(D,L)},[L,n,i,f,h,E,c,k,y,D,w,x,me,De,ge,Ae,Me,Re,ue,He,Z0,we,us,A,o,a,l,C,_,d,R,q,ts,rs,ss,as,ls,ns]}var Pa=class extends Ce{constructor(e){super(),he(this,e,Ia,Oa,fe,{base_path:25,page_size:26,reset_styles:1,show_images:2,show_sub_results:3,excerpt_length:24,process_result:4,process_term:27,show_empty_filters:5,open_filters:6,debounce_timeout_ms:28,pagefind_options:29,merge_index:30,trigger_search_term:23,translations:7,autofocus:8,sort:31,selected_filters:0},null,[-1,-1])}},La=Pa,Fe;try{document?.currentScript&&document.currentScript.tagName.toUpperCase()==="SCRIPT"&&(Fe=new URL(document.currentScript.src).pathname.match(/^(.*\/)(?:pagefind-)?ui.js.*$/)[1])}catch{Fe="/pagefind/"}var qa=class{constructor(e){this._pfs=null;let u=e.element??"[data-pagefind-ui]",t=e.bundlePath??Fe,r=e.pageSize??5,s=e.resetStyles??!0,a=e.showImages??!0,l=e.showSubResults??!1,n=e.excerptLength??0,i=e.processResult??null,f=e.processTerm??null,o=e.showEmptyFilters??!0,h=e.openFilters??[],C=e.debounceTimeoutMs??300,E=e.mergeIndex??[],c=e.translations??[],_=e.autofocus??!1,d=e.sort??null;delete e.element,delete e.bundlePath,delete e.pageSize,delete e.resetStyles,delete e.showImages,delete e.showSubResults,delete e.excerptLength,delete e.processResult,delete e.processTerm,delete e.showEmptyFilters,delete e.openFilters,delete e.debounceTimeoutMs,delete e.mergeIndex,delete e.translations,delete e.autofocus,delete e.sort;const R=u instanceof HTMLElement?u:document.querySelector(u);R?this._pfs=new La({target:R,props:{base_path:t,page_size:r,reset_styles:s,show_images:a,show_sub_results:l,excerpt_length:n,process_result:i,process_term:f,show_empty_filters:o,open_filters:h,debounce_timeout_ms:C,merge_index:E,translations:c,autofocus:_,sort:d,pagefind_options:e}}):console.error(`Pagefind UI couldn't find the selector ${u}`)}triggerSearch(e){this._pfs.$$set({trigger_search_term:e})}triggerFilters(e){let u={};for(let[t,r]of Object.entries(e))if(Array.isArray(r))for(let s of r)u[`${t}:${s}`]=!0;else u[`${t}:${r}`]=!0;this._pfs.$$set({selected_filters:u})}destroy(){this._pfs.$destroy()}};export{qa as PagefindUI}; diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs new file mode 100644 index 0000000..b69ff5d --- /dev/null +++ b/docs/astro.config.mjs @@ -0,0 +1,120 @@ +// @ts-check +import starlight from '@astrojs/starlight'; +import { defineConfig } from "astro/config"; +import AutoImport from 'astro-auto-import'; +import mdx from '@astrojs/mdx'; +import mermaid from "astro-mermaid"; +// https://astro.build/config +export default defineConfig({ + site: process.env.SITE_URL || 'https://platform9.github.io', + //base: '/vjailbreak/', + base: '/' + (process.env.BASE || ''), + trailingSlash: "always", + integrations: [ + mermaid({ + theme: 'forest', + autoTheme: true, + iconPacks: [ + { + name: 'logos', + loader: () => fetch('https://unpkg.com/@iconify-json/logos@1/icons.json').then(res => res.json()) + }, + ], + }), + AutoImport({ + imports: [ + './src/components/ReadMore.astro', + ], + }), + starlight({ + title: 'vJailbreak', + editLink: { + baseUrl: 'https://platform9.github.io/vjailbreak/', + }, + social: { + github: 'https://github.com/platform9/vjailbreak', + slack: 'https://join.slack.com/t/vjailbreak/shared_invite/zt-314pppw43-F1vzd6ZaPW5PoZqF~aa8lA', + }, + plugins: [], + components: { + Header: './src/components/Header.astro', + //SocialIcons: './src/components/githubRelease.astro', + }, + logo: { + src: './src/assets/platform9-logo.svg', + replacesTitle: true, + }, + head: [ + ], + sidebar: [ + { + label: 'Introduction', + items: [ + // Each item here is one entry in the navigation menu. + // manually done so to we can keep the order + { label: 'What is vJailbreak', slug: 'introduction/what_is_vjailbreak' }, + { label: 'Getting Started', slug: 'introduction/getting_started' }, + { label: 'Prerequisites', slug: 'introduction/prerequisites' }, + { label: 'FAQ', slug: 'introduction/faq' }, + ], + }, + { + label: 'Concepts', + items: [ + { label: 'User Credential Management', slug: 'concepts/user-management' }, + { label: 'Credential Management', slug: 'concepts/credential-management' }, + { label: 'Network Persistence', slug: 'concepts/network-persistence' }, + { label: 'Network & Storage Mapping', slug: 'concepts/network-storage-mapping' }, + { label: 'Migration Options', slug: 'concepts/migration-options' }, + { label: 'Cluster Conversion', slug: 'concepts/cluster-conversion' }, + { label: 'Storage Accelerated Copy', slug: 'concepts/storage-accelerated-copy' }, + { label: 'vJailbreak Accelerated Copy', slug: 'concepts/vjailbreak-accelerated-copy' }, + ], + }, + { + label: 'Architecture', + items: [ + { label: 'Architecture', slug: 'architecture/architecture-overview' }, + { label: 'vJailbreak VM', slug: 'architecture/vjailbreak-vm' }, + { label: 'Components', slug: 'architecture/components' }, + ], + }, + + { + label: 'Guide', + autogenerate: { directory: 'guides' }, + // items: [ + // { label: 'Scaling', slug: 'guides/How-to/scaling' }, + // { label: 'Troubleshooting', autogenerate: { directory: 'guides/troubleshooting' } }, + // { label: 'Building', slug: 'guides/How-to/building' }, + // { label: 'Using APIs', slug: 'guides/How-to/using_apis' }, + // { label: 'Injecting Custom Environment Variables', slug: 'guides/How-to/injecting_custom_env' }, + // { label: 'Debug Log Collection', slug: 'guides/How-to/debuglogs' }, + // { label: 'Debug vJailbreak Installation', slug: 'guides/How-to/debug_vjailbreak_install' }, + // { label: 'VirtIO Windows Driver Support', slug: 'guides/How-to/virtio_doc' }, + // ], + }, + { + label: 'Reference', + items: [ + { label: 'vJailbreak CRDs', slug: 'reference/reference' }, + { label: 'Compatibility Matrix', slug: 'reference/compatibility' }, + { label: 'Known Limitations', slug: 'reference/known-limitations' }, + ], + }, + { + label: 'Release Notes', + autogenerate: { directory: 'release_docs' }, + }, + { + label: 'Archives', + autogenerate: { directory: 'archives' }, + }, + ], + customCss: [ + './src/styles/custom.css' + ], + }), + mdx(), + ], +}); diff --git a/docs/favicon.svg b/docs/favicon.svg new file mode 100644 index 0000000..cba5ac1 --- /dev/null +++ b/docs/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/guides/building/index.html b/docs/guides/building/index.html new file mode 100644 index 0000000..2904498 --- /dev/null +++ b/docs/guides/building/index.html @@ -0,0 +1,147 @@ + Building vJailbreak | vJailbreak + Skip to content

Building vJailbreak

vJailbreak is intended to be run in a Kubernetes environment (k3s) on the appliance VM. In order to build and deploy the Kubernetes components, follow the instructions in k8s/migration to build and deploy the custom resources in the cluster.

+ +

In order to build v2v-helper,

+
make v2v-helper
+

In order to build migration-controller,

+
make vjail-controller
+

In order to build the UI,

+
make ui
+

Change the image names in the makefile to push to another repository.

\ No newline at end of file diff --git a/docs/guides/injecting_custom_env/index.html b/docs/guides/injecting_custom_env/index.html new file mode 100644 index 0000000..842615c --- /dev/null +++ b/docs/guides/injecting_custom_env/index.html @@ -0,0 +1,178 @@ + v2v-helper Environment Variable Injection via ConfigMap | vJailbreak + Skip to content

v2v-helper Environment Variable Injection via ConfigMap

Injecting environment variables into the v2v-helper pod is a feature that allows users to inject environment variables into the v2v-helper pod using a Kubernetes ConfigMap.

+

How It Works

+
    +
  1. +

    Cloud-init populates environment variables

    +

    Users must provide environment variables in the /etc/pf9/env file during provisioning, typically using a cloud-init script.

    +
  2. +
  3. +

    ConfigMap creation from /etc/pf9/env

    +

    A helper script or manual command reads /etc/pf9/env and creates a Kubernetes ConfigMap named pf9-env. +This is done while the vjailbreak VM is being provisioned.

    +
    Terminal window
    kubectl create configmap pf9-env --from-env-file=/etc/pf9/env -n migration-system
    +
  4. +
+

Example

+

If you want proxy variables to be injected into the v2v-helper pod, you can add the following to the /etc/pf9/env file via the cloud-init script:

+
Terminal window
http_proxy=http://<proxy-server>:<proxy-port>
https_proxy=http://<proxy-server>:<proxy-port>
no_proxy=localhost,127.0.0.1
+

You can either populate the /etc/pf9/env file via cloud-init or manually.

+

If done manually please follow the steps mentioned in Injecting Environment Variables Post-Provisioning:

+

Now this will be picked up by the v2v-helper pod and the proxy variables will be available in the pod and it would be respected by the v2v-helper pod.

+

Injecting Environment Variables Post-Provisioning

+

If you would like to inject environment variables after the vjailbreak VM has been provisioned, follow these steps:

+
    +
  1. +

    Delete the existing ConfigMap

    +
    Terminal window
    kubectl delete configmap pf9-env -n migration-system
    +
  2. +
  3. +

    Populate the /etc/pf9/env file with whatever env variables needed

    +
    Terminal window
    echo "http_proxy=http://<proxy-server>:<proxy-port>" >> /etc/pf9/env
    echo "https_proxy=http://<proxy-server>:<proxy-port>" >> /etc/pf9/env
    echo "no_proxy=localhost,127.0.0.1" >> /etc/pf9/env
    +
  4. +
  5. +

    Create a new ConfigMap

    +
    Terminal window
    kubectl create configmap pf9-env --from-env-file=/etc/pf9/env -n migration-system
    +
  6. +
  7. +

    Trigger a new migration for the envs to be reflected in the pod

    +

    Trigger via UI or via api.

    +
  8. +
\ No newline at end of file diff --git a/docs/guides/scaling/index.html b/docs/guides/scaling/index.html new file mode 100644 index 0000000..16d430c --- /dev/null +++ b/docs/guides/scaling/index.html @@ -0,0 +1,153 @@ + Scaling vJailbreak | vJailbreak + Skip to content

Scaling vJailbreak

vJailbreak can be scaled to perform multiple migrations in parallel by deploying additional agents, enabling greater efficiency and workload distribution.

+

Additional agents can be created in the Agents tab of the vJailbreak dashboard using the “Scale Up” button. You will need to choose the destination OpenStack credentials, the size of the agent VM(s), and the number of agent nodes up to a maximum of 5 per scale up. Additional agent nodes can be scaled up in batches of 5, providing the flexibility to change agent VM sizes to help with throttling network traffic.

+ +

Agent nodes can be scaled down by selecting the agent and using the “Scale Down” button.

+

To retrieve the ubuntu user’s password for SSH’ing into an agent, follow these steps:

+
    +
  • SSH into the primary vJailbreak VM and run:
  • +
+
Terminal window
cat /var/lib/rancher/k3s/server/token | cut -c 1-12
+

The first 12 characters of this token is the password for the agent VMs.

+ +

Each agent must also have a copy of the VMware VDDK libraries in their /home/ubuntu directories.

+
    +
  • Copy the latest version of the VDDK libraries for Linux into /home/ubuntu of the new agents. Untar it to a folder name vmware-vix-disklib-distrib in /home/ubuntu directory.
  • +
\ No newline at end of file diff --git a/docs/guides/troubleshooting/index.html b/docs/guides/troubleshooting/index.html new file mode 100644 index 0000000..ad36981 --- /dev/null +++ b/docs/guides/troubleshooting/index.html @@ -0,0 +1,167 @@ + Troubleshooting vJailbreak | vJailbreak + Skip to content

Troubleshooting vJailbreak

+

vJailbreak is deployed on Kubernetes running on Ubuntu 22.04.5, and distributed as a QCOW2 image. The Kubernetes namespace migration-system contains the vJailbreak UI and migration controller pods. Each VM migration will spawn a migration object. The status field contains a high level view of the progress of the migration of the VM. For more details about the migration, check the logs of the pod specified in the Migration object.

+

Getting logs

+

List all pods in the migration namespace

+
Terminal window
kubectl -n migration-system get pod
+

Find a specific VM migration pod

+
Terminal window
kubectl -n migration-system get pod | grep <source VM name>
+

Get details & events for a v2v-helper pod. This is helpful if a migration is stuck in a pending state, or to track the progress of a migration without the UI.

+
Terminal window
kubectl -n migration-system describe pod <v2v-helper-pod-name>
+

Get logs for a specific migration pod. This shows more detail than describe pod.

+
Terminal window
kubectl logs <pod> -n migration-system
+

Get logs for the migration-controller-manager

+
Terminal window
kubectl logs -n migration-system deploy/migration-controller-manager
+

Turn on Debug Mode

+
Terminal window
kubectl patch configmap -n migration-system migration-config-<vm-name> --type merge -p '{"data":{"DEBUG":"true"}}'
+

A migration is stuck in Pending

+

If the migration was set to Retry on Failure, then delete the v2v-helper pod for that VM and collect the logs of the pod that comes up.

+
Terminal window
kubectl delete pod -n migration-system v2v-helper-<vm-name>
+

If the v2v-helper pod doesn’t come back up, and you can’t delete the migration in the UI, then delete the associated migrationplan.

+
    +
  • First, get the migrationplan object name UUID for the associated VMs:
  • +
+
Terminal window
kubectl get migrationplans -n migration-system -o yaml
+
    +
  • Then delete the migrationplan object, which should remove it from the UI.
  • +
+
Terminal window
kubectl delete migrationplan <UUID> -n migration-system
+

Get all vJailbreak custom resource definitions (CRDs)

+
Terminal window
kubectl get migrationplans,migrations,migrationtemplates,networkmappings,openstackcreds,storagemappings,vmwarecreds,secrets -n migration-system -o yaml
\ No newline at end of file diff --git a/docs/guides/using_apis/index.html b/docs/guides/using_apis/index.html new file mode 100644 index 0000000..8d26e8e --- /dev/null +++ b/docs/guides/using_apis/index.html @@ -0,0 +1,139 @@ + Using vJailbreak via APIs | vJailbreak + Skip to content
\ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..4401cd0 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,168 @@ + vJailbreak | vJailbreak; + + Skip to content
+
+ +
+
+

vJailbreak

Effortless Open-Source VMware to OpenStack Migration
+

Download the latest Version

+
  1. Install ORAS

    brew install oras
  2. Download Image

    oras pull quay.io/platform9/vjailbreak:v0.1.9
  3. Get PCD

    https://platform9.com/private-cloud-director/
+

Enterprise VM Migration: VMware to OpenStack, Simplified

vJailbreak is an open-source tool featuring a user-friendly interface +designed to simplify and accelerate the migration of virtual machines (VMs) +from VMware vSphere environments to any OpenStack-compliant cloud. It +eliminates the complexities of cross-platform VM migration, enabling you to +modernize your infrastructure with minimal disruption and a streamlined, +visual workflow.

+

Key Benefits

+

Visual Interface

Intuitive UI with no command-line requirements for seamless VM migration

Fast & Efficient

Automated tasks with real-time progress tracking reduce migration time

Zero Disruption

Maintain source environment operations with minimal downtime

Cost Effective

Open-source solution eliminating expensive licensing costs

OpenStack Ready

Compatible with any standard OpenStack-compliant cloud platform

Step-by-Step Guide

Guided workflow with automated health checks and validation

+

Get Started

+

Ready to experience simplified, UI-driven VM migration?

+

Deploy vJailbreak

Deploy vJailbreak and connect it to your vSphere and OpenStack environments. +Learn more about installation, configuration, and usage in the vJailbreak +Documentation.

Contribute to vJailbreak

Help improve vJailbreak by contributing code, documentation, or bug reports. +View our contribution +guidelines +to get started.

+
\ No newline at end of file diff --git a/docs/introduction/components/index.html b/docs/introduction/components/index.html new file mode 100644 index 0000000..bbca492 --- /dev/null +++ b/docs/introduction/components/index.html @@ -0,0 +1,148 @@ + vJailbreak Components | vJailbreak + Skip to content

vJailbreak Components

vJailbreak is composed of several key components that work together to facilitate the migration of virtual machines from VMware environments to OpenStack-compliant clouds. Below is an overview of each component and its role in the migration process.

+

v2v-helper

+

The v2v-helper is the main application responsible for executing the migration process. It is designed to run as a pod within the vJailbreak virtual machine (VM) in the target OpenStack environment.

+

UI

+

The UI component provides a user-friendly interface for vJailbreak. It allows users to manage and monitor the migration process through an intuitive graphical interface.

+

migration-controller

+

The migration-controller is a Kubernetes controller that schedules and manages the migration tasks. It ensures that migrations are executed efficiently and in accordance with the defined policies.

+

v2v-cli

+

The v2v-cli is a command-line interface tool that can initiate the migration process. While it is available, it is not required in the current version of vJailbreak, as the primary interface is the UI.

+

By understanding these components, users can better appreciate the architecture and functionality of vJailbreak, enabling them to effectively manage and execute VM migrations.

\ No newline at end of file diff --git a/docs/introduction/getting_started/index.html b/docs/introduction/getting_started/index.html new file mode 100644 index 0000000..5bf52aa --- /dev/null +++ b/docs/introduction/getting_started/index.html @@ -0,0 +1,171 @@ + Getting Started | vJailbreak + Skip to content

Getting Started

Network and access requirements

+

Ensure that your vJailbreak VM can communicate with your OpenStack and VMware environments. This includes any setup required for VPNs, etc.

+
Further details can be found in Prerequisites.
+

Install ORAS and download vJailbreak

+

Download and install ORAS. Then, download the latest version of the vJailbreak image with the following command:

+
Terminal window
oras pull quay.io/platform9/vjailbreak:v0.1.9
+

This will download the vJailbreak qcow2 folder containing the image locally in the current directory named vjailbreak_qcow2/vjailbreak-image.qcow2.

+

Upload image and create vJailbreak VM

+ +
    +
  • Follow the instructions in Private Cloud Director > Images > Import with CLI to upload the image from the command line.
  • +
  • Upload vjailbreak-image.qcow2 to your image library. +
    Terminal window
    openstack image create --os-interface admin --insecure --container-format bare --disk-format qcow2 --file vjailbreak-image.qcow2 vjailbreak-image.qcow2
    +
  • +
  • Deploy a new VM from image, choosing the m1.xlarge flavor.
  • +
  • Choose a network that can reach your VMware vCenter environment.
  • +
  • Give the VM a name, optionally assign an SSH key, and set a password using cloud-init.
  • +
  • Assign a network security group that allows inbound and outbound traffic.
  • +
+

Copy VDDK Libraries

+
    +
  • Copy the latest version of the VDDK libraries for Linux into /home/ubuntu of the vJailbreak VM. Untar it to a folder named vmware-vix-disklib-distrib in the /home/ubuntu directory.
  • +
+

Launch vJailbreak

+
    +
  • Connect to the vJailbreak UI using the IP address assigned during VM creation.
  • +
  • Start the migration process by providing the VMware vCenter and OpenStack admin.rc credentials.
  • +
  • Select the VMs you wish to migrate and complete the rest of the migration form.
  • +
  • Migrate your VMs.
  • +
+

Scaling vJailbreak

+
Read more about scaling vJailbreak.
\ No newline at end of file diff --git a/docs/introduction/prerequisites/index.html b/docs/introduction/prerequisites/index.html new file mode 100644 index 0000000..4f3e91b --- /dev/null +++ b/docs/introduction/prerequisites/index.html @@ -0,0 +1,383 @@ + FAQ & Prerequisites | vJailbreak + Skip to content

FAQ & Prerequisites

Are IPs and MAC addresses persisted?

+

Yes, if your OpenStack network has a valid subnet range that allows the IP to be allocated, vJailbreak will create a port with the same MAC address and IP address as the source VM.

+

What OS versions are supported?

+

We internally use virt-v2v, so all operating systems supported for conversion by virt-v2v are supported by vJailbreak. You can find a detailed list of them here.

+

Do I need to perform any manual steps to remove VMware Tools?

+

No, vJailbreak will remove them for you, with the help of virt-v2v. The process that virt-v2v uses along with alternative approaches can be found here.

+

Do I need to perform any manual steps to install drivers for Linux and Windows VMs?

+

No, vJailbreak will install it for you. For Windows, we allow you to specify a URL for a specific version of virtio drivers. This is useful for older Windows versions, eg. Windows Server 2012, which specifically need v0.1.189 in order to work.

+

What access do I need for my vCenter user to be able to perform this migration?

+

Please refer to the following table for the required privileges:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PrivilegeDescription
Virtual machine.Interaction privileges:
Virtual machine.Interaction.Power OffAllows powering off a powered-on virtual machine. This operation powers down the guest operating system.
Virtual machine.Interaction.Power OnAllows powering on a powered-off virtual machine and resuming a suspended virtual machine.
Virtual machine.Guest operating system management by VIX APIAllows managing a virtual machine by the VMware VIX API.
Virtual machine.ProvisioningNote: All Virtual machine.Provisioning privileges are required.
Virtual machine.Provisioning.Allow disk accessAllows opening a disk on a virtual machine for random read and write access. Used mostly for remote disk mounting.
Virtual machine.Provisioning.Allow file accessAllows operations on files associated with a virtual machine, including VMX, disks, logs, and NVRAM.
Virtual machine.Provisioning.Allow read-only disk accessAllows opening a disk on a virtual machine for random read access. Used mostly for remote disk mounting.
Virtual machine.Provisioning.Allow virtual machine downloadAllows read operations on files associated with a virtual machine, including VMX, disks, logs, and NVRAM.
Virtual machine.Provisioning.Allow virtual machine files uploadAllows write operations on files associated with a virtual machine, including VMX, disks, logs, and NVRAM.
Virtual machine.Provisioning.Clone templateAllows cloning of a template.
Virtual machine.Provisioning.Clone virtual machineAllows cloning of an existing virtual machine and allocation of resources.
Virtual machine.Provisioning.Create template from virtual machineAllows creation of a new template from a virtual machine.
Virtual machine.Provisioning.Customize guestAllows customization of a virtual machine’s guest operating system without moving the virtual machine.
Virtual machine.Provisioning.Deploy templateAllows deployment of a virtual machine from a template.
Virtual machine.Provisioning.Mark as templateAllows marking an existing powered-off virtual machine as a template.
Virtual machine.Provisioning.Mark as virtual machineAllows marking an existing template as a virtual machine.
Virtual machine.Provisioning.Modify customization specificationAllows creation, modification, or deletion of customization specifications.
Virtual machine.Provisioning.Promote disksAllows promote operations on a virtual machine’s disks.
Virtual machine.Provisioning.Read customization specificationsAllows reading a customization specification.
Virtual machine.Snapshot management privileges:
Virtual machine.Snapshot management.Create snapshotAllows creation of a snapshot from the virtual machine’s current state.
Virtual machine.Snapshot management.Remove SnapshotAllows removal of a snapshot from the snapshot history.
Datastore privileges:
Datastore.Browse datastoreAllows exploring the contents of a datastore.
Datastore.Low level file operationsAllows performing low-level file operations - read, write, delete, and rename - in a datastore.
Sessions privileges:
Sessions.Validate sessionAllows verification of the validity of a session.
Cryptographic privileges:
Cryptographic.DecryptAllows decryption of an encrypted virtual machine.
Cryptographic.Direct accessAllows access to encrypted resources.
+

What ports do I need to open for vJailbreak to work?

+

Please refer the following table for the required ports:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PortProtocolSourceDestinationPurpose
443TCPPCD nodesVMware vCenter API endpiontVMware provider inventory

Disk transfer authentication
443TCPPCD nodesVMware ESXi hostsDisk transfer authentication
902TCPPCD nodesVMware ESXi hostsDisk transfer data copy
5480TCPPCD nodesVMware vCenter API endpointVMware Site Recovery Manager Appliance Management Interface
+

What network connectivity do I need for vJailbreak?

+

The vJailbreak VM and any helper nodes must be able to resolve & connect to your VMware vCenter environment and all ESXi hosts, and must be able to resolve & connect to quay.io.

+

Required Ingress Rules for Kubernetes Node with Kubelet, Metrics Server, and Prometheus

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ComponentPortProtocolSourcePurpose
Kubelet API10250TCPControl Plane / PrometheusHealth checks, logs, metrics
Kubelet Read-Only (Optional)10255TCPInternal OnlyDeprecated but might be used in some cases
Metrics Server4443TCPInternal ClusterK8s resource metrics (kubectl top)
Prometheus9090TCPInternal Cluster / Monitoring ServerPrometheus UI and API
Node Exporter (if used)9100TCPPrometheusNode-level metrics
Cadvisor (Optional)4194TCPInternal Cluster / PrometheusContainer metrics collection
\ No newline at end of file diff --git a/docs/introduction/what_is_vjailbreak/index.html b/docs/introduction/what_is_vjailbreak/index.html new file mode 100644 index 0000000..e85ba5b --- /dev/null +++ b/docs/introduction/what_is_vjailbreak/index.html @@ -0,0 +1,167 @@ + What is vJailbreak? | vJailbreak + Skip to content

What is vJailbreak?

vJailbreak is an open-source tool featuring a user-friendly interface designed to simplify and accelerate the migration of virtual machines (VMs) from VMware vSphere environments to any OpenStack-compliant cloud. It eliminates the complexities of cross-platform VM migration, enabling you to modernize your infrastructure with minimal disruption and a streamlined, visual workflow.

+

How vJailbreak Works

+

vJailbreak’s intuitive interface leverages the OpenStack & VMware SDKs to interact directly with both your VMware vSphere environment and your target OpenStack cloud. The UI guides you through these key steps:

+
    +
  1. Connection Setup: Easily configure connections to your source VMware vSphere environment and your target OpenStack cloud.
  2. +
  3. VM Selection: Select the VMs you wish to migrate from your vSphere environment.
  4. +
  5. Migration Planning: Configure migration settings, such as target storage and network configurations, through interactive forms.
  6. +
  7. Migration Execution: Initiate and monitor the migration process with real-time progress updates.
  8. +
  9. Post-Migration Validation: Verify the successful migration and launch of your VMs in OpenStack.
  10. +
+

Key Features

+
    +
  • Intuitive User Interface: Manage the entire migration process through a clear, easy-to-use graphical interface – no command-line expertise required.
  • +
  • Seamless vCenter Integration: Easily connect to your VMware vCenter to manage and migrate VMs.
  • +
  • Effortless VM Selection: Select the virtual machines you wish to migrate with just a few clicks.
  • +
  • Automated Disk Conversion: VM disks are automatically converted from vmdk to qcow2 format.
  • +
  • Driver and Device Installation: Necessary virtual devices and drivers are installed to ensure smooth operation post-migration.
  • +
  • Post-Migration Health Checks: Comprehensive health checks are performed to verify the success of the migration and the operational status of the VMs in the new environment.
  • +
+

Key Benefits

+
    +
  • Reduced Migration Time: Automate migration tasks and visualize progress, significantly reducing the time and effort compared to manual methods.
  • +
  • Minimized Downtime: vJailbreak’s efficient migration process helps minimize downtime for your critical workloads.
  • +
  • Cost-Effective Solution: As an open-source tool, vJailbreak eliminates licensing costs.
  • +
  • Broad Compatibility: Migrate VMs to any OpenStack cloud that adheres to standard OpenStack APIs.
  • +
  • Non-Disruptive Migration: Perform migrations without impacting the operation of your source VMware environment.
  • +
  • Visual Progress Tracking: Monitor the status of your migrations in real-time through the user interface.
  • +
+
\ No newline at end of file diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 0000000..15a345b --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,8258 @@ +{ + "name": "docs", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "version": "0.0.1", + "dependencies": { + "@astrojs/mdx": "^4.2.1", + "@astrojs/starlight": "^0.32.1", + "astro": "^5.1.5", + "astro-auto-import": "^0.4.4", + "astro-mermaid": "^1.0.4", + "mermaid": "^11.9.0", + "sharp": "^0.32.5" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.6.1" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/install-pkg/node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "license": "MIT" + }, + "node_modules/@antfu/utils": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.1.tgz", + "integrity": "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@astrojs/compiler": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/@astrojs/compiler/-/compiler-2.12.2.tgz", + "integrity": "sha512-w2zfvhjNCkNMmMMOn5b0J8+OmUaBL1o40ipMvqcG6NRpdC+lKxmTi48DT8Xw0SzJ3AfmeFLB45zXZXtmbsjcgw==", + "license": "MIT" + }, + "node_modules/@astrojs/internal-helpers": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@astrojs/internal-helpers/-/internal-helpers-0.7.1.tgz", + "integrity": "sha512-7dwEVigz9vUWDw3nRwLQ/yH/xYovlUA0ZD86xoeKEBmkz9O6iELG1yri67PgAPW6VLL/xInA4t7H0CK6VmtkKQ==", + "license": "MIT" + }, + "node_modules/@astrojs/markdown-remark": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@astrojs/markdown-remark/-/markdown-remark-6.3.5.tgz", + "integrity": "sha512-MiR92CkE2BcyWf3b86cBBw/1dKiOH0qhLgXH2OXA6cScrrmmks1Rr4Tl0p/lFpvmgQQrP54Pd1uidJfmxGrpWQ==", + "license": "MIT", + "dependencies": { + "@astrojs/internal-helpers": "0.7.1", + "@astrojs/prism": "3.3.0", + "github-slugger": "^2.0.0", + "hast-util-from-html": "^2.0.3", + "hast-util-to-text": "^4.0.2", + "import-meta-resolve": "^4.1.0", + "js-yaml": "^4.1.0", + "mdast-util-definitions": "^6.0.0", + "rehype-raw": "^7.0.0", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-smartypants": "^3.0.2", + "shiki": "^3.2.1", + "smol-toml": "^1.3.4", + "unified": "^11.0.5", + "unist-util-remove-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1", + "vfile": "^6.0.3" + } + }, + "node_modules/@astrojs/mdx": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@astrojs/mdx/-/mdx-4.3.3.tgz", + "integrity": "sha512-+9+xGP2TBXxcm84cpiq4S9JbuHOHM1fcvREfqW7VHxlUyfUQPByoJ9YYliqHkLS6BMzG+O/+o7n8nguVhuEv4w==", + "license": "MIT", + "dependencies": { + "@astrojs/markdown-remark": "6.3.5", + "@mdx-js/mdx": "^3.1.0", + "acorn": "^8.14.1", + "es-module-lexer": "^1.6.0", + "estree-util-visit": "^2.0.0", + "hast-util-to-html": "^9.0.5", + "kleur": "^4.1.5", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1", + "remark-smartypants": "^3.0.2", + "source-map": "^0.7.4", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.3" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + }, + "peerDependencies": { + "astro": "^5.0.0" + } + }, + "node_modules/@astrojs/prism": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/prism/-/prism-3.3.0.tgz", + "integrity": "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==", + "license": "MIT", + "dependencies": { + "prismjs": "^1.30.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@astrojs/sitemap": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.4.2.tgz", + "integrity": "sha512-wfN2dZzdkto6yaMtOFa/J9gc60YE3wl3rgSBoNJ+MU3lJVUMsDY9xf9uAVi8Mp/zEQKFDSJlQzBvqQUpw0Hf6g==", + "license": "MIT", + "dependencies": { + "sitemap": "^8.0.0", + "stream-replace-string": "^2.0.0", + "zod": "^3.24.4" + } + }, + "node_modules/@astrojs/starlight": { + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/@astrojs/starlight/-/starlight-0.32.6.tgz", + "integrity": "sha512-ASWGwNzq+0TmJ+GJFFxFFxx6Yra7BqIIMQbvOy/cweTHjqejB6mcaEWtS3Mag12LM7tXCES7v/fzmdPgjz8Yxw==", + "license": "MIT", + "dependencies": { + "@astrojs/mdx": "^4.0.5", + "@astrojs/sitemap": "^3.2.1", + "@pagefind/default-ui": "^1.3.0", + "@types/hast": "^3.0.4", + "@types/js-yaml": "^4.0.9", + "@types/mdast": "^4.0.4", + "astro-expressive-code": "^0.40.0", + "bcp-47": "^2.1.0", + "hast-util-from-html": "^2.0.1", + "hast-util-select": "^6.0.2", + "hast-util-to-string": "^3.0.0", + "hastscript": "^9.0.0", + "i18next": "^23.11.5", + "js-yaml": "^4.1.0", + "klona": "^2.0.6", + "mdast-util-directive": "^3.0.0", + "mdast-util-to-markdown": "^2.1.0", + "mdast-util-to-string": "^4.0.0", + "pagefind": "^1.3.0", + "rehype": "^13.0.1", + "rehype-format": "^5.0.0", + "remark-directive": "^3.0.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.2" + }, + "peerDependencies": { + "astro": "^5.1.5" + } + }, + "node_modules/@astrojs/telemetry": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.0.tgz", + "integrity": "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==", + "license": "MIT", + "dependencies": { + "ci-info": "^4.2.0", + "debug": "^4.4.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "is-docker": "^3.0.0", + "is-wsl": "^3.1.0", + "which-pm-runs": "^1.1.0" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.2.tgz", + "integrity": "sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", + "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==", + "license": "MIT" + }, + "node_modules/@capsizecss/unpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@capsizecss/unpack/-/unpack-2.4.0.tgz", + "integrity": "sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q==", + "license": "MIT", + "dependencies": { + "blob-to-buffer": "^1.2.8", + "cross-fetch": "^3.0.4", + "fontkit": "^2.0.2" + } + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "license": "Apache-2.0" + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.1.0.tgz", + "integrity": "sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@expressive-code/core": { + "version": "0.40.2", + "resolved": "https://registry.npmjs.org/@expressive-code/core/-/core-0.40.2.tgz", + "integrity": "sha512-gXY3v7jbgz6nWKvRpoDxK4AHUPkZRuJsM79vHX/5uhV9/qX6Qnctp/U/dMHog/LCVXcuOps+5nRmf1uxQVPb3w==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.0.4", + "hast-util-select": "^6.0.2", + "hast-util-to-html": "^9.0.1", + "hast-util-to-text": "^4.0.1", + "hastscript": "^9.0.0", + "postcss": "^8.4.38", + "postcss-nested": "^6.0.1", + "unist-util-visit": "^5.0.0", + "unist-util-visit-parents": "^6.0.1" + } + }, + "node_modules/@expressive-code/plugin-frames": { + "version": "0.40.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-frames/-/plugin-frames-0.40.2.tgz", + "integrity": "sha512-aLw5IlDlZWb10Jo/TTDCVsmJhKfZ7FJI83Zo9VDrV0OBlmHAg7klZqw68VDz7FlftIBVAmMby53/MNXPnMjTSQ==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.40.2" + } + }, + "node_modules/@expressive-code/plugin-shiki": { + "version": "0.40.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-shiki/-/plugin-shiki-0.40.2.tgz", + "integrity": "sha512-t2HMR5BO6GdDW1c1ISBTk66xO503e/Z8ecZdNcr6E4NpUfvY+MRje+LtrcvbBqMwWBBO8RpVKcam/Uy+1GxwKQ==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.40.2", + "shiki": "^1.26.1" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/core": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.29.2.tgz", + "integrity": "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ==", + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "1.29.2", + "@shikijs/engine-oniguruma": "1.29.2", + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/engine-javascript": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-1.29.2.tgz", + "integrity": "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1", + "oniguruma-to-es": "^2.2.0" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/engine-oniguruma": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz", + "integrity": "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/langs": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-1.29.2.tgz", + "integrity": "sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.29.2" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/themes": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-1.29.2.tgz", + "integrity": "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "1.29.2" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/@shikijs/types": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-1.29.2.tgz", + "integrity": "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.1", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/oniguruma-to-es": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz", + "integrity": "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==", + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^5.1.1", + "regex-recursion": "^5.1.1" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/regex": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-5.1.1.tgz", + "integrity": "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/regex-recursion": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-5.1.1.tgz", + "integrity": "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==", + "license": "MIT", + "dependencies": { + "regex": "^5.1.1", + "regex-utilities": "^2.3.0" + } + }, + "node_modules/@expressive-code/plugin-shiki/node_modules/shiki": { + "version": "1.29.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.29.2.tgz", + "integrity": "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "1.29.2", + "@shikijs/engine-javascript": "1.29.2", + "@shikijs/engine-oniguruma": "1.29.2", + "@shikijs/langs": "1.29.2", + "@shikijs/themes": "1.29.2", + "@shikijs/types": "1.29.2", + "@shikijs/vscode-textmate": "^10.0.1", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@expressive-code/plugin-text-markers": { + "version": "0.40.2", + "resolved": "https://registry.npmjs.org/@expressive-code/plugin-text-markers/-/plugin-text-markers-0.40.2.tgz", + "integrity": "sha512-/XoLjD67K9nfM4TgDlXAExzMJp6ewFKxNpfUw4F7q5Ecy+IU3/9zQQG/O70Zy+RxYTwKGw2MA9kd7yelsxnSmw==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.40.2" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.3.0.tgz", + "integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.0.0", + "@antfu/utils": "^8.1.0", + "@iconify/types": "^2.0.0", + "debug": "^4.4.0", + "globals": "^15.14.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.0.0", + "mlly": "^1.7.4" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "license": "MIT" + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz", + "integrity": "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mermaid-js/parser": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.2.tgz", + "integrity": "sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ==", + "license": "MIT", + "dependencies": { + "langium": "3.3.1" + } + }, + "node_modules/@oslojs/encoding": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", + "integrity": "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==", + "license": "MIT" + }, + "node_modules/@pagefind/darwin-arm64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-arm64/-/darwin-arm64-1.3.0.tgz", + "integrity": "sha512-365BEGl6ChOsauRjyVpBjXybflXAOvoMROw3TucAROHIcdBvXk9/2AmEvGFU0r75+vdQI4LJdJdpH4Y6Yqaj4A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/darwin-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@pagefind/darwin-x64/-/darwin-x64-1.3.0.tgz", + "integrity": "sha512-zlGHA23uuXmS8z3XxEGmbHpWDxXfPZ47QS06tGUq0HDcZjXjXHeLG+cboOy828QIV5FXsm9MjfkP5e4ZNbOkow==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pagefind/default-ui": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@pagefind/default-ui/-/default-ui-1.3.0.tgz", + "integrity": "sha512-CGKT9ccd3+oRK6STXGgfH+m0DbOKayX6QGlq38TfE1ZfUcPc5+ulTuzDbZUnMo+bubsEOIypm4Pl2iEyzZ1cNg==", + "license": "MIT" + }, + "node_modules/@pagefind/linux-arm64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-arm64/-/linux-arm64-1.3.0.tgz", + "integrity": "sha512-8lsxNAiBRUk72JvetSBXs4WRpYrQrVJXjlRRnOL6UCdBN9Nlsz0t7hWstRk36+JqHpGWOKYiuHLzGYqYAqoOnQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/linux-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@pagefind/linux-x64/-/linux-x64-1.3.0.tgz", + "integrity": "sha512-hAvqdPJv7A20Ucb6FQGE6jhjqy+vZ6pf+s2tFMNtMBG+fzcdc91uTw7aP/1Vo5plD0dAOHwdxfkyw0ugal4kcQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pagefind/windows-x64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@pagefind/windows-x64/-/windows-x64-1.3.0.tgz", + "integrity": "sha512-BR1bIRWOMqkf8IoU576YDhij1Wd/Zf2kX/kCI0b2qzCKC8wcc2GQJaaRMCpzvCCrmliO4vtJ6RITp/AnoYUUmQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/pluginutils": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz", + "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.6.1.tgz", + "integrity": "sha512-DNGZvZDO5YF7jN5fX8ZqmGLjZEXIJRdJEdTFMhiyXqyXubBa0WVLDWSNlQ5JR2PNgDbEV1VQowhVRUh+74D+RA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.9.2.tgz", + "integrity": "sha512-3q/mzmw09B2B6PgFNeiaN8pkNOixWS726IHmJEpjDAcneDPMQmUg2cweT9cWXY4XcyQS3i6mOOUgQz9RRUP6HA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.9.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.9.2.tgz", + "integrity": "sha512-kUTRVKPsB/28H5Ko6qEsyudBiWEDLst+Sfi+hwr59E0GLHV0h8RfgbQU7fdN5Lt9A8R1ulRiZyTvAizkROjwDA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.9.2", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.9.2.tgz", + "integrity": "sha512-Vn/w5oyQ6TUgTVDIC/BrpXwIlfK6V6kGWDVVz2eRkF2v13YoENUvaNwxMsQU/t6oCuZKzqp9vqtEtEzKl9VegA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.9.2", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.9.2.tgz", + "integrity": "sha512-X1Q6wRRQXY7HqAuX3I8WjMscjeGjqXCg/Sve7J2GWFORXkSrXud23UECqTBIdCSNKJioFtmUGJQNKtlMMZMn0w==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.9.2" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.9.2.tgz", + "integrity": "sha512-6z5lBPBMRfLyyEsgf6uJDHPa6NAGVzFJqH4EAZ+03+7sedYir2yJBRu2uPZOKmj43GyhVHWHvyduLDAwJQfDjA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.9.2" + } + }, + "node_modules/@shikijs/types": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.9.2.tgz", + "integrity": "sha512-/M5L0Uc2ljyn2jKvj4Yiah7ow/W+DJSglVafvWAJ/b8AZDeeRAdMu3c2riDzB7N42VD+jSnWxeP9AKtd4TfYVw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/fontkit": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@types/fontkit/-/fontkit-2.0.8.tgz", + "integrity": "sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/nlcst": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/nlcst/-/nlcst-2.0.3.tgz", + "integrity": "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/node": { + "version": "18.19.121", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.121.tgz", + "integrity": "sha512-bHOrbyztmyYIi4f1R0s17QsPs1uyyYnGcXeZoGEd227oZjry0q6XQBQxd82X1I57zEfwO8h9Xo+Kl5gX1d9MwQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-iterate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/array-iterate/-/array-iterate-2.0.1.tgz", + "integrity": "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/astro": { + "version": "5.12.8", + "resolved": "https://registry.npmjs.org/astro/-/astro-5.12.8.tgz", + "integrity": "sha512-KkJ7FR+c2SyZYlpakm48XBiuQcRsrVtdjG5LN5an0givI/tLik+ePJ4/g3qrAVhYMjJOxBA2YgFQxANPiWB+Mw==", + "license": "MIT", + "dependencies": { + "@astrojs/compiler": "^2.12.2", + "@astrojs/internal-helpers": "0.7.1", + "@astrojs/markdown-remark": "6.3.5", + "@astrojs/telemetry": "3.3.0", + "@capsizecss/unpack": "^2.4.0", + "@oslojs/encoding": "^1.1.0", + "@rollup/pluginutils": "^5.1.4", + "acorn": "^8.14.1", + "aria-query": "^5.3.2", + "axobject-query": "^4.1.0", + "boxen": "8.0.1", + "ci-info": "^4.2.0", + "clsx": "^2.1.1", + "common-ancestor-path": "^1.0.1", + "cookie": "^1.0.2", + "cssesc": "^3.0.0", + "debug": "^4.4.0", + "deterministic-object-hash": "^2.0.2", + "devalue": "^5.1.1", + "diff": "^5.2.0", + "dlv": "^1.1.3", + "dset": "^3.1.4", + "es-module-lexer": "^1.6.0", + "esbuild": "^0.25.0", + "estree-walker": "^3.0.3", + "flattie": "^1.1.1", + "fontace": "~0.3.0", + "github-slugger": "^2.0.0", + "html-escaper": "3.0.3", + "http-cache-semantics": "^4.1.1", + "import-meta-resolve": "^4.1.0", + "js-yaml": "^4.1.0", + "kleur": "^4.1.5", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "mrmime": "^2.0.1", + "neotraverse": "^0.6.18", + "p-limit": "^6.2.0", + "p-queue": "^8.1.0", + "package-manager-detector": "^1.1.0", + "picomatch": "^4.0.2", + "prompts": "^2.4.2", + "rehype": "^13.0.2", + "semver": "^7.7.1", + "shiki": "^3.2.1", + "smol-toml": "^1.3.4", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.12", + "tsconfck": "^3.1.5", + "ultrahtml": "^1.6.0", + "unifont": "~0.5.0", + "unist-util-visit": "^5.0.0", + "unstorage": "^1.15.0", + "vfile": "^6.0.3", + "vite": "^6.3.4", + "vitefu": "^1.0.6", + "xxhash-wasm": "^1.1.0", + "yargs-parser": "^21.1.1", + "yocto-spinner": "^0.2.1", + "zod": "^3.24.4", + "zod-to-json-schema": "^3.24.5", + "zod-to-ts": "^1.2.0" + }, + "bin": { + "astro": "astro.js" + }, + "engines": { + "node": "18.20.8 || ^20.3.0 || >=22.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/astrodotbuild" + }, + "optionalDependencies": { + "sharp": "^0.33.3" + } + }, + "node_modules/astro-auto-import": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/astro-auto-import/-/astro-auto-import-0.4.4.tgz", + "integrity": "sha512-tiYe1hp+VusdiyaD3INgZgbvXEPamDFiURnQR5Niz+E9fWa6IHYjJ99TwGlHh/evfaXE/U/86jp9MRKWTuJU1A==", + "license": "MIT", + "dependencies": { + "@types/node": "^18.0.0", + "acorn": "^8.8.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "astro": "^2.0.0 || ^3.0.0-beta || ^4.0.0-beta || ^5.0.0-beta" + } + }, + "node_modules/astro-expressive-code": { + "version": "0.40.2", + "resolved": "https://registry.npmjs.org/astro-expressive-code/-/astro-expressive-code-0.40.2.tgz", + "integrity": "sha512-yJMQId0yXSAbW9I6yqvJ3FcjKzJ8zRL7elbJbllkv1ZJPlsI0NI83Pxn1YL1IapEM347EvOOkSW2GL+2+NO61w==", + "license": "MIT", + "dependencies": { + "rehype-expressive-code": "^0.40.2" + }, + "peerDependencies": { + "astro": "^4.0.0-beta || ^5.0.0-beta || ^3.3.0" + } + }, + "node_modules/astro-mermaid": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/astro-mermaid/-/astro-mermaid-1.0.4.tgz", + "integrity": "sha512-2M4bVjqLpDB2EZ4EfD6Utzs7VEEORmlt5hNZcMK54IcKWzflohKvowCzg79RHoAdu30W8a4aECAExH8mF7wG4w==", + "license": "MIT", + "dependencies": { + "mdast-util-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "peerDependencies": { + "astro": "^4.0.0 || ^5.0.0", + "mermaid": "^10.0.0 || ^11.0.0" + } + }, + "node_modules/astro/node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "license": "Apache-2.0" + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bare-events": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.6.0.tgz", + "integrity": "sha512-EKZ5BTXYExaNqi3I3f9RtEsaI/xBSGjE0XZCZilPzFAV/goswFHuPd9jEZlPIZ/iNZJwDSao9qRiScySz7MbQg==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.6.tgz", + "integrity": "sha512-25RsLF33BqooOEFNdMcEhMpJy8EoR88zSMrnOQOaM3USnOK2VmaJ1uaQEwPA6AQjrv1lXChScosN6CzbwbO9OQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", + "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/blob-to-buffer": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/blob-to-buffer/-/blob-to-buffer-1.2.9.tgz", + "integrity": "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/boxen": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", + "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^8.0.0", + "chalk": "^5.3.0", + "cli-boxes": "^3.0.0", + "string-width": "^7.2.0", + "type-fest": "^4.21.0", + "widest-line": "^5.0.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.5.0.tgz", + "integrity": "sha512-1tm8DTaJhPBG3bIkVeZt1iZM9GfSX2lzOeDVZH9R9ffRHpmHvxZ/QhgQH/aDTkswQVt+YHdXAdS/In/30OjCbg==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/ci-info": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "license": "ISC" + }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cookie-es": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "license": "MIT" + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, + "node_modules/css-selector-parser": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.1.3.tgz", + "integrity": "sha512-gJMigczVZqYAk0hPVzx/M4Hm1D9QOtqkdQk9005TNzDIUGzo5cnHEDiKUT7jGPximL/oYb+LIitcHFQ4aKupxg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cytoscape": { + "version": "3.33.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.0.tgz", + "integrity": "sha512-2d2EwwhaxLWC8ahkH1PpQwCyu6EY3xDRdcEJXrLTb4fOUtVc+YWQalHU67rFS1a6ngj1fgv9dQLtJxP/KAFZEw==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.11.tgz", + "integrity": "sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/deterministic-object-hash": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/deterministic-object-hash/-/deterministic-object-hash-2.0.2.tgz", + "integrity": "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==", + "license": "MIT", + "dependencies": { + "base-64": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/devalue": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", + "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "license": "MIT", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "license": "MIT" + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "license": "MIT" + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/expressive-code": { + "version": "0.40.2", + "resolved": "https://registry.npmjs.org/expressive-code/-/expressive-code-0.40.2.tgz", + "integrity": "sha512-1zIda2rB0qiDZACawzw2rbdBQiWHBT56uBctS+ezFe5XMAaFaHLnnSYND/Kd+dVzO9HfCXRDpzH3d+3fvOWRcw==", + "license": "MIT", + "dependencies": { + "@expressive-code/core": "^0.40.2", + "@expressive-code/plugin-frames": "^0.40.2", + "@expressive-code/plugin-shiki": "^0.40.2", + "@expressive-code/plugin-text-markers": "^0.40.2" + } + }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/flattie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flattie/-/flattie-1.1.1.tgz", + "integrity": "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/fontace": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/fontace/-/fontace-0.3.0.tgz", + "integrity": "sha512-czoqATrcnxgWb/nAkfyIrRp6Q8biYj7nGnL6zfhTcX+JKKpWHFBnb8uNMw/kZr7u++3Y3wYSYoZgHkCcsuBpBg==", + "license": "MIT", + "dependencies": { + "@types/fontkit": "^2.0.8", + "fontkit": "^2.0.4" + } + }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==", + "license": "ISC" + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/h3": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz", + "integrity": "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.2", + "crossws": "^0.3.5", + "defu": "^6.1.4", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.2", + "radix3": "^1.1.2", + "ufo": "^1.6.1", + "uncrypto": "^0.1.3" + } + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.4.tgz", + "integrity": "sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5/node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-escaper": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", + "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==", + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause" + }, + "node_modules/i18next": { + "version": "23.16.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", + "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/katex": { + "version": "0.16.22", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", + "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "license": "MIT" + }, + "node_modules/langium": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", + "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", + "license": "MIT", + "dependencies": { + "chevrotain": "~11.0.3", + "chevrotain-allstar": "~0.3.0", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.0.8" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.1.tgz", + "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.0.1", + "quansync": "^0.2.8" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "16.1.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.1.2.tgz", + "integrity": "sha512-rNQt5EvRinalby7zJZu/mB+BvaAY2oz3wCuCjt1RDrWNpS1Pdf9xqMOeC9Hm5adBdcV/3XZPJpG58eT+WBc0XQ==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mdast-util-definitions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", + "integrity": "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, + "node_modules/mermaid": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.9.0.tgz", + "integrity": "sha512-YdPXn9slEwO0omQfQIsW6vS84weVQftIyyTGAZCwM//MGhPzL1+l6vO6bkf0wnP4tHigH1alZ5Ooy3HXI2gOag==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.0.4", + "@iconify/utils": "^2.1.33", + "@mermaid-js/parser": "^0.6.2", + "@types/d3": "^7.4.3", + "cytoscape": "^3.29.3", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.11", + "dayjs": "^1.11.13", + "dompurify": "^3.2.5", + "katex": "^0.16.22", + "khroma": "^2.1.0", + "lodash-es": "^4.17.21", + "marked": "^16.0.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", + "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^2.0.1", + "pkg-types": "^1.3.0", + "ufo": "^1.5.4" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/nlcst-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nlcst-to-string/-/nlcst-to-string-4.0.0.tgz", + "integrity": "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/node-abi": { + "version": "3.75.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", + "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-mock-http": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.2.tgz", + "integrity": "sha512-zWaamgDUdo9SSLw47we78+zYw/bDr5gH8pH7oRRs8V3KmBtu8GLgGIbV2p/gRPd3LWpEOpjQj7X1FOU3VFMJ8g==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/ofetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.4.1.tgz", + "integrity": "sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.3", + "node-fetch-native": "^1.6.4", + "ufo": "^1.5.4" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz", + "integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/p-limit": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-8.1.0.tgz", + "integrity": "sha512-mxLDbbGIBEXTJL0zEx8JIylaj3xQ7Z/7eEVjcF9fJX4DBiH9oqe+oahYnlKKxm0Ci9TlWTyhSHgygxMxjIB2jw==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^6.1.2" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", + "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-manager-detector": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.3.0.tgz", + "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", + "license": "MIT" + }, + "node_modules/pagefind": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.3.0.tgz", + "integrity": "sha512-8KPLGT5g9s+olKMRTU9LFekLizkVIu9tes90O1/aigJ0T5LmyPqTzGJrETnSw3meSYg58YH7JTzhTTW/3z6VAw==", + "license": "MIT", + "bin": { + "pagefind": "lib/runner/bin.cjs" + }, + "optionalDependencies": { + "@pagefind/darwin-arm64": "1.3.0", + "@pagefind/darwin-x64": "1.3.0", + "@pagefind/linux-arm64": "1.3.0", + "@pagefind/linux-x64": "1.3.0", + "@pagefind/windows-x64": "1.3.0" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-latin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse-latin/-/parse-latin-7.0.0.tgz", + "integrity": "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "@types/unist": "^3.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-modify-children": "^4.0.0", + "unist-util-visit-children": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.2.0.tgz", + "integrity": "sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/quansync": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.10.tgz", + "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.2.tgz", + "integrity": "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "rehype-parse": "^9.0.0", + "rehype-stringify": "^10.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-expressive-code": { + "version": "0.40.2", + "resolved": "https://registry.npmjs.org/rehype-expressive-code/-/rehype-expressive-code-0.40.2.tgz", + "integrity": "sha512-+kn+AMGCrGzvtH8Q5lC6Y5lnmTV/r33fdmi5QU/IH1KPHKobKr5UnLwJuqHv5jBTSN/0v2wLDS7RTM73FVzqmQ==", + "license": "MIT", + "dependencies": { + "expressive-code": "^0.40.2" + } + }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-directive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", + "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", + "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==", + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-smartypants": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/remark-smartypants/-/remark-smartypants-3.0.2.tgz", + "integrity": "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==", + "license": "MIT", + "dependencies": { + "retext": "^9.0.0", + "retext-smartypants": "^6.0.0", + "unified": "^11.0.4", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "license": "MIT" + }, + "node_modules/retext": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/retext/-/retext-9.0.0.tgz", + "integrity": "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "retext-latin": "^4.0.0", + "retext-stringify": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-latin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-latin/-/retext-latin-4.0.0.tgz", + "integrity": "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "parse-latin": "^7.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-smartypants": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/retext-smartypants/-/retext-smartypants-6.2.0.tgz", + "integrity": "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/retext-stringify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/retext-stringify/-/retext-stringify-4.0.0.tgz", + "integrity": "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==", + "license": "MIT", + "dependencies": { + "@types/nlcst": "^2.0.0", + "nlcst-to-string": "^4.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", + "prebuild-install": "^7.1.1", + "semver": "^7.5.4", + "simple-get": "^4.0.1", + "tar-fs": "^3.0.4", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/shiki": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.9.2.tgz", + "integrity": "sha512-t6NKl5e/zGTvw/IyftLcumolgOczhuroqwXngDeMqJ3h3EQiTY/7wmfgPlsmloD8oYfqkEDqxiaH37Pjm1zUhQ==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.9.2", + "@shikijs/engine-javascript": "3.9.2", + "@shikijs/engine-oniguruma": "3.9.2", + "@shikijs/langs": "3.9.2", + "@shikijs/themes": "3.9.2", + "@shikijs/types": "3.9.2", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.0.tgz", + "integrity": "sha512-+AbdxhM9kJsHtruUF39bwS/B0Fytw6Fr1o4ZAIAEqA6cke2xcoO2GleBw9Zw7nRzILVEgz7zBM5GiTJjie1G9A==", + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.2.4" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" + }, + "node_modules/smol-toml": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.4.1.tgz", + "integrity": "sha512-CxdwHXyYTONGHThDbq5XdwbFsuY4wlClRGejfE2NtwUtiHYsP1QtNsHb/hnj31jKYSchztJsaA8pSQoVzkfCFg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stream-replace-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stream-replace-string/-/stream-replace-string-2.0.0.tgz", + "integrity": "sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==", + "license": "MIT" + }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/style-to-js": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", + "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.9" + } + }, + "node_modules/style-to-object": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", + "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/tar-fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz", + "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT" + }, + "node_modules/ultrahtml": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ultrahtml/-/ultrahtml-1.6.0.tgz", + "integrity": "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==", + "license": "MIT" + }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unifont": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/unifont/-/unifont-0.5.2.tgz", + "integrity": "sha512-LzR4WUqzH9ILFvjLAUU7dK3Lnou/qd5kD+IakBtBK4S15/+x2y9VX+DcWQv6s551R6W+vzwgVS6tFg3XggGBgg==", + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0", + "ofetch": "^1.4.1", + "ohash": "^2.0.0" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-modify-children": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-modify-children/-/unist-util-modify-children-4.0.0.tgz", + "integrity": "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "array-iterate": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-children": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit-children/-/unist-util-visit-children-3.0.0.tgz", + "integrity": "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unstorage": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.16.1.tgz", + "integrity": "sha512-gdpZ3guLDhz+zWIlYP1UwQ259tG5T5vYRzDaHMkQ1bBY1SQPutvZnrRjTFaWUUpseErJIgAZS51h6NOcZVZiqQ==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.3", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.6", + "ofetch": "^1.4.1", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT" + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which-pm-runs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz", + "integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/widest-line": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", + "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", + "license": "MIT", + "dependencies": { + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xxhash-wasm": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.1.0.tgz", + "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", + "license": "MIT" + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yocto-spinner": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-0.2.3.tgz", + "integrity": "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==", + "license": "MIT", + "dependencies": { + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18.19" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + }, + "node_modules/zod-to-ts": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/zod-to-ts/-/zod-to-ts-1.2.0.tgz", + "integrity": "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==", + "peerDependencies": { + "typescript": "^4.9.4 || ^5.0.2", + "zod": "^3" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..a0040b3 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,24 @@ +{ + "name": "", + "type": "module", + "version": "0.0.1", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "@astrojs/mdx": "^4.2.1", + "@astrojs/starlight": "^0.32.1", + "astro": "^5.1.5", + "astro-auto-import": "^0.4.4", + "astro-mermaid": "^1.0.4", + "mermaid": "^11.9.0", + "sharp": "^0.32.5" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.6.1" + } +} diff --git a/docs/public/favicon.png b/docs/public/favicon.png new file mode 100644 index 0000000..e50ed20 Binary files /dev/null and b/docs/public/favicon.png differ diff --git a/docs/public/favicon.svg b/docs/public/favicon.svg new file mode 100644 index 0000000..e00507e --- /dev/null +++ b/docs/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/public/images/admin_cutover_button.png b/docs/public/images/admin_cutover_button.png new file mode 100644 index 0000000..55c5928 Binary files /dev/null and b/docs/public/images/admin_cutover_button.png differ diff --git a/docs/public/images/admin_cutover_confirmation.png b/docs/public/images/admin_cutover_confirmation.png new file mode 100644 index 0000000..700f287 Binary files /dev/null and b/docs/public/images/admin_cutover_confirmation.png differ diff --git a/docs/public/images/admin_cutover_form.png b/docs/public/images/admin_cutover_form.png new file mode 100644 index 0000000..6db5e0e Binary files /dev/null and b/docs/public/images/admin_cutover_form.png differ diff --git a/docs/public/images/admin_cutover_run.png b/docs/public/images/admin_cutover_run.png new file mode 100644 index 0000000..dc9802e Binary files /dev/null and b/docs/public/images/admin_cutover_run.png differ diff --git a/docs/public/images/cleaned_up_sucessfully.png b/docs/public/images/cleaned_up_sucessfully.png new file mode 100644 index 0000000..4b27491 Binary files /dev/null and b/docs/public/images/cleaned_up_sucessfully.png differ diff --git a/docs/public/images/cleaning_up_resources.png b/docs/public/images/cleaning_up_resources.png new file mode 100644 index 0000000..da00263 Binary files /dev/null and b/docs/public/images/cleaning_up_resources.png differ diff --git a/docs/public/images/cluster-conversion-1.png b/docs/public/images/cluster-conversion-1.png new file mode 100644 index 0000000..bbc3df9 Binary files /dev/null and b/docs/public/images/cluster-conversion-1.png differ diff --git a/docs/public/images/cluster-conversion-2.png b/docs/public/images/cluster-conversion-2.png new file mode 100644 index 0000000..dc75cd9 Binary files /dev/null and b/docs/public/images/cluster-conversion-2.png differ diff --git a/docs/public/images/create_profile.png b/docs/public/images/create_profile.png new file mode 100644 index 0000000..3e08dcb Binary files /dev/null and b/docs/public/images/create_profile.png differ diff --git a/docs/public/images/cronjob-logs-available.png b/docs/public/images/cronjob-logs-available.png new file mode 100644 index 0000000..8625e9d Binary files /dev/null and b/docs/public/images/cronjob-logs-available.png differ diff --git a/docs/public/images/debug-bundle-download-button.png b/docs/public/images/debug-bundle-download-button.png new file mode 100644 index 0000000..5fce5e8 Binary files /dev/null and b/docs/public/images/debug-bundle-download-button.png differ diff --git a/docs/public/images/deployment-architecture.png b/docs/public/images/deployment-architecture.png new file mode 100644 index 0000000..0f78e74 Binary files /dev/null and b/docs/public/images/deployment-architecture.png differ diff --git a/docs/public/images/deployment-architecture.png.old b/docs/public/images/deployment-architecture.png.old new file mode 100644 index 0000000..ab9f043 Binary files /dev/null and b/docs/public/images/deployment-architecture.png.old differ diff --git a/docs/public/images/firstboot-form-1.png b/docs/public/images/firstboot-form-1.png new file mode 100644 index 0000000..ed019df Binary files /dev/null and b/docs/public/images/firstboot-form-1.png differ diff --git a/docs/public/images/firstboot-form.png b/docs/public/images/firstboot-form.png new file mode 100644 index 0000000..c626f3a Binary files /dev/null and b/docs/public/images/firstboot-form.png differ diff --git a/docs/public/images/network-traffic-separation.png b/docs/public/images/network-traffic-separation.png new file mode 100644 index 0000000..98b3b56 Binary files /dev/null and b/docs/public/images/network-traffic-separation.png differ diff --git a/docs/public/images/profiles_page.png b/docs/public/images/profiles_page.png new file mode 100644 index 0000000..9b7e419 Binary files /dev/null and b/docs/public/images/profiles_page.png differ diff --git a/docs/public/images/revalidate_openstack_cred.png b/docs/public/images/revalidate_openstack_cred.png new file mode 100644 index 0000000..3ad752f Binary files /dev/null and b/docs/public/images/revalidate_openstack_cred.png differ diff --git a/docs/public/images/revalidate_vmware_cred.png b/docs/public/images/revalidate_vmware_cred.png new file mode 100644 index 0000000..c2ff30f Binary files /dev/null and b/docs/public/images/revalidate_vmware_cred.png differ diff --git a/docs/public/images/select_profile.png b/docs/public/images/select_profile.png new file mode 100644 index 0000000..af2b99a Binary files /dev/null and b/docs/public/images/select_profile.png differ diff --git a/docs/public/images/upgrade_available.png b/docs/public/images/upgrade_available.png new file mode 100644 index 0000000..61a2cb3 Binary files /dev/null and b/docs/public/images/upgrade_available.png differ diff --git a/docs/public/images/upgrade_completed.png b/docs/public/images/upgrade_completed.png new file mode 100644 index 0000000..2288f3a Binary files /dev/null and b/docs/public/images/upgrade_completed.png differ diff --git a/docs/public/images/upgrade_in_progress.png b/docs/public/images/upgrade_in_progress.png new file mode 100644 index 0000000..0a68f42 Binary files /dev/null and b/docs/public/images/upgrade_in_progress.png differ diff --git a/docs/public/images/upgrade_modal.png b/docs/public/images/upgrade_modal.png new file mode 100644 index 0000000..17f173b Binary files /dev/null and b/docs/public/images/upgrade_modal.png differ diff --git a/docs/public/images/vjb-cluster-conversion.gif b/docs/public/images/vjb-cluster-conversion.gif new file mode 100644 index 0000000..c1d0f34 Binary files /dev/null and b/docs/public/images/vjb-cluster-conversion.gif differ diff --git a/docs/public/images/vjb-internal.png b/docs/public/images/vjb-internal.png new file mode 100644 index 0000000..8d98145 Binary files /dev/null and b/docs/public/images/vjb-internal.png differ diff --git a/docs/public/images/vtpm-customize-hardware.png b/docs/public/images/vtpm-customize-hardware.png new file mode 100644 index 0000000..3b8bad5 Binary files /dev/null and b/docs/public/images/vtpm-customize-hardware.png differ diff --git a/docs/public/images/vtpm-datastore-default-policy.png b/docs/public/images/vtpm-datastore-default-policy.png new file mode 100644 index 0000000..66d835f Binary files /dev/null and b/docs/public/images/vtpm-datastore-default-policy.png differ diff --git a/docs/public/images/vtpm-disable-vbs.png b/docs/public/images/vtpm-disable-vbs.png new file mode 100644 index 0000000..baed0d8 Binary files /dev/null and b/docs/public/images/vtpm-disable-vbs.png differ diff --git a/docs/public/images/vtpm-flavor-metadata.png b/docs/public/images/vtpm-flavor-metadata.png new file mode 100644 index 0000000..42f26af Binary files /dev/null and b/docs/public/images/vtpm-flavor-metadata.png differ diff --git a/docs/public/images/vtpm-key-provider.png b/docs/public/images/vtpm-key-provider.png new file mode 100644 index 0000000..2e52356 Binary files /dev/null and b/docs/public/images/vtpm-key-provider.png differ diff --git a/docs/public/images/vtpm-migration-success.png b/docs/public/images/vtpm-migration-success.png new file mode 100644 index 0000000..9d02c3a Binary files /dev/null and b/docs/public/images/vtpm-migration-success.png differ diff --git a/docs/public/images/vtpm-remove-tpm-device.png b/docs/public/images/vtpm-remove-tpm-device.png new file mode 100644 index 0000000..ff98246 Binary files /dev/null and b/docs/public/images/vtpm-remove-tpm-device.png differ diff --git a/docs/public/images/vtpm-select-guest-os.png b/docs/public/images/vtpm-select-guest-os.png new file mode 100644 index 0000000..6c2af39 Binary files /dev/null and b/docs/public/images/vtpm-select-guest-os.png differ diff --git a/docs/public/images/vtpm-tpm-management-console.png b/docs/public/images/vtpm-tpm-management-console.png new file mode 100644 index 0000000..c562351 Binary files /dev/null and b/docs/public/images/vtpm-tpm-management-console.png differ diff --git a/docs/public/images/vtpm-virsh-tpm-verify.png b/docs/public/images/vtpm-virsh-tpm-verify.png new file mode 100644 index 0000000..5a22d6e Binary files /dev/null and b/docs/public/images/vtpm-virsh-tpm-verify.png differ diff --git a/docs/public/images/vtpm-vm-details-pcd.png b/docs/public/images/vtpm-vm-details-pcd.png new file mode 100644 index 0000000..a37fe9f Binary files /dev/null and b/docs/public/images/vtpm-vm-details-pcd.png differ diff --git a/docs/public/images/vtpm-vm-policies-menu.png b/docs/public/images/vtpm-vm-policies-menu.png new file mode 100644 index 0000000..7db04a4 Binary files /dev/null and b/docs/public/images/vtpm-vm-policies-menu.png differ diff --git a/docs/public/scripts/Test-VjbLdmSystemDisk.ps1 b/docs/public/scripts/Test-VjbLdmSystemDisk.ps1 new file mode 100644 index 0000000..acb2576 --- /dev/null +++ b/docs/public/scripts/Test-VjbLdmSystemDisk.ps1 @@ -0,0 +1,386 @@ +<# +.SYNOPSIS + Determines whether the Windows system disk is an LDM (dynamic) disk. + +.DESCRIPTION + Run this on the SOURCE VM in vCenter, before migrating with vJailbreak. + + Windows VMs whose system volume sits on a dynamic disk (Logical Disk Manager) + cannot be converted by virt-v2v and follow vJailbreak's dedicated SATA-first + migration path, which has extra prerequisites. Dynamic DATA disks are + irrelevant - only the disk carrying the system volume changes the outcome. + + The script is strictly READ-ONLY. It issues no diskpart command other than + 'list disk', 'select volume', 'detail volume' and 'san', none of which modify + the disk layout. + + Detection uses four independent signals, so a wrong answer requires several + of them to agree incorrectly: + + 1. GPT partition type GUID - 5808C8AA-... (LDM metadata) + AF9B60A0-... (LDM data) + 2. MBR partition type byte - 0x42 (LDM) + 3. WMI Win32_DiskPartition.Type containing "Logical Disk Manager" + 4. diskpart's Dyn column in 'list disk' / 'detail volume' + + The system volume is mapped to its physical disk(s) by three independent + routes (Storage module, WMI associators, diskpart). On a dynamic disk the + Storage-module route fails by design - the partition layer does not model LDM + volumes - and that failure is itself recorded as corroborating evidence. + +.PARAMETER LogPath + Where the transcript is written. Defaults to %TEMP%\vjb-ldm-check.log. + +.PARAMETER Quiet + Suppress the per-signal transcript on the console. The verdict banner is + always printed. + +.OUTPUTS + A verdict banner, plus a single machine-readable line: + VJB_LDM_SYSTEM_DISK=YES|NO|UNKNOWN + + Exit codes: + 0 NO system disk is basic - normal migration path + 1 YES system disk is LDM - SATA-first migration path + 2 UNKNOWN inconclusive, inspect the log + +.NOTES + Requires an elevated PowerShell session. + Tested against Windows Server 2012 and later. Written to PowerShell 2.0 + syntax so it also runs on Windows Server 2008 R2, where the Storage module + is absent and signals 1 and 2 are unavailable. +#> + +[CmdletBinding()] +param( + [string] $LogPath = (Join-Path $env:TEMP 'vjb-ldm-check.log'), + [switch] $Quiet +) + +$ErrorActionPreference = 'Continue' + +# GPT partition type GUIDs that identify an LDM disk. +$LDM_GPT_TYPES = @( + '5808c8aa-7e8f-42e0-85d2-e1e90434cfb3', # LDM metadata partition + 'af9b60a0-1431-4f62-bc68-3311714a69ad' # LDM data partition +) +# MBR partition type byte 0x42 = LDM. +$LDM_MBR_TYPE = 66 + +$script:LogLines = New-Object System.Collections.ArrayList + +# ---------------------------------------------------------------- logging ---- + +function Write-Log { + param([string] $Level, [string] $Message) + + $line = '{0} [{1,-5}] {2}' -f (Get-Date).ToString('yyyy-MM-dd HH:mm:ss'), $Level, $Message + [void] $script:LogLines.Add($line) + + if (-not $Quiet) { + $color = 'Gray' + if ($Level -eq 'WARN') { $color = 'Yellow' } + if ($Level -eq 'ERROR') { $color = 'Red' } + if ($Level -eq 'HIT') { $color = 'Magenta' } + Write-Host $line -ForegroundColor $color + } +} + +# ------------------------------------------------------------- primitives ---- + +function Test-Elevated { + try { + $identity = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = New-Object Security.Principal.WindowsPrincipal($identity) + return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) + } catch { + return $false + } +} + +function Invoke-DiskPart { + param([string[]] $Commands) + + $scriptFile = [System.IO.Path]::GetTempFileName() + try { + Set-Content -Path $scriptFile -Value $Commands -Encoding ASCII + $output = & diskpart.exe /s $scriptFile 2>&1 + return @($output | ForEach-Object { [string] $_ }) + } catch { + Write-Log 'WARN' ("diskpart invocation failed: " + $_.Exception.Message) + return @() + } finally { + Remove-Item $scriptFile -Force -ErrorAction SilentlyContinue + } +} + +function Get-Column { + # Slice a fixed-width diskpart column, bleeding one character either side + # because diskpart does not always pad exactly to its own rule line. + param([string] $Line, $Column, [int] $Bleed = 1) + + $start = $Column.Start - $Bleed + if ($start -lt 0) { $start = 0 } + if ($Line.Length -le $start) { return '' } + + $length = $Column.Length + ($Column.Start - $start) + $Bleed + if (($start + $length) -gt $Line.Length) { $length = $Line.Length - $start } + return $Line.Substring($start, $length) +} + +function Read-DiskPartDiskTable { + # Both 'list disk' and 'detail volume' emit the same six-column table: + # + # Disk ### Status Size Free Dyn Gpt + # -------- ------------- ------- ------- --- --- + # Disk 0 Online 100 GB 0 B * + # + # Column offsets are taken from the rule line rather than from the header, + # so parsing does not depend on the console language. + param([string[]] $Lines) + + $rows = @() + $ruleIndex = -1 + $columns = @() + + for ($i = 0; $i -lt $Lines.Count; $i++) { + if ($Lines[$i] -match '^\s*-{3,}(\s+-{2,}){3,}\s*$') { + $ruleIndex = $i + foreach ($match in ([regex] '-{2,}').Matches($Lines[$i])) { + $columns += New-Object PSObject -Property @{ + Start = $match.Index + Length = $match.Length + } + } + break + } + } + + if ($ruleIndex -lt 0 -or $columns.Count -lt 5) { + Write-Log 'WARN' 'Could not locate the diskpart column rule line; skipping this signal.' + return $rows + } + + for ($i = $ruleIndex + 1; $i -lt $Lines.Count; $i++) { + if ($Lines[$i].Trim().Length -eq 0) { break } + + $number = [regex]::Match((Get-Column $Lines[$i] $columns[0] 0), '\d+') + if (-not $number.Success) { continue } + + $gpt = $false + if ($columns.Count -ge 6) { + $gpt = (Get-Column $Lines[$i] $columns[5]).Contains('*') + } + + $rows += New-Object PSObject -Property @{ + Number = [int] $number.Value + Dynamic = (Get-Column $Lines[$i] $columns[4]).Contains('*') + Gpt = $gpt + } + } + + return $rows +} + +# ------------------------------------------------------------ inspection ----- + +function Get-DynamicDiskEvidence { + # Returns the disk numbers that at least one signal reports as LDM. + param([bool] $StorageModule) + + $dynamic = @{} + + # Signal 4 - diskpart Dyn column. + foreach ($row in (Read-DiskPartDiskTable (Invoke-DiskPart @('list disk')))) { + $state = 'basic' + if ($row.Dynamic) { $state = 'DYNAMIC'; $dynamic[$row.Number] = $true } + Write-Log 'INFO' ('diskpart : disk {0} -> {1}' -f $row.Number, $state) + } + + # Signal 3 - WMI partition type string. + foreach ($partition in @(Get-WmiObject -Class Win32_DiskPartition -ErrorAction SilentlyContinue)) { + if ($partition.Type -match 'Logical Disk Manager|LDM') { + $dynamic[[int] $partition.DiskIndex] = $true + Write-Log 'HIT' ('Win32_DiskPartition: disk {0} partition type "{1}"' -f $partition.DiskIndex, $partition.Type) + } else { + Write-Log 'INFO' ('Win32_DiskPartition: disk {0} partition type "{1}"' -f $partition.DiskIndex, $partition.Type) + } + } + + # Signals 1 and 2 - GPT type GUID and MBR type byte. Storage module only. + if ($StorageModule) { + foreach ($partition in @(Get-Partition -ErrorAction SilentlyContinue)) { + $gptType = '' + if ($partition.GptType) { $gptType = ($partition.GptType -replace '[{}]', '').ToLower() } + + if ($LDM_GPT_TYPES -contains $gptType) { + $dynamic[[int] $partition.DiskNumber] = $true + Write-Log 'HIT' ('GPT type GUID : disk {0} partition {1} -> {2}' -f $partition.DiskNumber, $partition.PartitionNumber, $gptType) + } elseif ([int] $partition.MbrType -eq $LDM_MBR_TYPE) { + $dynamic[[int] $partition.DiskNumber] = $true + Write-Log 'HIT' ('MBR type byte : disk {0} partition {1} -> 0x42 (LDM)' -f $partition.DiskNumber, $partition.PartitionNumber) + } + } + } else { + Write-Log 'WARN' 'Storage module unavailable; partition-type signals skipped (expected on Server 2008 R2).' + } + + return @($dynamic.Keys | ForEach-Object { [int] $_ }) +} + +function Get-SystemVolumeDiskNumber { + # Three independent routes. Returns the union of whatever resolves. + param([string] $DriveLetter, [bool] $StorageModule) + + $found = @{} + + # Route 1 - Storage module. Fails by design when the volume is LDM. + if ($StorageModule) { + $partition = Get-Partition -DriveLetter $DriveLetter -ErrorAction SilentlyContinue + if ($partition) { + $hit = @() + foreach ($p in @($partition)) { + $found[[int] $p.DiskNumber] = $true + $hit += [string] $p.DiskNumber + } + Write-Log 'INFO' ('Get-Partition : {0}: resolves to disk {1}' -f $DriveLetter, ($hit -join ', ')) + } else { + Write-Log 'WARN' ('Get-Partition : {0}: has no partition object. The partition layer does not model LDM volumes, so this is expected on a dynamic disk.' -f $DriveLetter) + } + } + + # Route 2 - WMI associators. Also unavailable for LDM volumes. + $query = "ASSOCIATORS OF {{Win32_LogicalDisk.DeviceID='{0}:'}} WHERE AssocClass=Win32_LogicalDiskToPartition" -f $DriveLetter + $associated = @(Get-WmiObject -Query $query -ErrorAction SilentlyContinue) + if ($associated.Count -gt 0) { + $hit = @() + foreach ($p in $associated) { + $found[[int] $p.DiskIndex] = $true + $hit += [string] $p.DiskIndex + } + Write-Log 'INFO' ('WMI associator: {0}: resolves to disk {1}' -f $DriveLetter, ($hit -join ', ')) + } else { + Write-Log 'WARN' ('WMI associator: {0}: has no Win32_LogicalDiskToPartition association, which is typical for an LDM volume.' -f $DriveLetter) + } + + # Route 3 - diskpart. Works for both basic and dynamic volumes. + $detail = Invoke-DiskPart @(('select volume ' + $DriveLetter), 'detail volume') + foreach ($row in (Read-DiskPartDiskTable $detail)) { + $found[$row.Number] = $true + $state = 'basic' + if ($row.Dynamic) { $state = 'DYNAMIC' } + Write-Log 'INFO' ('detail volume : {0}: resides on disk {1} ({2})' -f $DriveLetter, $row.Number, $state) + } + + return @($found.Keys | ForEach-Object { [int] $_ }) +} + +# ------------------------------------------------------------------ main ----- + +Write-Log 'INFO' '=== vJailbreak LDM system-disk probe (read-only) ===' +Write-Log 'INFO' ('Host : {0}' -f $env:COMPUTERNAME) +Write-Log 'INFO' ('OS : {0}' -f (Get-WmiObject -Class Win32_OperatingSystem -ErrorAction SilentlyContinue).Caption) +Write-Log 'INFO' ('PowerShell : {0}' -f $PSVersionTable.PSVersion.ToString()) +Write-Log 'INFO' ('System drive : {0}' -f $env:SystemDrive) + +if (-not (Test-Elevated)) { + Write-Log 'ERROR' 'Not running elevated. diskpart and the Storage module need Administrator rights.' + Write-Log 'ERROR' 'Re-run this script from an elevated PowerShell session.' + $verdict = 'UNKNOWN' +} else { + $driveLetter = $env:SystemDrive.Substring(0, 1) + $storageModule = $null -ne (Get-Command Get-Disk -ErrorAction SilentlyContinue) + Write-Log 'INFO' ('Storage module: {0}' -f $(if ($storageModule) { 'available' } else { 'NOT available' })) + Write-Log 'INFO' '--- mapping the system volume to its physical disk(s) ---' + + $systemDisks = Get-SystemVolumeDiskNumber -DriveLetter $driveLetter -StorageModule $storageModule + + Write-Log 'INFO' '--- collecting LDM evidence for every disk ---' + $dynamicDisks = Get-DynamicDiskEvidence -StorageModule $storageModule + + Write-Log 'INFO' '--- summary ---' + Write-Log 'INFO' ('System volume resides on disk(s) : {0}' -f $(if ($systemDisks.Count) { ($systemDisks | Sort-Object) -join ', ' } else { '' })) + Write-Log 'INFO' ('Disks reported as LDM/dynamic : {0}' -f $(if ($dynamicDisks.Count) { ($dynamicDisks | Sort-Object) -join ', ' } else { 'none' })) + + if ($systemDisks.Count -gt 0) { + $hit = @($systemDisks | Where-Object { $dynamicDisks -contains $_ }) + if ($hit.Count -gt 0) { + $verdict = 'YES' + Write-Log 'HIT' ('System volume resides on dynamic disk(s): {0}' -f (($hit | Sort-Object) -join ', ')) + } else { + $verdict = 'NO' + $dataOnly = @($dynamicDisks | Where-Object { $systemDisks -notcontains $_ }) + if ($dataOnly.Count -gt 0) { + Write-Log 'INFO' ('Disk(s) {0} are dynamic but carry no system volume. Data disks do not affect the migration path.' -f (($dataOnly | Sort-Object) -join ', ')) + } + } + } elseif ($dynamicDisks.Count -gt 0) { + # No route could map the system volume to a disk, and dynamic disks + # exist. Both mapping failures are themselves LDM symptoms. + $verdict = 'YES' + Write-Log 'HIT' 'The system volume could not be mapped to any partition object while dynamic disks are present. Both facts point to an LDM system volume.' + } else { + $verdict = 'UNKNOWN' + Write-Log 'ERROR' 'The system volume could not be mapped to a disk and no LDM evidence was found. Inspect the log and check the disk layout manually.' + } + + # Informational only - not part of the verdict. The SAN policy is a + # prerequisite for the LDM migration path. + $sanOutput = Invoke-DiskPart @('san') + foreach ($line in $sanOutput) { + if ($line -match ':') { + $trimmed = $line.Trim() + if ($trimmed -match 'Online|Offline') { + Write-Log 'INFO' ('SAN policy : {0}' -f $trimmed) + } + } + } +} + +# Persist the transcript before anything else can fail. +try { + Set-Content -Path $LogPath -Value $script:LogLines -Encoding ASCII + Write-Host '' + Write-Host ('Full transcript: {0}' -f $LogPath) -ForegroundColor DarkGray +} catch { + Write-Host '' + Write-Host ('Could not write the transcript to {0}: {1}' -f $LogPath, $_.Exception.Message) -ForegroundColor Yellow +} + +# ---------------------------------------------------------------- verdict ---- +# Printed last, on its own, so it is never buried in the transcript above. + +$banner = 'Gray' +if ($verdict -eq 'YES') { $banner = 'Red' } +if ($verdict -eq 'NO') { $banner = 'Green' } +if ($verdict -eq 'UNKNOWN') { $banner = 'Yellow' } + +# Built by measurement rather than by hand-counted spaces, so the box always +# closes regardless of the verdict length. +$width = 64 +$rule = '#' * $width +$blank = '#' + (' ' * ($width - 2)) + '#' +$label = ' SYSTEM DISK IS LDM (DYNAMIC): ' + $verdict +$pad = $width - 2 - $label.Length +if ($pad -lt 0) { $pad = 0 } + +Write-Host '' +Write-Host $rule +Write-Host $blank +Write-Host ('#' + $label + (' ' * $pad) + '#') -ForegroundColor $banner +Write-Host $blank +Write-Host $rule +Write-Host '' + +if ($verdict -eq 'YES') { + Write-Host 'This VM takes the SATA-first migration path. Install the VirtIO guest' -ForegroundColor Yellow + Write-Host 'tools and set "diskpart > san policy=onlineall" on THIS VM before' -ForegroundColor Yellow + Write-Host 'migrating, and expect a manual cutover at LDM Boot Verification.' -ForegroundColor Yellow + Write-Host '' +} + +Write-Host ('VJB_LDM_SYSTEM_DISK={0}' -f $verdict) + +if ($verdict -eq 'YES') { exit 1 } +if ($verdict -eq 'NO') { exit 0 } +exit 2 diff --git a/docs/reference/example/index.html b/docs/reference/example/index.html new file mode 100644 index 0000000..d5f7c6c --- /dev/null +++ b/docs/reference/example/index.html @@ -0,0 +1,144 @@ + Example Reference | vJailbreak + Skip to content

Example Reference

Reference pages are ideal for outlining how things work in terse and clear terms. +Less concerned with telling a story or addressing a specific use case, they should give a comprehensive outline of what you’re documenting.

+

Further reading

+
\ No newline at end of file diff --git a/docs/reference/reference/index.html b/docs/reference/reference/index.html new file mode 100644 index 0000000..070ba1b --- /dev/null +++ b/docs/reference/reference/index.html @@ -0,0 +1,224 @@ + vJailbreak CRD references | vJailbreak + Skip to content

vJailbreak CRD references

The following custom resource definitions (CRD) are deployed in the same namespace as the Migration Controller pod. By default, the namespace is migration-system.

+

Credentials

+

OpenStack

+
    +
  • OpenstackCreds use the variables from the openstack.rc file. All fields are required except OS_INSECURE
  • +
+
apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
kind: OpenstackCreds
metadata:
name: osc1
namespace: migration-system
spec:
secretRef:
name: osc1-openstack-secret
---
apiVersion: v1
data:
OS_AUTH_URL:
OS_DOMAIN_NAME:
OS_INSECURE:
OS_PASSWORD:
OS_REGION_NAME:
OS_TENANT_NAME:
OS_USERNAME:
kind: Secret
metadata:
name: osc1-openstack-secret
namespace: migration-system
type: Opaque
+

VMware

+
    +
  • All fields in VMwareCreds are required.
  • +
+
apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
kind: VMwareCreds
metadata:
name: vmc1
namespace: migration-system
spec:
secretRef:
name: vmc1-vmware-secret
---
apiVersion: v1
data:
VCENTER_HOST:
VCENTER_INSECURE:
VCENTER_PASSWORD:
VCENTER_USERNAME:
kind: Secret
metadata:
name: vmc1-vmware-secret
namespace: migration-system
type: Opaque
+

Network mapping

+
apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
kind: NetworkMapping
metadata:
name: nwmap1
namespace: migration-system
spec:
networks:
- source: VM Network
target: vlan3002
- source: VM Network 2
target: vlan3003
+

Datastore mapping

+
apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
kind: StorageMapping
metadata:
name: stmap1
namespace: migration-system
spec:
storages:
- source: vcenter-datastore-1
target: lvm
- source: vcenter-datastore-2
target: ceph
+

MigrationTemplate

+
apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
kind: MigrationTemplate
metadata:
name: migrationtemplate-windows
namespace: migration-system
spec:
networkMapping: name_of_networkMapping
storageMapping: name_of_storageMapping
osType: windows/linux <optional>
source:
datacenter: name_of_datacenter
vmwareRef: name_of_VMwareCreds
destination:
openstackRef: name_of_OpenstackCreds
+
    +
  • osType is optional. If not provided, the osType is retrieved from vCenter. If it can’t be automatically determined, migration will not proceed.
  • +
+

MigrationPlan

+
apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
kind: MigrationPlan
metadata:
name: vm-migration-app1
namespace: migration-system
spec:
migrationTemplate: migrationtemplate-windows
retry: true/false <optional>
advancedOptions:
granularVolumeTypes:
- newvoltype1
granularNetworks:
- newnetworkname1
- newnetworkname2
granularPorts:
- <port uuid 1>
- <port uuid 2>
migrationStrategy:
type: hot/cold
dataCopyStart: 2024-08-27T17:30:25.230Z
vmCutoverStart: 2024-08-27T17:30:25.230Z
vmCutoverEnd: 2024-08-28T17:30:25.230Z
adminInitiatedCutOver: true/false
performHealthChecks: true/false
healthCheckPort: string
virtualmachines:
- - winserver2k12
- winserver2k16
- - winserver2k19
- winserver2k22
+
    +
  • retry: Optional. Retries one failed migration in a migration plan once. Set to false after a migration has been retried.
  • +
  • advancedOptions: This is an optional field for granular control over migration options. MigrationTemplate with mappings must still be present. These options override the ones in the template, if set. If you use these options, you must only have 1 VM present in the virtualmachines list. +
      +
    • granularVolumeTypes: In case you wish to provide different volume types to disks of a VM when they are all on the same datastore, you can specify the volume type of each disk of your VM in order. You must define one volume type for one disk present on the VM
    • +
    • granularNetworks: In case you wish to override the default network mapping for a VM, you can provide a list of OpenStack network names to use in for each NIC on the VM, in order.
    • +
    • granularPorts: In case you wish to pre-create ports for a VM with certain configs and directly provide them to the target VM, you can define a list of port IDS to be used for each network on the VM. It will override options set in granularNetworks.
    • +
    +
  • +
  • migrationStrategy: This is an optional field +
      +
    • type: +
        +
      • cold: Cold indicates to power off VMs in migrationplan at the start of the migration. Quicker than hot.
      • +
      • hot: Powers VM off just before cutover starts. Data copy occurs with the source VM powered on. May take longer.
      • +
      +
    • +
    • dataCopyStart: Optional. ISO 8601 timestamp indicating when to start data copy
    • +
    • vmCutoverStart: Optional. ISO 8601 timestamp indicating when to start VM cutover
    • +
    • vmCutoverEnd: Optional. ISO 8601 timestamp indicating the latest time by when VM cutover can start. If this time has been passed before the cutover can start, migration will fail.
    • +
    • adminInitiatedCutOver: Set to true if you wish to manually trigger the cutover process. Default: false
    • +
    • performHealthChecks: Set to false if you want to disable Ping and HTTP GET health check. Failing these checks does not clean up the targeted VM. Default: false
    • +
    • healthCheckPort: Port to run the HTTP GET health check against. Default “443”
    • +
    +
  • +
  • virtualmachines: Specify names of VMs to migrate. In this example the batch of VMs winserver2k12 and winserver2k16 migrate in parallel. winserver2k19 and winserver2k22 will wait for the first 2 to complete successfully, and then start in parallel. You can use this notation to specify whether VMs should migrate sequentially or in parallel within a plan.
  • +
+

VjailbreakNode

+

vJailbreak can be scaled to perform multiple migrations in parallel by deploying additional agents, enabling greater efficiency and workload distribution. The VjailbreakNode Custom Resource Definition (CRD) streamlines the creation and management of these agents, ensuring seamless integration into the migration workflow. Each VjailbreakNode represents a VM that functions as an independent migration agent. These agents are dynamically added to the original VjailbreakNode, forming a cohesive cluster that enhances scalability, reliability, and overall migration performance.

+
apiVersion: vjailbreak.k8s.pf9.io/v1alpha1
kind: VjailbreakNode
metadata:
name: example-vjailbreak-node
namespace: migration-system
spec:
imageid: "your-openstack-image-id" # This ID is for the first vjailbreak VMimage. It auto-populates in the UI—do not delete it.
noderole: "migration-worker"
openstackcreds:
name: "name" # Reference to your OpenstackCreds
namespace: "migration-system"
openstackflavorid: "your-openstack-flavor-id"
+

This VjailbreakNode CRD defines a Kubernetes resource that provisions a VM in OpenStack to act as a migration agent. Below is a breakdown of each field:

+
    +
  • metadata: Metadata contains identifying details about the VjailbreakNode. +
      +
    • name: example-vjailbreak-node: Specifies the name of this VjailbreakNode resource in Kubernetes.
    • +
    • namespace: migration-system: Indicates the namespace where this resource is deployed within the Kubernetes cluster.
    • +
    +
  • +
+

The spec section defines the desired state of the VjailbreakNode.

+
    +
  • imageid: "your-openstack-image-id": This is the ID of the OpenStack image used to create the VM. +
      +
    • It must match the image ID used to create the initial vJailbreak VM, ensuring compatibility across all migration agents.
    • +
    +
  • +
  • noderole: "worker": Defines the role of the node. +
      +
    • It should be set to "worker" as this node functions as a migration agent within the vJailbreak cluster.
    • +
    +
  • +
  • openstackcreds:: OpenstackCreds use the variables from the openstack.rc file. +
      +
    • name: "name" → Refers to a Secret or CustomResource storing OpenStack authentication details.
    • +
    • namespace: "migration-system" → The namespace where OpenStack credentials are stored.
    • +
    +
  • +
  • openstackflavorid: "your-openstack-flavor-id": Specifies the OpenStack flavor ID, which determines the VM’s compute resources (CPU, RAM, disk size, etc.). +
      +
    • The chosen flavor should align with the resource requirements for migration workloads.
    • +
    +
  • +
\ No newline at end of file diff --git a/docs/release_docs/v012/index.html b/docs/release_docs/v012/index.html new file mode 100644 index 0000000..5022098 --- /dev/null +++ b/docs/release_docs/v012/index.html @@ -0,0 +1,177 @@ + v0.1.2 | vJailbreak + Skip to content

v0.1.2

What’s Changed

+ +

Known Issues

+
    +
  • If the VM to be migrated has an LV spanning multiple physical devices used as boot volume unless both are mounted simultaneously to the OS-VM, vJailbreak cannot detect whether it is bootable, or not. (issue link)
  • +
  • If a user turns on a VM on VCenter after migration and tries to migrate it again, the migration object will not be created. In this case, the user should delete the VM from PCD/Openstack before trying the migration again. This error will be pushed up the stack for visibility.
  • +
+

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.1…16.01

\ No newline at end of file diff --git a/docs/release_docs/v013/index.html b/docs/release_docs/v013/index.html new file mode 100644 index 0000000..c255b5d --- /dev/null +++ b/docs/release_docs/v013/index.html @@ -0,0 +1,143 @@ + v0.1.3 | vJailbreak + Skip to content
\ No newline at end of file diff --git a/docs/release_docs/v014/index.html b/docs/release_docs/v014/index.html new file mode 100644 index 0000000..2d4fa22 --- /dev/null +++ b/docs/release_docs/v014/index.html @@ -0,0 +1,160 @@ + v0.1.4 | vJailbreak + Skip to content

v0.1.4

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.3…v0.1.4

\ No newline at end of file diff --git a/docs/release_docs/v015/index.html b/docs/release_docs/v015/index.html new file mode 100644 index 0000000..769c35e --- /dev/null +++ b/docs/release_docs/v015/index.html @@ -0,0 +1,160 @@ + v0.1.5 | vJailbreak + Skip to content

v0.1.5

What’s Changed

+ +

New Contributors

+
    +
  • @damian-pf9
  • +
  • @anmolsachan
  • +
  • @bhavin192
  • +
+

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.4…v0.1.5

\ No newline at end of file diff --git a/docs/release_docs/v016/index.html b/docs/release_docs/v016/index.html new file mode 100644 index 0000000..900caa4 --- /dev/null +++ b/docs/release_docs/v016/index.html @@ -0,0 +1,152 @@ + v0.1.6 | vJailbreak + Skip to content

v0.1.6

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.5…v0.1.6

\ No newline at end of file diff --git a/docs/release_docs/v017/index.html b/docs/release_docs/v017/index.html new file mode 100644 index 0000000..600b27d --- /dev/null +++ b/docs/release_docs/v017/index.html @@ -0,0 +1,168 @@ + v0.1.7 | vJailbreak + Skip to content

v0.1.7

What’s Changed

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.6…v0.1.7

\ No newline at end of file diff --git a/docs/release_docs/v018/index.html b/docs/release_docs/v018/index.html new file mode 100644 index 0000000..d5b5514 --- /dev/null +++ b/docs/release_docs/v018/index.html @@ -0,0 +1,150 @@ + v0.1.8 | vJailbreak + Skip to content

v0.1.8

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.6…v0.1.8

\ No newline at end of file diff --git a/docs/release_docs/v019/index.html b/docs/release_docs/v019/index.html new file mode 100644 index 0000000..c7e8151 --- /dev/null +++ b/docs/release_docs/v019/index.html @@ -0,0 +1,156 @@ + v0.1.9 | vJailbreak + Skip to content

v0.1.9

What’s Changed

+ +

New Contributors

+ +

Full Changelog: https://github.com/platform9/vjailbreak/compare/v0.1.8…v0.1.9

\ No newline at end of file diff --git a/docs/sitemap-0.xml b/docs/sitemap-0.xml new file mode 100644 index 0000000..62ff924 --- /dev/null +++ b/docs/sitemap-0.xml @@ -0,0 +1 @@ +https://platform9.github.io/https://platform9.github.io/guides/building/https://platform9.github.io/guides/injecting_custom_env/https://platform9.github.io/guides/scaling/https://platform9.github.io/guides/troubleshooting/https://platform9.github.io/guides/using_apis/https://platform9.github.io/introduction/components/https://platform9.github.io/introduction/getting_started/https://platform9.github.io/introduction/prerequisites/https://platform9.github.io/introduction/what_is_vjailbreak/https://platform9.github.io/reference/example/https://platform9.github.io/reference/reference/https://platform9.github.io/release_docs/v012/https://platform9.github.io/release_docs/v013/https://platform9.github.io/release_docs/v014/https://platform9.github.io/release_docs/v015/https://platform9.github.io/release_docs/v016/https://platform9.github.io/release_docs/v017/https://platform9.github.io/release_docs/v018/https://platform9.github.io/release_docs/v019/ \ No newline at end of file diff --git a/docs/sitemap-index.xml b/docs/sitemap-index.xml new file mode 100644 index 0000000..94b8340 --- /dev/null +++ b/docs/sitemap-index.xml @@ -0,0 +1 @@ +https://platform9.github.io/sitemap-0.xml \ No newline at end of file diff --git a/docs/src/assets/classic_logo.jpeg b/docs/src/assets/classic_logo.jpeg new file mode 100644 index 0000000..6ae8078 Binary files /dev/null and b/docs/src/assets/classic_logo.jpeg differ diff --git a/docs/src/assets/clusterblueprint.png b/docs/src/assets/clusterblueprint.png new file mode 100644 index 0000000..c6d43b1 Binary files /dev/null and b/docs/src/assets/clusterblueprint.png differ diff --git a/docs/src/assets/credsedit.png b/docs/src/assets/credsedit.png new file mode 100644 index 0000000..0085b05 Binary files /dev/null and b/docs/src/assets/credsedit.png differ diff --git a/docs/src/assets/houston.webp b/docs/src/assets/houston.webp new file mode 100644 index 0000000..930c164 Binary files /dev/null and b/docs/src/assets/houston.webp differ diff --git a/docs/src/assets/information-flow.png b/docs/src/assets/information-flow.png new file mode 100644 index 0000000..1415c34 Binary files /dev/null and b/docs/src/assets/information-flow.png differ diff --git a/docs/src/assets/logo.jpg b/docs/src/assets/logo.jpg new file mode 100644 index 0000000..3c0c673 Binary files /dev/null and b/docs/src/assets/logo.jpg differ diff --git a/docs/src/assets/logo.png b/docs/src/assets/logo.png new file mode 100644 index 0000000..efa7349 Binary files /dev/null and b/docs/src/assets/logo.png differ diff --git a/docs/src/assets/platform9-logo.svg b/docs/src/assets/platform9-logo.svg new file mode 100644 index 0000000..eb8a08a --- /dev/null +++ b/docs/src/assets/platform9-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/src/assets/sshkey.png b/docs/src/assets/sshkey.png new file mode 100644 index 0000000..b832803 Binary files /dev/null and b/docs/src/assets/sshkey.png differ diff --git a/docs/src/assets/storagemanagementpage.png b/docs/src/assets/storagemanagementpage.png new file mode 100644 index 0000000..72697a2 Binary files /dev/null and b/docs/src/assets/storagemanagementpage.png differ diff --git a/docs/src/assets/stream_syslog_loki.png b/docs/src/assets/stream_syslog_loki.png new file mode 100644 index 0000000..9d6d2ed Binary files /dev/null and b/docs/src/assets/stream_syslog_loki.png differ diff --git a/docs/src/assets/trigger-mig.png b/docs/src/assets/trigger-mig.png new file mode 100644 index 0000000..03c940c Binary files /dev/null and b/docs/src/assets/trigger-mig.png differ diff --git a/docs/src/assets/vJailbreak.gif b/docs/src/assets/vJailbreak.gif new file mode 100644 index 0000000..3bee444 Binary files /dev/null and b/docs/src/assets/vJailbreak.gif differ diff --git a/docs/src/assets/vmware-adding-back-disk.png b/docs/src/assets/vmware-adding-back-disk.png new file mode 100644 index 0000000..691a5a5 Binary files /dev/null and b/docs/src/assets/vmware-adding-back-disk.png differ diff --git a/docs/src/assets/vmware-removing-rdm-disk.png b/docs/src/assets/vmware-removing-rdm-disk.png new file mode 100644 index 0000000..be74028 Binary files /dev/null and b/docs/src/assets/vmware-removing-rdm-disk.png differ diff --git a/docs/src/components/ApiReference.astro b/docs/src/components/ApiReference.astro new file mode 100644 index 0000000..d725f1f --- /dev/null +++ b/docs/src/components/ApiReference.astro @@ -0,0 +1,81 @@ +--- +export interface Props { + versions: string[]; +} +const { versions } = Astro.props; +--- + + + +
+ {versions.map(tag => ( + + ))} +
+
+ +
+ + \ No newline at end of file diff --git a/docs/src/components/Aurora.astro b/docs/src/components/Aurora.astro new file mode 100644 index 0000000..2d9f57f --- /dev/null +++ b/docs/src/components/Aurora.astro @@ -0,0 +1,139 @@ +--- +const widths = ['0.5rem', '1rem', '1.5rem', '2rem', '2.5rem', '3rem', '4rem']; +const heights = ['80%', '85%', '90%', '95%', '100%']; +const anims = [ + ['0s', '4s'], + ['-3s', '7s'], + ['-2s', '8s'], + ['-3s', '9s'], + ['-2s', '10s'], + ['-4s', '11s'], + ['-6s', '12s'], +]; +const opacity = [ + 'opacity-10', + 'opacity-20', + 'opacity-30', + 'opacity-40', + 'opacity-50', + 'opacity-60', + 'opacity-70', + 'opacity-80', + 'opacity-90', + 'opacity-100', +]; + +const margins = ['0', '0.3rem', '1rem']; + +interface Props { + class?: string; + bands?: number; +} + +const { bands = 40 } = Astro.props; +//const validBands = Number.isInteger(bands) && bands > 0 ? bands : 5; // Ensure it's a valid number +const slices = Array.from({ length: bands }).map((_, i) => { + const opacityI = + i < bands / 2 + ? Math.ceil(i / 2) + : bands - i < opacity.length + ? bands - i + : Math.floor((Math.random() * opacity.length) / 2 + opacity.length / 2); + + const anim = Math.floor(Math.random() * anims.length); + + return { + animationDelay: anims[anim][0], + animationDuration: anims[anim][1], + opacity: (opacityI + 1) / 15, + marginRight: margins[Math.floor(Math.random() * margins.length)], + height: heights[Math.floor(Math.random() * heights.length)], + width: widths[Math.floor(Math.random() * widths.length)], + }; +}); +--- + +
+
+ {slices.map((slice, index) => ( +
+ ))} +
+
+ + diff --git a/docs/src/components/Header.astro b/docs/src/components/Header.astro new file mode 100644 index 0000000..1601c6c --- /dev/null +++ b/docs/src/components/Header.astro @@ -0,0 +1,104 @@ +--- +import config from 'virtual:starlight/user-config'; + +import LanguageSelect from 'virtual:starlight/components/LanguageSelect'; +import Search from 'virtual:starlight/components/Search'; +import SiteTitle from 'virtual:starlight/components/SiteTitle'; +import SocialIcons from 'virtual:starlight/components/SocialIcons'; +import ThemeSelect from 'virtual:starlight/components/ThemeSelect'; +import GithubRelease from './githubRelease.astro'; + +/** + * Render the `Search` component if Pagefind is enabled or the default search component has been overridden. + */ +const shouldRenderSearch = + config.pagefind || config.components.Search !== '@astrojs/starlight/components/Search.astro'; +--- + +
+
+ +
+
+ {shouldRenderSearch && } +
+
+ +
+ +
+ + +
+
+ + diff --git a/docs/src/components/ReadMore.astro b/docs/src/components/ReadMore.astro new file mode 100644 index 0000000..ccd33ca --- /dev/null +++ b/docs/src/components/ReadMore.astro @@ -0,0 +1,24 @@ +--- +import { Icon } from '@astrojs/starlight/components'; +--- + +
+ + +
+ + \ No newline at end of file diff --git a/docs/src/components/about.astro b/docs/src/components/about.astro new file mode 100644 index 0000000..cdb9738 --- /dev/null +++ b/docs/src/components/about.astro @@ -0,0 +1,81 @@ +--- +interface Props { + title?: string; +} + +const { title = "Powered by Platform9" } = Astro.props; +--- + +
+ +
+ + \ No newline at end of file diff --git a/docs/src/components/githubRelease.astro b/docs/src/components/githubRelease.astro new file mode 100644 index 0000000..4f70c44 --- /dev/null +++ b/docs/src/components/githubRelease.astro @@ -0,0 +1,110 @@ +--- +import '../styles/custom.css'; +// Fetch releases on the server-side +const fetchReleases = async () => { + const latestVersion = [{ + id: 0, + name: "v0.1.7", + html_url: "https://github.com/platform9/vjailbreak/releases/tag/v0.1.7" + }]; + + try { + const response = await fetch('https://api.github.com/repos/platform9/vjailbreak/releases'); + if (!response.ok) { + console.error("Failed to fetch releases from GitHub."); + return latestVersion; + } + const data = await response.json(); + return data.slice(0, 5); + } catch (error) { + console.error("Error fetching releases:", error); + return latestVersion; + } +}; + +const releases = await fetchReleases(); +const latestRelease = releases[0]?.name || 'No releases available'; +const baseURL = Astro.site || '/'; // Fallback to '/' if Astro.site is undefined +--- + + +
+ + +
+ \ No newline at end of file diff --git a/docs/src/components/slackInvite.astro b/docs/src/components/slackInvite.astro new file mode 100644 index 0000000..a0bb22b --- /dev/null +++ b/docs/src/components/slackInvite.astro @@ -0,0 +1,63 @@ +--- +import { Icon } from '@astrojs/starlight/components'; +import '../styles/custom.css'; +--- + +
+ + + + + + + + +
+ + \ No newline at end of file diff --git a/docs/src/content.config.ts b/docs/src/content.config.ts new file mode 100644 index 0000000..d9ee8c9 --- /dev/null +++ b/docs/src/content.config.ts @@ -0,0 +1,7 @@ +import { defineCollection } from 'astro:content'; +import { docsLoader } from '@astrojs/starlight/loaders'; +import { docsSchema } from '@astrojs/starlight/schema'; + +export const collections = { + docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), +}; diff --git a/docs/src/content/docs/architecture/architecture-overview.md b/docs/src/content/docs/architecture/architecture-overview.md new file mode 100644 index 0000000..876e678 --- /dev/null +++ b/docs/src/content/docs/architecture/architecture-overview.md @@ -0,0 +1,10 @@ +--- +title: Overview +description: Overview of vJailbreak architecture +--- + + +Below is high level architecture of how vJailbreak works. vJailbreak runs +in a virtual machine in the target OpenStack environment. vJailbreak connects with VMware environment via vSphere APIs, using the VDDK library for the Standard copy method only. vJailbreak Accelerated Copy and Storage-Accelerated Copy transfer disk data without requiring VDDK. It also uses the OpenStack SDK to interact with the OpenStack environment and perform the necessary provisioning operations including creation of volumes, VMs. + +![vJailbreak Architecture](/vjailbreak/images/deployment-architecture.png) \ No newline at end of file diff --git a/docs/src/content/docs/architecture/components.md b/docs/src/content/docs/architecture/components.md new file mode 100644 index 0000000..d24befc --- /dev/null +++ b/docs/src/content/docs/architecture/components.md @@ -0,0 +1,21 @@ +--- +title: Components +description: Overview of vJailbreak components +--- + + +Below is an overview of each component and its role in the migration process. + +### v2v-helper +The `v2v-helper` is the main application responsible for executing the migration process. It is designed to run as a pod within the vJailbreak virtual machine (VM) in the target OpenStack environment. + +### UI +The `UI` component provides a user-friendly interface for vJailbreak. It allows users to manage and monitor the migration process through an intuitive graphical interface. + +### migration-controller +The `migration-controller` is a Kubernetes controller that schedules and manages the migration tasks. It ensures that migrations are executed efficiently and in accordance with the defined policies. + +### v2v-cli +The `v2v-cli` is a command-line interface tool that can initiate the migration process. While it is available, it is not required in the current version of vJailbreak, as the primary interface is the UI. + +By understanding these components, users can better appreciate the architecture and functionality of vJailbreak, enabling them to effectively manage and execute VM migrations. \ No newline at end of file diff --git a/docs/src/content/docs/architecture/vjailbreak-vm.md b/docs/src/content/docs/architecture/vjailbreak-vm.md new file mode 100644 index 0000000..8ac2f3d --- /dev/null +++ b/docs/src/content/docs/architecture/vjailbreak-vm.md @@ -0,0 +1,13 @@ +--- +title: vJailbreak VM +description: Overview of vJailbreak VM +--- + +As part of the deployment process, vJailbreak is shipped as a virtual machine (VM) that can be deployed in the target OpenStack environment. The VM is configured with the necessary resources, including sufficient memory and processing power, to handle the migration tasks efficiently. + +The vJailbreak VM comes with a [k3s](https://k3s.io/) deployment that runs various components as pods. The components include the v2v-helper, UI, and migration-controller as described in the [components](../components/). + +The vJailbreak VM can be scaled out to handle multiple migration tasks concurrently. This is achieved by deploying additional vJailbreak VMs in the target OpenStack environment. For more information on scaling vJailbreak, see the [scaling](../guides/scaling/) guide. + +![vJailbreak VM](/vjailbreak/images/vjb-internal.png) + diff --git a/docs/src/content/docs/archives/release_notes.md b/docs/src/content/docs/archives/release_notes.md new file mode 100644 index 0000000..ebf0a2a --- /dev/null +++ b/docs/src/content/docs/archives/release_notes.md @@ -0,0 +1,967 @@ +--- +title: Archived Releases +description: Archived Release Notes for vJailbreak +--- + +## v0.1.2 +### What's Changed +* Upgrade min version to v0.1.1 by @tanaypf9 in https://github.com/platform9/vjailbreak/pull/71 +* Use version as tag for release event by @tanaypf9 in https://github.com/platform9/vjailbreak/pull/72 +* Added React Query to cache data, refactored MigrationForm component, and added error handling to MigrationForm by @knnguy in https://github.com/platform9/vjailbreak/pull/69 +* Add FAQs by @tanaypf9 in https://github.com/platform9/vjailbreak/pull/74 +* Fix for incomplete transfer of changed blocks by @tanaypf9 in https://github.com/platform9/vjailbreak/pull/75 +* Add Video Demo by @tanaypf9 in https://github.com/platform9/vjailbreak/pull/73 +* Add vCenter role perms and port req by @tanaypf9 in https://github.com/platform9/vjailbreak/pull/76 +* Adding additional network port prereqs for NBDkit by @jeremymv2 in https://github.com/platform9/vjailbreak/pull/77 +* replaced vCenter Role permissions with more comprehensive list from R… by @jeremymv2 in https://github.com/platform9/vjailbreak/pull/78 +* update README with table for prereqs by @jeremymv2 in https://github.com/platform9/vjailbreak/pull/79 +* Add debug logs to v2v-helper by @tanaypf9 in https://github.com/platform9/vjailbreak/pull/80 +* UI: Fixes by @knnguy in https://github.com/platform9/vjailbreak/pull/87 +* Remove unused VMInfo field by @tanaypf9 in https://github.com/platform9/vjailbreak/pull/82 +* Add ephemeral storage req and limits by @tanaypf9 in https://github.com/platform9/vjailbreak/pull/119 +* Compress nmcli scripts by @tanaypf9 in https://github.com/platform9/vjailbreak/pull/120 +* Wait for volume mount for up to a minute by @tanaypf9 in https://github.com/platform9/vjailbreak/pull/121 +* Disable healthchecks by default by @tanaypf9 in https://github.com/platform9/vjailbreak/pull/122 +* Update healthcheck in README by @tanaypf9 in https://github.com/platform9/vjailbreak/pull/124 +* Mapped OS_PROJECT_DOMAIN_NAME to OS_DOMAIN_NAME and OS_PROJECT_NAME t… by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/130 +* add IpAddress and VM state info to migrationtemplate status by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/133 +* 106 - disabled stopped VMs from migrating and notified with a tooltip by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/136 +* Allowed one to many network mappings from VMware to Openstack by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/137 +* UI: If the form is not submitted CRs created, should be deleted by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/139 +* UI: Handle VM refresh with additional put call to migration template and add adjust the timeout needed for it to reflect latest detail by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/142 +* check if disks have os installed in lexical order (release) by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/140 + +### Known Issues +* If the VM to be migrated has an LV spanning multiple physical devices used as boot volume unless both are mounted simultaneously to the OS-VM, vJailbreak cannot detect whether it is bootable, or not. ([issue link](https://github.com/platform9/vjailbreak/issues/146)) +* If a user turns on a VM on VCenter after migration and tries to migrate it again, the migration object will not be created. In this case, the user should delete the VM from PCD/Openstack before trying the migration again. This error will be pushed up the stack for visibility. + +### New Contributors +* @patil-pratik-87 made their first contribution in https://github.com/platform9/vjailbreak/pull/130 +* @OmkarDeshpande7 made their first contribution in https://github.com/platform9/vjailbreak/pull/133 + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.1...16.01 + +## v0.1.3 +### What's Changed +* Fix ui to accept OS_INSECURE from rc file by @spai-p9 in https://github.com/platform9/vjailbreak/pull/177 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.2...v0.1.3 + +## v0.1.4 +### What's Changed +* Fix GH workflow runs on release event by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/147 +* workflow dispatch manual by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/148 +* Update README.md by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/149 +* Update model.ts by @markp93 in https://github.com/platform9/vjailbreak/pull/152 +* Added delete migration functionality to the migration table by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/155 +* Adding workers nodes to the vjailbreak by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/161 +* update package.json by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/172 +* Update issue templates by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/158 +* Update bug_report.yaml by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/175 +* Migration data copy progress % and migration phases by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/167 +* Nit: consistent referring to company VMware by @ericwb in https://github.com/platform9/vjailbreak/pull/164 +* Update README.md by @markp93 in https://github.com/platform9/vjailbreak/pull/170 +* release version v0.1.4 (release) by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/196 + +### New Contributors +* @markp93 made their first contribution in https://github.com/platform9/vjailbreak/pull/152 +* @ericwb made their first contribution in https://github.com/platform9/vjailbreak/pull/164 + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.3...v0.1.4 + +## v0.1.5 +### What's Changed +* Accept vmware and openstack creds via secert by @spai-p9 in https://github.com/platform9/vjailbreak/pull/228 +* Create and Reuse existing open stack and vmware creds with other mino… by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/237 +* Support OS installed on LV by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/227 +* Adding templates for github wiki by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/226 +* resolving lint issues by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/239 +* resolving lint issues by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/240 +* update image tag to v0.1.5 by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/242 +* Base documentation update for vJailbreak by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/243 +* Update checkout action to generate release notes by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/249 +* Update checkout action to generate release notes by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/250 +* Ingress changes to call k8s apis by @spai-p9 in https://github.com/platform9/vjailbreak/pull/252 +* Reformatted readme by @damian-pf9 in https://github.com/platform9/vjailbreak/pull/206 + +### New Contributors +* @damian-pf9 +* @anmolsachan +* @bhavin192 + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.4...v0.1.5 + +## v0.1.6 +### What's Changed +* Update the correct gh-pages by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/265 +* Update the correct icon for gh-pages by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/266 +* Update the correct version for gh-pages by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/267 +* Bug :: #269 :: Unable to use uppercase letters in a credential's name by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/273 +* fix insecure openstack authentication (release) by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/278 +* Fixed issues with openstack cred creation + validation by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/280 + +### New Contributors +* @AbhijeetThakur made their first contribution in https://github.com/platform9/vjailbreak/pull/273 + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.5...v0.1.6 + +## v0.1.7 +### What's Changed +* Update video link to latest by @anmolsachan in https://github.com/platform9/vjailbreak/pull/290 +* fix readme for openstackcreds and vmware creds by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/288 +* ( release ) Change base image to ubuntu base image, reduced image size. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/286 +* Ignore Failed phase on retry by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/289 +* api cosmetic fixes by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/274 +* Support rollback for failed VM by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/300 +* check VM status before marking migration as complete by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/295 +* UI: Api cosmetic changes UI by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/297 +* UI: Fix active migration by @spai-p9 in https://github.com/platform9/vjailbreak/pull/304 +* fix mocks, unit tests by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/308 +* block openstackcreds deletion for master creds by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/303 +* UI: Implement a new credential addition workflow, and remove the creds addition from MigrationForm and Scaleup drawer by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/298 +* Easy debug config by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/287 +* resolved lint issues by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/311 +* 270 identically named source destination credentials arent displayed properly by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/312 +* Actions enhancement: Build and push on every PR raised to main or release branch. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/313 +* Heavily re-organized docs pages by @damian-pf9 in https://github.com/platform9/vjailbreak/pull/309 +* Update the docs github page by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/314 +* Sync VDDK across agents without any manual intervention by @spai-p9 in https://github.com/platform9/vjailbreak/pull/296 +* Fix versions and github actions by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/316 +* Fixed regression while updating cosmetic api changes, virtualmac… by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/317 +* 0.1.7 release fixes by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/318 +* Build: Fix CGO Linking for libnbd Library in Build Process (release) by @spai-p9 in https://github.com/platform9/vjailbreak/pull/315 +* fix migration status with random pod ref name by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/320 +* UI: Disable scale down of a node when active migration is going on by @spai-p9 in https://github.com/platform9/vjailbreak/pull/319 +* release: Fix github action when PR raised from release branch by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/323 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.6...v0.1.7 + +## v0.1.8 +### What's Changed +* Release v0.1.7 by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/322 +* Update release notes by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/326 +* Updating the video by @roopakparikh in https://github.com/platform9/vjailbreak/pull/342 +* Update the readme to point to the 'Getting Started' by @roopakparikh in https://github.com/platform9/vjailbreak/pull/349 + +### New Contributors +* @roopakparikh made their first contribution in https://github.com/platform9/vjailbreak/pull/342 + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.6...v0.1.8 + +## v0.1.9 +### What's Changed +* Backend: Add check if datacenter exists by @spai-p9 in https://github.com/platform9/vjailbreak/pull/368 +* Proxy env injection via configmap by @spai-p9 in https://github.com/platform9/vjailbreak/pull/364 +* removed creds from logs and Made changes to match OS_INSECURE value t… by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/379 +* vddk files check before migration ( release ) by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/375 +* Damian doc updates by @damian-pf9 in https://github.com/platform9/vjailbreak/pull/377 +* Doc: Inject any environment variables required by user into v2v-helper pod. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/381 +* Backend: Change ownership of /home/ubuntu/vmware-vix-disklib-distrib to ubuntu:ubuntu via init container. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/378 +* ensure correct permissions on shared vmwarelib directory for rsync ac… by @spai-p9 in https://github.com/platform9/vjailbreak/pull/383 +* Scaling doc update by @damian-pf9 in https://github.com/platform9/vjailbreak/pull/391 +* Backend: Remove logs in vmwaremachines by @spai-p9 in https://github.com/platform9/vjailbreak/pull/390 + +### New Contributors +* @sarika-p9 made their first contribution in https://github.com/platform9/vjailbreak/pull/379 + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.8...v0.1.9 + +## v0.1.10 +### What's Changed +* added os icons in vms list table in Migrations by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/431 +* Backend change to block selection of vms without appropriate flavors in openstack by @spai-p9 in https://github.com/platform9/vjailbreak/pull/424 +* Backend:Retrieve correct network names from backing Network references instead of device summary by @spai-p9 in https://github.com/platform9/vjailbreak/pull/432 +* Backend: Improve the error handling and event reporting on these failures. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/411 +* Backend: Preserve the job logs. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/435 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.9...v0.1.10 +## v0.1.11 + +### What's Changed +* added swagger ui for v0.1.9 and v0.1.10 by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/439 +* counter output for incremental copy by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/451 +* automated swagger ui at every push by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/450 +* Migration stuck in Pending on UI by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/459 +* Wait for volume attachements also to clear out. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/461 +* v2v-helper: Add check if subnet exists to avoid panic. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/460 +* enable openstack re-auth by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/464 +* use flavor id instead of name in label by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/465 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.10...v0.1.11 +## v0.1.12 + +### What's Changed +* save migration debug logs at hostPath '/var/log/pf9' ( release ) by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/479 +* Add ons in v2v-helper for post-migration actions by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/490 +* Updated documentation for all external connectivity required for vjailbreak by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/467 +* Sync migration logs from worker to master ( release ) by @spai-p9 in https://github.com/platform9/vjailbreak/pull/494 +* User should be able to delete migration when in pending or any stage by @spai-p9 in https://github.com/platform9/vjailbreak/pull/511 +* added the admincutover option to the migration strategy by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/514 +* disabled migration form submission on enter key when text inputs are … by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/515 +* added insecure option to the vmware creds form by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/516 +* Add check from nova side also for volume dettach by @spai-p9 in https://github.com/platform9/vjailbreak/pull/480 +* Document: Add cbt privilege and flavor selection logic in Doc. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/502 +* Increase timeout for vm status to become active by @spai-p9 in https://github.com/platform9/vjailbreak/pull/525 +* create delete update vmwaremachines by @spai-p9 in https://github.com/platform9/vjailbreak/pull/523 +* fix build issue. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/526 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.11...v0.1.12 +## v0.1.13 + +### What’s Changed +* vPwned: Rolling Conversion (release) by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/546 +* uncomment build-vpwned action by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/552 +* added the functionality to validate and assign/edit ips to/of the vms for rolling conversion by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/567 +* added the target cluster selections for migrations by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/573 +* convert PCD Cluster name to k8s compatible by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/576 +* fix issues when VM is attached to same network twice by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/579 +* Release v0.1.13 (release) by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/581 +* Add proxy for vjailbreak by @spai-p9 in https://github.com/platform9/vjailbreak/pull/562 +* add/update/edit os and flavors in vms list in rolling conversion by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/586 +* Add alpine docker image with rsync,coreutils in quay by @spai-p9 in https://github.com/platform9/vjailbreak/pull/590 +* DOC: Add doc for virtio drivers injection by the user by @spai-p9 in https://github.com/platform9/vjailbreak/pull/589 +* change from cloud-ctl to pcdctl by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/599 +* Change the base ubuntu image for vJailbreak qcow by @spai-p9 in https://github.com/platform9/vjailbreak/pull/598 +* Fix scale up by @spai-p9 in https://github.com/platform9/vjailbreak/pull/601 +* Fix cluster name by @spai-p9 in https://github.com/platform9/vjailbreak/pull/602 +* fix openstack volume sizes being smaller than original disk size by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/558 +* improved guestfish logging by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/565 +* Rolling Conversion UI changes by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/548 +* fix debug log file for migration re-runs by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/560 +* move os family selection to per VM basis by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/568 +* save disk space on vjb VM by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/571 +* Prebake components by @spai-p9 in https://github.com/platform9/vjailbreak/pull/574 +* Give users option to upload virtio drivers to a path in master and then propogate it down to agents by @spai-p9 in https://github.com/platform9/vjailbreak/pull/575 +* make delete migration-controller-manager pod work by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/572 + +#### Usability Enhancements and Stability Improvements + +- **Rolling Conversion (Beta Availability)** + The Rolling Conversion feature is now available as a beta release. + +- **Pre-Packaged vJailbreak Image** + A new vJailbreak image is now available, containing all required components pre-installed. This enhancement eliminates the need for internet access during installation, making it suitable for restricted network environments. + +- **Support for Custom VirtIO Drivers** + Users may now upload VirtIO drivers to a designated path. If present, these drivers will be utilized during the migration process. If absent, the system will default to downloading the necessary drivers from the internet, providing flexibility based on deployment conditions. https://github.com/platform9/vjailbreak/blob/main/docs/src/content/docs/guides/virtio_doc.md + +- **Resolution of Pod Eviction Due to Disk Pressure** + An issue causing pod eviction under disk pressure conditions has been resolved. This fix improves the reliability and stability of workloads during extended migration operations. + +- **Enhanced Debug Logging** + Debug logs have been improved to provide more comprehensive and structured output + + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.12...v0.1.13 +## v0.1.14 + +### What's Changed +* revert the changes for duplicate networks by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/605 +* accept os_family correctly and override if present by @spai-p9 in https://github.com/platform9/vjailbreak/pull/607 +* support migration without a cluster on pcd by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/608 +* do not fail migrations for snapshot delettions by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/611 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.13...v0.1.14 +## v0.1.15 + +### What's Changed +* fix part-to-dev input by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/614 +* Fix RC file parsing to support special characters in OpenStack credentials. by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/619 +* Made changes to the desination cluster selection by showing cred name… by @patil-pratik-87 n https://github.com/platform9/vjailbreak/pull/634 +* pcdclusters and tenant in UI (release) by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/633 +* Destination cluster dropdown changes for same id by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/636 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.14...v0.1.15 +## v0.2.0 + +### What's Changed +* refactor: unify release notes workflow for both PR merges and direct releases by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/631 +* Update release notes for v0.1.14 by @github-actions[bot] in https://github.com/platform9/vjailbreak/pull/645 +* Update release notes for v0.1.15 by @github-actions[bot] in https://github.com/platform9/vjailbreak/pull/646 +* PR description for testing by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/647 +* configmap for current vjailbreak version (release) by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/632 +* Update docs from gh-pages by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/649 +* Updated VMware custom resource to capture RDM disk information in VM details by @rishabh625 in https://github.com/platform9/vjailbreak/pull/563 +* sanitize kubernetes label values by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/655 +* dynamic etc host entries for controller (release) by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/663 +* Added a new sidenav by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/664 +* rolling conversion validations by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/566 +* remove docs dir and optimize API doc generation by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/652 +* #667 Fixes the Rebase Action by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/668 +* Check the target IP Allocation Pool to determine if Source VM IP is Available and Handle Port Creation 409 Conflict (release) by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/662 +* fix docs for yamls by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/670 +* Validate Openstack creds only for same env by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/681 +* Delete the mastercreds for openstack by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/678 +* Bugsnag and sidenav enahancements by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/679 +* Delete VMware credentials stuck in Unknown state by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/683 +* Detect ubuntu vm's < 17.10 and appropriately handle the networking for the interfaces by @spai-p9 in https://github.com/platform9/vjailbreak/pull/674 +* Fixed issue where sidenav collapse icon was coming on top of the Migr… by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/695 +* v2v-helper: Add sled in the checks by @spai-p9 in https://github.com/platform9/vjailbreak/pull/694 + +### New Contributors +* @rishabh625 made their first contribution in https://github.com/platform9/vjailbreak/pull/563 + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.1.15...v0.2.0 +## v0.2.1 + +### What's Changed +* Nit: readme by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/704 +* Backport: vPwned: fix condition trigger for vm migrations by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/682 +* GH actions for cross fork PRs by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/709 +* remove docker login for build only steps by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/717 +* add kubernetes dashboard by default to grafana by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/714 +* Add opensource.txt file,inject it into the vm and prebake virtio-drivers by @spai-p9 in https://github.com/platform9/vjailbreak/pull/718 +* Revert pr 662 by @spai-p9 @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/719 +* simplified the release notes workflow to update docs by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/710 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.2.0...v0.2.1 +## v0.3.0 + +### What's Changed +* RDM disk migration from VMware to Openstack on Same SAN array - if RDM is attached to single VM by @rishabh625 in https://github.com/platform9/vjailbreak/pull/654 +* persist host dns by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/735 +* fix for long vm names by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/738 +* additional vm name changes (release) by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/746 +* process single VM at a time by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/749 +* Feature: add volume backend to openstackcreds custom resource by @rishabh625 in https://github.com/platform9/vjailbreak/pull/745 +* v2v-helper: log time taken by disk copy and conversion by @jessicaralhan in https://github.com/platform9/vjailbreak/pull/737 +* throttle goroutines by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/758 +* unblock vcenter with no clusters by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/755 +* added option to select security group by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/761 +* Delete rolling migration plan by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/754 +* Reset migrated status on migration object deletion by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/744 +* configurable vjb settings (release) by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/762 +* added amplitude changes by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/765 +* Adding time elapsed for migration and cluster conversions + fixed the … by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/767 +* Improve ESXi host configuration UX by removing selection confusion by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/757 +* Disconnect all Virtual NICs on VMWare Source VM upon successful migration by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/768 +* Backend: rhel family guest network ip retention ( release ) by @spai-p9 in https://github.com/platform9/vjailbreak/pull/766 +* v0.3.0 fixes for SG and os creds validation by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/773 +* fix ui for clusters by @spai-p9 in https://github.com/platform9/vjailbreak/pull/775 +* Fix time elapsed issue by @spai-p9 in https://github.com/platform9/vjailbreak/pull/776 +* fix for wait active timeout by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/777 +* fix vpwned unavailable issue by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/779 +* idle connection timeout for vcenter by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/781 +* revert back to metadata name by @spai-p9 in https://github.com/platform9/vjailbreak/pull/780 + +### New Contributors +* @jessicaralhan made their first contribution in https://github.com/platform9/vjailbreak/pull/737 + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.2.1...v0.3.0 +## v0.3.1 + +### What's Changed +* Feature: Addition of rdm disk custom resource and controller by @rishabh625 in https://github.com/platform9/vjailbreak/pull/753 +* re-auth vcenter after timeout by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/787 +* reduce default vm scan concurrency, improve logging by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/791 +* Add fallback if /etc/os-release doesn't exist by @spai-p9 in https://github.com/platform9/vjailbreak/pull/772 +* bring back dhcp for non-matching subnets by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/795 +* amplitude and bugsnag keys (release) by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/769 +* fix vm refresh for new govomi clients by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/803 +* fix: one of the status.phase correctly renamed from pending to available by @rishabh625 in https://github.com/platform9/vjailbreak/pull/806 +* Add vjailbreak-settings.yaml ( release ) by @spai-p9 in https://github.com/platform9/vjailbreak/pull/808 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.3.0...v0.3.1 +## v0.3.2 + +### What's Changed +* handle for sles by @spai-p9 in https://github.com/platform9/vjailbreak/pull/815 +* make the power off live vms default when selected and add completed at column. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/813 +* UI: Do admin cutover via UI by @spai-p9 in https://github.com/platform9/vjailbreak/pull/810 +* Added polling for QCOW2 image before docs update by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/804 +* add a setting to keep the copied volumes in case of failure by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/823 +* optimisations for openstack creds upload time by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/829 +* fix dasboard count mismatch by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/826 +* optimise disk pressure problem due to logs by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/832 +* In-place upgrade by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/792 +* Filtering security group according to particular tenant by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/824 +* change completed at to created at by @spai-p9 in https://github.com/platform9/vjailbreak/pull/835 +* Support Dynamic Hotplug-Enabled Flavors in Target Openstack Environment by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/838 +* crd update and ui changes required for hotplug by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/842 +* indicate missing base flavor for specific vmwaremachine by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/843 +* search option(s) for faster migration triggers by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/844 +* Fix label getting triggered back to no, when patched by @spai-p9 in https://github.com/platform9/vjailbreak/pull/847 +* Push things to S3 and introduce nightly builds (release) by @sharma-tapas @sarika-p9 in https://github.com/platform9/vjailbreak/pull/793 +* search for volume types and networks by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/849 +* added alert in the ui while upgrade in progress by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/850 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.3.1...v0.3.2 +## v0.3.3 + +### What's Changed +* Fixed artifacts availability check by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/851 +* fill networks based on network devices by @spai-p9 in https://github.com/platform9/vjailbreak/pull/846 +* equate mac id case insensitive by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/857 +* get bootable index for windows LDM by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/855 +* Added MAX Keys to hotplug metadata and made it specific to PCD by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/860 +* UI and backend for multiple ip by @spai-p9 in https://github.com/platform9/vjailbreak/pull/833 +* Fix admin cutover by @spai-p9 in https://github.com/platform9/vjailbreak/pull/862 +* locked versions for nbdkit by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/865 +* fetch rpm from main by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/870 +* Fixed the os assigment in the migration form by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/867 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.3.2...v0.3.3 +## v0.3.4 + +### What's Changed +* volume wait interval through settings by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/872 +* logs all openstack calls that we make by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/873 +* admin cut over poweroff sequence prevent user error by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/874 +* Added cronjob to fetch for latest release and notify in UI by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/882 +* bugfix: Add logic to assign IP from network interfaces if not already… by @rishabh625 in https://github.com/platform9/vjailbreak/pull/895 +* create openstack ports before copy by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/879 +* Admin cutover improve the pod label watch function by @spai-p9 in https://github.com/platform9/vjailbreak/pull/881 +* remove --delete to not purge each others logs in master by @spai-p9 in https://github.com/platform9/vjailbreak/pull/904 +* Advance option in UI to ask for fallback to DHCP if static IP assignment fails by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/906 +* block on channel send to ensure initial label delivery by @spai-p9 in https://github.com/platform9/vjailbreak/pull/900 +* Support for shared pRDM disk migration in vJailbreak, including all necessary controller changes and validation through testing with clustered Windows VMs using RDM disks by @rishabh625 in https://github.com/platform9/vjailbreak/pull/858 +* Optimize OpenstackCreds reconciliation for scalability by @spai-p9 in https://github.com/platform9/vjailbreak/pull/908 +* Change implementation from select to normal blocking channel. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/917 +* bugfix: Add check for RDM disks in VM migration validation by @rishabh625 in https://github.com/platform9/vjailbreak/pull/915 +* Added option in UI to retry Failed migrations by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/918 +* changed MAAS to Bare-metal by @sarika-p9 in https://github.com/platform9/vjailbreak/pull/916 +* Added static RPMs and versionlocked in dnf by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/921 +* Add SELinux for nbdkit by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/923 +* vmdataexporter cli arg validation by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/884 +* Fixed fonts inconsistency in vJailbreak UI by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/898 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.3.3...v0.3.4 +## v0.3.5 + +### What's Changed +* Cache OpenStack instance metadata for performance and reliability by @spai-p9 in https://github.com/platform9/vjailbreak/pull/913 +* disabled upgrade button while migrations are in progress by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/934 +* disabled docs update workflow to run on release event by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/935 +* Updated CRD Upgrade logic with missing permissions by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/933 +* Bugfix: Added vmwaresession logged out, after retrieving data from vmware by @rishabh625 in https://github.com/platform9/vjailbreak/pull/939 +* Changed description of a advanced option - Fallback to DHCP by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/948 +* feat: View Migration pod logs from UI by @rishabh625 in https://github.com/platform9/vjailbreak/pull/942 +* Add support to display rdm disk and populate rdm disk details in UI by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/926 +* Refactor : VMwareMachine retrieval and validation in migration plan reconciliation by @rishabh625 in https://github.com/platform9/vjailbreak/pull/947 +* Custom header for a vjailbreak deployment through configmap by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/958 +* add creds requeue config to vjailbreak settings by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/965 +* feat: Implement RDM disks validation and query integration in migration workflow by @rishabh625 in https://github.com/platform9/vjailbreak/pull/963 +* feat: Set owner reference for RDM disks to ensure proper deletion with VMwareCreds by @rishabh625 in https://github.com/platform9/vjailbreak/pull/967 +* feat: Enhance RDM validation to include configuration checks and improve error messaging by @rishabh625 in https://github.com/platform9/vjailbreak/pull/972 +* fix: Update RDM disk migration retry logic and error handling by @rishabh625 in https://github.com/platform9/vjailbreak/pull/970 +* Add a default password for Ubuntu user that needs to be changed on first boot by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/976 +* List udev/fstab rules to preserve device mapping after migrations by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/905 +* Disable selection of older date and time while scheduling cutover by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/960 +* tls: remove cert trust logic, rely on OS_INSECURE for skipping verification by @jessicaralhan in https://github.com/platform9/vjailbreak/pull/863 +* change controller rollout policy to re-create by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/979 +* Fixed the bug where the script was not passed to the backend + update… by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/980 +* feat: Enhance RDM disk info population by validating disk name before updating volume reference by @rishabh625 in https://github.com/platform9/vjailbreak/pull/984 +* disabled log icon in ui (release) by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/986 +* disabled rdm config in UI (release) by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/987 +* enable k3s encryption at rest by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/990 +* Added htpasswd based authentification for ui by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/994 +* cache cred info for UI by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/996 +* Removed secrets GET calls from UI by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/995 +* Added command line utility to change and manage the user credentials for UI by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1002 +* bugfix: add limit to log streaming - limit number of bytes by @rishabh625 in https://github.com/platform9/vjailbreak/pull/988 +* Implemented automated HTTPS with Cert-Manager (release) by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1005 +* removed nonce from CSP by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1006 +* Vjbctl - utility to add and manage users by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1003 +* Vjbctl by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1010 +* cronjob fix by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1013 +* Add host entries to migration-vpwned-ingress by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1015 +* Implemented HTTPS with Cert-Manager by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1021 +* Updated default username from ubuntu to admin by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1023 +* added --no-restart to the utility to vjbctl by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1024 +* fixed count of selected vm by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1022 +* Custom title on browser tab for each vjailbreak VM deployment by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1026 + +### New Contributors +* @meghansh-pf9 made their first contribution in https://github.com/platform9/vjailbreak/pull/958 + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.3.4...v0.3.5 +## v0.3.6 + +### What's Changed +* fix - Migration only takes the first subnet while creating the ports by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1019 +* Bugfix: sync annotation from VMware to RDM disk openstack volume reference if changed by @rishabh625 in https://github.com/platform9/vjailbreak/pull/993 +* Added vjailbreak-settings ConfigMap and implement RDM owner VM validation toggle by @rishabh625 in https://github.com/platform9/vjailbreak/pull/953 +* bugfix: previous PR introduced a issue where importToCinder was not g… by @rishabh625 in https://github.com/platform9/vjailbreak/pull/1033 +* Fix - blocking port creation if dhcp is selected but VM doesn't have a valid IP by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1047 +* Version Checker Cronjob exits gracefully in air-gapped environments by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1045 +* enabled rdm configuration button by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1041 +* bugfix: fix ImportToCinder to true from migrationplan controller by @rishabh625 in https://github.com/platform9/vjailbreak/pull/1038 +* Added filter on migrations based on current status and current creation time by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/944 +* Blocked migration of VM having unknown OS by @patil-pratik-87 in https://github.com/platform9/vjailbreak/pull/1042 +* Skip CopyingChagedBlocks phase when migration type is cold by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1055 +* fix: changing from patch to update while modifying importToCinder fieeld of RDM CR, adding idempotent guard and retry on conflict.. by @rishabh625 in https://github.com/platform9/vjailbreak/pull/1054 +* delete firstboot configmap when migration is deleted by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1071 +* Sv/issue 920/enhancements log collector bundle by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1063 +* added FQDN tooltip for better user guidance by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1069 +* bugfix: Handle VMware login failures gracefully without requeuing by @rijojohn85 in https://github.com/platform9/vjailbreak/pull/1061 +* Admin Cutover Periodic sync by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1056 +* fix - Delay in password expiration after creating the vjailbreak vm upon first login by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1076 +* Implement VM OS type validation in migration plan (release) by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1044 +* Cluster-Based VM Filtering in Migration Form by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/964 +* fix(airgap): Pre-bake cert-manager images by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1087 +* Show tenant name in openstack cluster name by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1085 +* Handle inconsistent types in Host API response by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1099 +* Separate Periodic Sync Interval Configuration for different migrations by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1083 +* Add DeepWiki badge to README by @roopakparikh in https://github.com/platform9/vjailbreak/pull/1107 +* Fix: Display RDM disk sizes correctly for disks smaller than 1 GB by @rijojohn85 in https://github.com/platform9/vjailbreak/pull/1090 +* fix: issue-1073 Removing password log and replacing with "REDACTED" echo by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1064 +* #1067 :: Remove unnecessary get migration calls on UI by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1103 +* Added Pre-migration check to see if port is available before migration by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1115 +* Fix: Prevent race condition in RDMDisk controller causing duplicate Cinder volume imports by @rijojohn85 in https://github.com/platform9/vjailbreak/pull/1086 +* [UI] Need a Global settings page by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1114 +* Remove race condition in credential deletion flow by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1116 +* Removed retry on failure checkbox by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1118 +* Added powershell scripts for firstboot by @amar-chand in https://github.com/platform9/vjailbreak/pull/945 +* mark migrationplan as successful if there are no VMs to migrate by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1123 +* fix deleted creds used in migrationtemplate and migrationplan by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1127 +* Resolve race condition and remove RDM sleep delay by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1125 +* Added Retry Mechanism in Periodic Sync by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1080 +* 1017 assigned ip for all vms removed if we select os of one of the vms post assigning ip by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1128 +* Added a new option to enter Periodic Sync interval by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1126 +* add proxy env set by user in controller as well by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1135 +* do not wait for status after sumitting migrationform by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1132 +* Fix - User should not be able to submit migration if OS is not selected during cold migration by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1136 +* powering on of cold migration vms fix by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1131 +* #1104 :: Bug :: Filter box for tenant search is going out of focus by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1138 +* #989 :: Bug :: VM search box is going out of focus on start migration page by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1141 +* Implemented wait for RDM disks availability by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1142 +* fix - Periodic sync values are not being populated in the migration ConfigMap, causing periodic sync to be skipped by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1148 +* fix - Vm with multiple IPs on same MAC address (Tried with 2 IPs) are not getting 2nd IP on interface post migration by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1034 +* Added an enhancement to revalidate the creds whenever required by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1113 +* Added podfailed status check and reordered validation logic by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1151 +* #1137 :: Bug :: OpenStack “Validate IP” does not fail gracefully when receiving a 500 (Internal Server Error) response by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1147 +* #1144,#1145 :: periodic sync interval should not be a mandatory field on the migration form page by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1152 +* find the correct boot device without guestfish by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1133 +* Pass user-assigned IPs through MigrationPlan for cold migration by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1153 +* #864 :: Fixed multiple IP reset on OS selection by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1156 +* #1154 :: Convert the global settings page to a tabular format by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1162 +* Fetch resources post revalidation of credentials by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1161 +* Fix: Use proxy from ENV for creating net/http clients by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/1159 +* added edge cases for blocking plan to reconcile by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1163 +* bugfix: Add empty check by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1166 +* fix: #903 Using format() with timezone offset instead of ISOString() for a… by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1171 +* Add envFrom configmap and have logs in validate IP endpoint by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1172 +* #1129 :: Remove duplicate error text on credential creation failure by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1174 +* Format UI directory with prettier by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1170 +* Removed duplicate options in migration form by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1177 +* #1175 :: Periodic sync validation requires clicking outside the input box before the Start button becomes enabled/disabled, which leads to start migration enabled even with wrong value sometimes by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1179 +* In advanced option validation check only for granularoptions ignore rest by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1183 +* Fix sdk while adding logs and other details by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/1178 +* Added check to fallback to 5m if interval is less than 5m by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1187 +* #1181 :: UI: Move to folder cannot use anyother name, when clearing the vjailbreakedVMs it appears again by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1186 +* #1192 :: Bug: Migration Submit button is disabled when triggering admin cutover with periodic sync by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1197 +* Inject log_collector.sh into the VM by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1194 +* fix netplan upload for multi-disk VM by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1195 +* Update revalidation status correctly by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1205 + +### New Contributors +* @rijojohn85 made their first contribution in https://github.com/platform9/vjailbreak/pull/1061 + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.3.5...v0.3.6 +## v0.3.7 + +### What's Changed +* fix - Periodic Sync Migration Fails on Transient Network Errors Instead of Retrying by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1204 +* Prebaked Openstack Cli inside the vjailbreak VM by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1211 +* fix - Migration controller logs are being flooded by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1212 +* #1202 :: Bug :: Single migrationtemplate object should be created for one migrationplan (Use Dynamic Hotplug-Enabled Flavors is the advance option making post calls) by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1215 +* #1140 :: Enhancement :: Allow user input for IP's for any VM being migrated by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1217 +* Added migration pod and controller logs in the UI by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1214 +* #1040 :: Bug :: UI should block migration of VM having unknown OS (Should provide an option to select OS) by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1222 +* Backend implementation for ability to add server group to the VMs by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1219 +* Frontend for selecting server group during migration by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1220 +* [Fix] added APIReader to get RDM by @rijojohn85 in https://github.com/platform9/vjailbreak/pull/1149 +* cluster conversion fixes for static esx IPs by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1203 +* fix - Password set in cloud init is not being reflected by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1226 +* add more aggressive cleaning in v2v-helper by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1235 +* support gpu flavours by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1233 +* Show correct time elapsed during migration on ui by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1237 +* Removed fstrim by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1143 +* Reduced log verbosity for disk copy by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1249 +* Disable 'Apply Changes' button when ip has invalid format by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1251 +* fix - Can't assign dhcp IP to the migrated instance by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1234 +* Fix migration timeout for multi-attach RDM volumes by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1247 +* add setting to delete port after migration failure by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1255 +* reflect current default migration method in migration form by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1257 +* Fix batch script to properly write self-delete logic to the generated startup script by @Track2k in https://github.com/platform9/vjailbreak/pull/1213 +* add UI option to automatically run script to fix fstab entries during migration by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1258 +* feat: add a net pacakge with unit test cases by @sharma-tapas in https://github.com/platform9/vjailbreak/pull/1160 +* Added script to check disks status in Windows post-migration by @amar-chand in https://github.com/platform9/vjailbreak/pull/1167 +* fix agents tenant and security groups by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1260 +* fix multiple migration succeeded events in amplitude by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1262 +* fix - Migration fails when port already exist on pcd side (DHCP=true) by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1279 +* Setting up the default password for the agent nodes by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1281 +* Fixed default migration-method to cold and map with global settings by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1284 +* Updating CRDs for release by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1285 +* Populate metadata in extra specs in openstack creds by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1286 + +### New Contributors +* @Track2k made their first contribution in https://github.com/platform9/vjailbreak/pull/1213 + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.3.6...v0.3.7 +## v0.3.8 + +### What's Changed +* Implement Session Timeout (18 hours inactivity) for UI by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1264 +* install vjbctl binary system wide by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1269 +* Make datacenter field as an optional entry in vmware creds form by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1256 +* #1227, #1224 :: Create a Common Component Library for Consistent UI Across the Application by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1297 +* support additional machine states, optional set to PXE boot by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1301 +* fix - Agent Node not prompting to change password by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1310 +* fix errored agent nodes deletion from PCD by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1303 +* Added script to preserve network interfaces by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1304 +* #1299 :: New UI – Issues and Required Fixes by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1313 +* Added advanced option in UI to select if the user wants to persist network interfaces by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1314 +* add script to check and bring the offline volumes online by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1309 +* Added a new state to show migrationplan validation failure in ui by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1298 +* fix - logs for persistance script showing errors by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1317 +* Add endpoint to take proxy variable, update the cm and restart controller. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1316 +* Fix logs UI and updated CRDs by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1320 +* #1294 :: UI add proxy by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1322 +* #1294 :: UI: Add proxy with separate Http, Https field by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1325 +* added validationFailed to stop time elapsed by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1328 +* Refactored network persistance script by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1327 +* #1331 :: Show warning message on UI after saving proxy by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1332 +* no_proxy default values by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1335 +* Fixed migrationplan status based on all edge cases (combination of validationFailed, failed, suceeded, inprogress) by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1333 +* Fix - Network persistence not working for older versions of ubuntu by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1338 +* Vmware tools fix by @amar-chand in https://github.com/platform9/vjailbreak/pull/1267 +* fix - Interfaces with no ip for ubuntu older versions getting default ip by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1340 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.3.7...v0.3.8 +--- +title: v0.4.0 +description: Release Notes for v0.4.0 +--- + +### What's Changed +* Added link to public documentation in the ui by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1345 +* #1336, #834 :: UI implement feedback changes by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1346 +* Implement Storage SDK Foundation (#1091 #1093) - PART1 by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1291 +* continues the array-accelerated migration work by implementing the storage array gRPC server layer by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1350 +* Storage SDK Foundation - Part 3 -- removed all UI changes by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1353 +* Upload VDDK via UI. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1319 +* fix: Make VCENTER_DATACENTER optional in secret by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1356 +* Reverting 3 commits that introduced breaking changes by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1357 +* storage array integration by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1361 +* Added migration details modal by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1355 +* remove stale folders from the project by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1359 +* Handled trailing slash in vCenter URL by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1349 +* VDDK upload by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1364 +* Added more relevant option in MigrationForm by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1347 +* Bug: Check for empty arraycreds secret ref when normal migration and change names. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1369 +* Restrict retry on rdm migration by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1365 +* Added validation for hotplug flavor discovery by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1360 +* merge two common folders for better code structure by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1379 +* Added return to avoid segmentation fault or panic by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1387 +* VM Migrations stats and alerts on grafana dashboards by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1362 +* #1368, #1367 date time selection is not working for scheduled cutover and copy start time by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1384 +* feat: adding vddk upload status api by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1377 +* deterministic volume ordering during copy by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1389 +* UI: Create UI for storage accelerated copy. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1378 +* Added missing logs for better debugging by @geet-pf9 in https://github.com/platform9/vjailbreak/pull/1401 +* feat: adding vddk version in the api by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1397 +* #731 :: [UI] add prompts for missing VDDK, credentials by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1372 +* Fix - windows 2022 not able to recognise the network interfaces by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1382 +* pass vminfo by reference and improve sync error handling by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1391 +* Reset currentState to initial in case of error and success by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1404 +* Implemented OpenStack token-based authentication by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1394 +* feat: adding fuzzy search by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1392 +* Add VM power status check after poweroff by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1405 +* Make request limit configurable and increase limit to 5Gib by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1407 +* feat: Adding netapp storage.go by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1399 +* Mark migrations ValidationFailed when RDM disk is not managed by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1406 +* Disabled hour-time-picker for scheduled cutover if minutes are disabled by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1415 +* Fix issues in vddk upload page by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1418 +* fix: added buildBackendToVolumeTypeMap function and cleanup logic by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1422 +* #1424 :: The screen flickers first time when clicking on other Global Settings tabs while the VDDK upload is in progress or uploaded by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1429 +* #1434 :: Add ESXi SSH Private Key Input Field to Storage Array Credentials Page by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1435 +* Prioritizing Cold Migration in case of cutover options selected with cold migration by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1439 +* #1444 :: Couldn't upload ssh key without an extension by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1447 +* Throttling the go routines during copying changed block stage by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1446 +* UI :: #1438 :: Admin cutover + cold migration is not a valid usecase by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1441 +* #1430 :: Updating the vJailbreak settings through the UI removes ConfigMap keys that are not shown in the UI by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1436 + +### New Contributors +* @geet-pf9 made their first contribution in https://github.com/platform9/vjailbreak/pull/1401 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.3.8...v0.4.0 + +--- +title: v0.4.1 +description: Release Notes for v0.4.1 +--- + +### What's Changed +* upgarde core components by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1458 +* Network Persistence For Windows by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1408 +* increase QPS for kubernetes client in proxy pod by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1460 +* Fixed - v2v build failure by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1471 +* Added s3 URL artifact to build outputs (release) by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1464 +* Push image on S3 only for release, nightly and manual triggers by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1474 +* Added option to migrate a vm without powering off the source vm by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1472 +* Fixed the variable issue in v2v helper by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1476 +* Added downloadable s3 url for qcow2 images by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1475 +* #1451 :: Remove data copy method and cutover options from adavance options when user selects storage accelerated copy by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1473 +* Install latest golang and libnbd-devel in Dockerfile by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1485 +* chore: upgrade grafana by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1483 +* user experience enhancements (download logs) by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1486 +* add pre-commit hooks for lint and YAML generator by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1494 +* Use GitHub repository variables for S3 bucket names and region in CI workflow by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1488 +* Added manual pre-release workflow for CRD and installer generation by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1489 +* add backoff for migration pod checking logs by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1396 +* #1454 :: Deselecting the advanced option does not reset the UI to its original state by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1477 +* Replace pre-baked Ubuntu base image with Ubuntu Minimal and automate K3s/Helm installation by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1496 +* firstboot scheduling windows by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1500 +* Run upgrade process as a job by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1467 +* Removed extra logging by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1490 +* pin golang lint version by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1517 +* fix: continue network discovery even for orphaned networks by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1457 +* #1515 :: UI Bug: “Start Conversion” Button Not Enabled in Rolling Cluster Conversion Form by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1516 +* #1398 :: Reorganize Main Menu into Two-Level Structure by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1512 +* replace grafana yamls by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1521 +* add more printcolumns by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1522 +* Apply PostMigrationAction for all migrations in a batch by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1492 +* Support duplicate VM names by using vCenter MOID as a unique identifier by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1523 +* fix nginx grafana by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1526 +* Keep icons consistent in all the pages by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1530 +* #1450 :: UI is not allowing to delete auto discovered storage mapping by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1470 +* Revert #1523: Remove duplicate vm name changes by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1535 +* Add ssh validate CR for key validation by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1505 +* Removed extra logs showing repeated 0% by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1536 +* Add necessary tools for easier debug ability by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1538 +* Cluster Conversion: fix the capacity check for esxi hosts by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1504 +* #1506 :: Create New Page for ESXi SSH Key Management by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1539 +* fix - Firstboot script for nic recovery is not running and errors on specific windows versions by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1534 +* add esxi details by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1525 +* Resolve UI synchronization lag during Admin Cutover by refining MigrationPlan predicates by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1542 +* Introduce warning error states during periodic sync so that customer can take necessary steps by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1497 +* Feature/option not to preserve ip and mac by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1511 +* Fixed retry mechanism by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1544 +* backend volume type validations by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1498 +* fix: scope VM validation to non-terminal states and harden post-migration actions by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1549 +* Create esxi ssh cr while configuring esxi ssh key. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1558 +* bake virtio 0.1.185 for windows server 2012 by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1425 +* script to remove log files regarding vmwaretools,vmware etc by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1547 +* Added advanced option to run vmware removal script with just a check in the migration form by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1550 +* user firstboot fix by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1569 +* #1545 : UI - Redirect to the first sub item upon clicking the Item in the sidebar by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1570 +* fix: adding a timestamp measure to ensure the latest version is fetched for vddk version by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1562 +* skip deletion of vmwaremachine that was migrated and renamed by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1566 +* fix generate-mount-persistence invocation for multi-disk by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1564 +* Remove irrelevent filters on ESXi SSH Credentials page by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1567 +* #1510 :: UI Request option to NOT preserve the IP address and MAC address to allow migration to a different subnet by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1557 +* #1443 : Add Storage accelerated copy related information in migration details for storage mapping (currently showing NA) by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1571 +* UI: Fix rdm configure tab in migration form by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1576 +* #1574: Update Tour Popup Messaging for Separate VMware and PCD Pages by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1579 +* fix: on empty IP create only port group else route the standard way by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1573 +* fix: Migration phase skips CopyingChangedBlocks/ConvertingDisk after admin cutover by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1585 +* guestfish run fix, fix grub bootloader by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1586 +* Net persist patch by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1589 +* Remove 2 errors on UI for rdm form and add a warning dialog box if wrong selected by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1590 +* Injected Disk-Online script into the codebase and enhanced tool removal script for win2012 by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1578 +* fix: custom IP is not applied for no preserve IP and no preserve MAC by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1597 +* #1583 : Cannot remove assign IP post a new IP is applied by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1592 +* Include resourceVersion when updating ESXi SSH credentials by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1605 +* fix: adding dhcp search for no ip by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1481 +* Revert fix generate-mount-persistence invocation for multi-disk by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1606 +* Continue after failure Firstboot Schduling for windows firstboot by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1604 +* Interface Name preservation by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1609 +* Disable github hook on pre-release commit by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1613 +* chore: Pre-release CRD generation for v0.4.1 by @github-actions[bot] in https://github.com/platform9/vjailbreak/pull/1614 +* Use Cloud image instead of minimal ubuntu by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1618 +* Update nginx ingress and prometheus alert manager images ( release ) by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1619 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.4.0...v0.4.1 + + +#### Upgrade Guide: v0.4.0 → v0.4.1 + +Users upgrading from v0.4.0 to v0.4.1 need to follow the steps below: + +##### Step 1: Update v0.4.0 Container Images + +In your existing v0.4.0 setup, update the deployment images to pull the latest v0.4.0 containers and set `imagePullPolicy` to `Always` using the following command: + +```bash +kubectl patch deployment migration-controller-manager -n migration-system --type='json' \ +-p='[{"op":"replace","path":"/spec/template/spec/containers/0/imagePullPolicy","value":"Always"}]' && \ +kubectl patch deployment migration-vpwned-sdk -n migration-system --type='json' \ +-p='[{"op":"replace","path":"/spec/template/spec/containers/0/imagePullPolicy","value":"Always"}]' && \ +kubectl patch deployment vjailbreak-ui -n migration-system --type='json' \ +-p='[{"op":"replace","path":"/spec/template/spec/containers/0/imagePullPolicy","value":"Always"}]' +``` + +##### Step 2: Restart Deployments + +Restart the deployments to ensure the updated images are pulled and running: + +```bash +kubectl rollout restart deployment -n migration-system +``` + +##### Step 3: Follow Upgrade Steps from the [public documentation](https://platform9.github.io/vjailbreak/guides/how-to/upgrade_vjailbreak/) +## v0.4.2 + +### What's Changed +* UI access control by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1629 +* refactor for a cleaner code: consolidate go modules and constants by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1560 +* Simplify S3 QCOW2 upload paths by removing date folders by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1631 +* fix - Static network interface name is not getting preserved for Rocky9 by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1634 +* Refactor: move commonly used functions to common package by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1633 +* fix: Wait for network IP and default route before installing K3s master and worker by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1644 +* metadata as input by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1639 +* Implement independent post-migration script execution for mixed OS plans by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1638 +* #1607 : Cancel button is not working during vddk upload by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1635 +* #1575, #1577 : Start Migration is not getting disabled by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1637 +* Added checks for failure in user management vjbctl by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1659 +* Add exponential backoff auto-reconnect for migration pod log streams by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1657 +* Fix UI RBAC permissions for pod operations by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1661 +* fix: Scale up agents on L2 network by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1654 +* Detect if target is L2 network if so ignore ip by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1666 +* Added failure checks in vjbctl by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1672 +* create seperate CI to create base image, use this base image to generate qcow2 by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1677 +* Revert "create seperate CI to create base image, use this base image to generate qcow2" by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1684 +* #1543 : VM selection filter is not working properly by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1682 +* fix: Version lock ingress version by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1686 +* fix - Migrations failing with - failed to reserve ports for VM by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1688 +* #1694 : Extract only first IPv4 address when displaying and editing VM IP addresses by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1695 +* Fix: VJailbreakNode CR status was stuck at "CreatingVM" for L2 networks even after the VM joined the K8s cluster. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1693 +* Ability to reuse ports in case of l2 network by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1700 +* #1692 : Support multiple IP addresses per network interface by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1697 +* API change: multiple ips for single nic in vmwaremachines by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1696 +* Disabled Fallbacktodhcp and Securitygrp for L2 by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1705 +* #1694 : Preserve IP for a powered on VM is getting wrong IPs for no IP interfaces by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1704 +* fix agents migration for L3 by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1706 +* #1710 : Remove no ip validation in assign IP box for no ip interfaces by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1712 +* Mount persist debug log by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1711 +* Disabled assigned ip box for multiple ips by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1713 +* Defining refresh ui function for vjbctl by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1715 +* chore: Pre-release CRD generation for v0.4.2 by @github-actions[bot] in https://github.com/platform9/vjailbreak/pull/1716 +* Added a check to preserve ip by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1718 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.4.1...v0.4.2 +## v0.4.3 + +### What's Changed +* fix for no cluster filter by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1726 +* fix - Unable to migrate powered-off VM without IP address by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1732 +* Optimise VMware tool removal by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1675 +* Added dnsConfig with ndots: 1 for controller,v2v-helper and vpwned pods by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1736 +* Replaced the manual name construction with a call to resolvecinderVolumeToLun Function by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1738 +* Added delete permissions for secrets by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1740 +* uuid for image id discovery call by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1739 +* Duplicate ESXi hosts are visible for multiple VMware credentials by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1707 +* Preserved non-ASCII chars in Kubernetes secrets by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1745 +* #1656: Reset to Default button toggles off Auto Fstab Update and sets migration type to hot by default by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1734 +* #1733 : Add a toggle to pass the instance ID when creating PCD credentials on a VJB VM that was created using an L2 network by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1744 +* #1648 : Remove the N/A drop down from Storage Accelerated Copy by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1748 +* Disable security group selection for L2-enabled PCD credentials by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1757 +* Updating virt-v2v version by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1763 +* Give permission to UI user to create arraycredsmapping by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1760 +* mount point for lvm by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1599 +* Implement subnet compatibility warnings for VM network mapping by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1755 +* Agents markdown file by @roopakparikh in https://github.com/platform9/vjailbreak/pull/1507 +* Feat/support virtual portgroups by @sanya-pf9 in https://github.com/platform9/vjailbreak/pull/1730 +* v2v checks for windows by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1775 +* Add update patch list to secret for ui role by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1777 +* #1591 : Migration details in the details modal becomes NA if we remove creds and create new creds, Its reconciling with old creds by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1753 +* adding proper filtering for agent nodes by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1778 +* Added devcon utility by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1790 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.4.2...v0.4.3 + +### Release Notes & Known Issues +* Static IP and interface name preservation is not supported when the target network has DHCP disabled. +* For Windows VMs, static IP configurations are automatically converted to DHCP. However, the original source IP is retained on the destination VM to maintain network connectivity. +* The VMware Tools removal script may leave behind some residual artifacts. Refer to the [documentation](https://platform9.github.io/vjailbreak/guides/troubleshooting/vmware_residual_artifacts/#_top) for more details. +* Agent scaling is not supported for vJailbreak VMs operating on L2 networks. +## v0.4.4 + +### What's Changed +* Add offline VMware Tools cleanup and optimize firstboot script for cleaner migrations by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1779 +* Claude.md and other files for LLM integration by @roopakparikh in https://github.com/platform9/vjailbreak/pull/1690 +* Add security scan file for vulnerability scans by @hsri-pf9 in https://github.com/platform9/vjailbreak/pull/1039 +* Removing the volume attachment delay due to metadata service connectivity by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1804 +* ci: skip build-image and lint workflow by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1807 +* Support duplicate VM names by using vCenter MOID as a unique identifier by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1772 +* fix - Interface preservation not working on DHCP disabled network for windows by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1805 +* #1761 : Bugsnag not working properly by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1765 +* Fix multi-disk volume-type mismatch by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1815 +* fix: update VM identification from name to ID in VmsSelectionStep by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1817 +* validate CURRENT_INSTACE_ID is in the same env by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1816 +* #1783 : Duplicate Amplitude Events Triggered During Migration by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1791 +* Added user assigned ip per nic in backend by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1719 +* Added UI for per-NIC multi-IP overrides by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1820 +* Added workflow for old builds cleanup from quay and S3 by @geet-pf9 in https://github.com/platform9/vjailbreak/pull/1735 +* change volume attachment wait time and logs by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1819 +* Virtio installation in windows by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1824 +* Improved Mac Filtering by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1831 +* 1825 : Enhance Amplitude Events with OS Region Name from Credentials by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1835 +* feat: add UI support for setting volume metadata prior to migration by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1838 +* feat: add volume image properties prior to migration by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1834 +* Handling interfaces with no ip for rhel based os by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1842 +* UI: show selected profiles in details modal by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1841 +* Remove default profile auto-selection and fix boot volume metadata merge by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1846 +* Update portgroupkey if stale with correct live mor of the dvpg. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1836 +* improve bootable partition detection for Grub 2.12 and add script logging by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1829 +* L2 scaling support. Don't query metadataservice, get uuid from pod's nodename. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1848 +* Feature: Add FC support for pure and netapp. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1851 +* fix - Suse-11-4 - VM ends up in maintenance mode post migration by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1850 +* fix - dirty disk post migration windows by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1856 +* Pass vmkey instead of vmname to delete object by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1865 +* Map lun to igroup based on svm by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1866 +* Prevent the user from adding multiple IPs if the Preserve IP toggle is turned off by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1863 +* use custom http timeout from settings by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1860 +* Remove L2 instance ID from UI and backend by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1858 +* Fixed master instance ID issue by @geet-pf9 in https://github.com/platform9/vjailbreak/pull/1871 +* After we copy the disks via storagecopy add image tags to boot correctly by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1870 +* Bug: Use GetIsSimpleNetwork method instead of using stale env variable by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1875 +* increased version window for github tags by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1876 +* Keepalive client mechanism by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1881 + +### New Contributors +* @hsri-pf9 made their first contribution in https://github.com/platform9/vjailbreak/pull/1039 + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.4.3...v0.4.4 + +### Highlights +- **VMware Tools Removal:** The removal script now removes all residual files and folders. A small number of registry entries may remain depending on the Windows version, but these are harmless and can be removed manually if needed. +- **Migration Profiles:** Pre-defined key-value pairs can now be applied to a migrated VM's root volume in OpenStack. Profiles are supported for both Windows and Linux guests. +- **L2 Network Scaling Support:** Scaling of agents in L2 network (Simple Networking) is now supported. +- **Persistent Network Interface for Windows (2016+):** Static network configurations including interface name, IP, MAC, and gateway are now preserved across migration for Windows Server 2016 and above. +- **FC Support for Storage Area Migration (SAM):** Fibre Channel support has been added for SAM workflows, verified with NetApp FC targets. + +### Known Limitations +- **Windows 2012 and Earlier — Persist Network:** The persistent network interface option is not supported on Windows 2012 and below. IP and MAC addresses are preserved, but the interface name and gateway may get lost, and the NIC configuration becomes dynamic after migration. +- **Migration Profiles — Upgrade Path:** The two default profiles (Windows and Linux) are auto-created on fresh installations only. Users upgrading from lower versions to v0.4.4 will need to create these profiles manually. +- **VMware Tools Registry Entries:** Depending on the Windows version, a small number of VMware Tools registry entries (listed in public docs) may not be removed by the uninstall script. These are harmless but can be removed manually if desired. +- **FC SAM — Verified Target:** FC support for SAM has been verified against NetApp FC targets. Compatibility with other FC storage vendors is not yet validated. +## v0.4.5 + +### What's Changed +* Fix RDM configuration issue from UI by @geet-pf9 in https://github.com/platform9/vjailbreak/pull/1895 +* Bug: Fix unauthenticated error when doing vcenter operations after a long time. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1891 +* Fixed migration issue for shared rdm disk by @geet-pf9 in https://github.com/platform9/vjailbreak/pull/1902 +* chore: Pre-release CRD generation for v0.4.5 by @github-actions[bot] in https://github.com/platform9/vjailbreak/pull/1904 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.4.4...v0.4.5 diff --git a/docs/src/content/docs/concepts/cluster-conversion.md b/docs/src/content/docs/concepts/cluster-conversion.md new file mode 100644 index 0000000..2592327 --- /dev/null +++ b/docs/src/content/docs/concepts/cluster-conversion.md @@ -0,0 +1,35 @@ +--- +title: Cluster Conversion +description: Overview of Cluster Conversion Process +--- + +vJailbreak offers two primary migration options + +## VM Migration +The default option, where a user selects a set of VMs within VMware, across clusters and migrates them. This keeps +the source VM intact and creates a new copy of them into OpenStack/PCD. + +## Cluster Conversion +The second option, where a user selects a cluster within VMware and migrates it to a PCD cluster. This converts not only the VMs within the cluster but also the individual ESXi into PCD Hypervisor. +This is done through 'evacuating' the ESXi host and converting it into PCD hypervisor through IPMI/PXE, in the current version of vJailbreak, Canonical MAAS is used to achieve that. + +See below the animation of the cluster conversion process. + +![Cluster conversion](/vjailbreak/images/vjb-cluster-conversion.gif) + +Choose a VMware cluster and select the destination cluster in PCD. Once the clusters are selected, you will be shown the VMware ESXi hosts and the corresponding VMs. The VM portion of the wizard is similar/same as that in the [VM Migration](#vm-migration) section. + +The ESXi portion of the wizard is different from the VM portion and deals with options on how each server will be configured as a hypervisor in the PCD cluster. The most important aspect is the Host configuration, the information is pulled from the PCD cluster blueprint. The host config determines what NICs are associated with what networks. See [PCD cluster blueprint docs](https://platform9.com/docs/private-cloud-director/private-cloud-director/virtualized-cluster-blueprint) for more details. + +The process of converting the ESXi into PCD hypervisor is simple, each ESXi host is put into maintenance mode which migrates all the running VMs into other ESXi hosts. Then the ESXi host is converted into PCD hypervisor and the VMs are migrated into the PCD hypervisor. This process is repeated for all the ESXi hosts in the cluster. + +The detailed configuration steps are described in [cluster conversion guide](../../guides/cluster-conversion/cluster-conversion/). + +See below the diagram of the cluster conversion setup. + +![Cluster conversion](/vjailbreak/images/cluster-conversion-2.png) + + +:::note +The conversion of ESXi into PCD hypervisor is a one time operation and cannot be undone. All the VMs are converted into PCD VMs and requires a fully DRS cluster configuration on the VMware side. +::: \ No newline at end of file diff --git a/docs/src/content/docs/concepts/credential-management.md b/docs/src/content/docs/concepts/credential-management.md new file mode 100644 index 0000000..15e6b56 --- /dev/null +++ b/docs/src/content/docs/concepts/credential-management.md @@ -0,0 +1,174 @@ +--- +title: Credential Management +description: Overview of Credential Management +--- + +Before you start using vJailbreak, you need to provide credentials for both the VMware vCenter and OpenStack/PCD environments. + +## VMware vCenter Credentials +VMware vCenter credentials are required to connect to the vCenter server and retrieve information about the virtual machines you want to migrate. + +The credentials should have enough permissions to retrieve information about the virtual machines you want to migrate and if you are looking to the cluster conversion, the credentials should have enough permissions to retrieve information about the cluster, put host into maintenance mode, etc (see [Cluster Conversion](../../guides/cluster-conversion/) for more details). + +The VMware credentials needs vCenter Server IP address or vCenter Server name, username and password. +The credentials also take the Datacenter name and the VMs, Hosts being worked on would be restricted to the Datacenter specified in the credentials. + +Some VMware environments may be using self signed certificates, in such cases, you would need to "Allow insecure connection" option in the credentials. + + +## OpenStack/PCD Credentials +OpenStack/PCD credentials are required to create VMs inside the OpenStack/PCD environment. The credentials are supplied via the `openstack.rc` file that is available in the PCD environment. + +To copy the content of the `openstack.rc` file, you should navigate to Settings > API Access > pcdctl RC section. + +If using PCD we recommend toggling the "Is PCD credentials" option. This will automatically indicate to vJailbreak that the credentials are for PCD and would use PCD Cluster as a destination for different migrations. + +For non PCD environment the `openstack.rc` file will be available as part of various distribution and documentation. The `openstack.rc` file is typically used for any automation with the OpenStack CLI. + +--- + +### Password-Based Authentication + +#### Required Variables + +vJailbreak requires the following environment variables to be present in your admin RC file. **All of these variables are mandatory** and the migration will fail if any are missing: + +| Variable | Description | Example | +|----------|-------------|---------| +| `OS_AUTH_URL` | OpenStack Keystone authentication URL | `https://keystone.example.com:5000/v3` | +| `OS_USERNAME` | OpenStack username with admin privileges | `admin` | +| `OS_PASSWORD` | Password for the OpenStack user | `your-secure-password` | +| `OS_REGION_NAME` | OpenStack region where VMs will be created | `RegionOne` | +| `OS_PROJECT_NAME` | OpenStack project name for VM deployment | `service` | +| `OS_PROJECT_DOMAIN_NAME` | OpenStack project domain name | `Default` | +| `OS_AUTH_TYPE` | OpenStack authentication type | `password` | +| `OS_IDENTITY_API_VERSION` | OpenStack identity API version | `3` | +| `OS_USER_DOMAIN_NAME` | OpenStack user domain name | `Default` | +| `OS_INTERFACE` | OpenStack API interface type | `public` | + +#### Optional Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `OS_INSECURE` | Skip SSL certificate verification | `true` or `false` | + +#### User Permissions + +The user specified in `OS_USERNAME` must have administrative privileges in OpenStack to: +- Create and manage virtual machines +- Access network and storage resources +- Create and manage volumes +- Access compute, network, and storage services + +The project specified in `OS_PROJECT_NAME` must exist and have sufficient quotas for the VMs being migrated. + +#### Example Admin RC File (Password-Based) + +```bash +export OS_USERNAME= +export OS_PASSWORD= +export OS_AUTH_URL=https:///keystone/v3 +export OS_AUTH_TYPE=password +export OS_IDENTITY_API_VERSION=3 +export OS_REGION_NAME=region-1 +export OS_USER_DOMAIN_NAME=Default +export OS_PROJECT_DOMAIN_NAME=Default +export OS_PROJECT_NAME=service +export OS_INTERFACE=public +export OS_INSECURE=false +```` + +--- + +### Token-Based Authentication + +In addition to password-based authentication, vJailbreak also supports **token-based authentication** for OpenStack/PCD environments. + +With token-based authentication, access is provided using a **Keystone authentication token** instead of a username and password. The token must be generated in your OpenStack environment and supplied to vJailbreak via the `openstack.rc` file. + +This authentication method is useful in environments where password-based authentication is restricted or where short-lived credentials are preferred. + +#### Required Variables + +When using token-based authentication, the following environment variables **must** be present in the RC file: + +| Variable | Description | Example | +| ------------------------- | -------------------------------------------- | ------- | +| `OS_AUTH_URL` | OpenStack Keystone authentication URL | `https://keystone.example.com:5000/v3` | +| `OS_IDENTITY_API_VERSION` | OpenStack identity API version. | `3` | +| `OS_REGION_NAME` | OpenStack region where VMs will be created | `RegionOne` | +| `OS_PROJECT_NAME` | OpenStack project name for VM deployment | `service` | +| `OS_PROJECT_DOMAIN_NAME` | OpenStack project domain name | `Default` | +| `OS_INTERFACE` | OpenStack API interface type | `public` | +| `OS_AUTH_TOKEN` | Openstack authentication token | `` | +| `OS_AUTH_TYPE` | Openstack authentication type | `token` | + +#### Optional Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `OS_INSECURE` | Skip SSL certificate verification | `true` or `false` | + +#### Example Admin RC File (Token-Based) + +```bash +export OS_AUTH_URL=https:///keystone/v3 +export OS_IDENTITY_API_VERSION=3 +export OS_REGION_NAME= +export OS_PROJECT_NAME= +export OS_PROJECT_DOMAIN_NAME=Default +export OS_INTERFACE=public +export OS_AUTH_TOKEN= +export OS_AUTH_TYPE=token +``` + +#### Notes on Token-Based Authentication + +* The `OS_AUTH_TOKEN` must be generated in your OpenStack environment and must be valid at the time of migration. +* Token expiration is controlled by Keystone. If the token expires, the migration will fail and a new token must be provided. +* The `openstack.rc` must contain both the `Domain` and the `Project`/`Tenant` information. When using the OpenStack credentials, the `Domain` and `Project`/`Tenant` information is used as the destination `domain` and `project`/`tenant` for the OpenStack/PCD environment. + +### Credential Revalidation + +Revalidation re-runs the same flow that runs on credential creation. It is triggered automatically by the controller and can also be initiated manually from the UI. + +![img1](../../../../public/images/revalidate_openstack_cred.png) +![img1](../../../../public/images/revalidate_vmware_cred.png) + +#### What Revalidation Does + +When a credential is revalidated, vJailbreak performs two steps: + +1. **Authentication check** — verifies the credentials are still valid against the target environment (vCenter for VMware, Keystone for OpenStack/PCD). If authentication fails, the credential is marked invalid and migrations using it will be blocked. +2. **Resource resync** — if authentication succeeds, vJailbreak re-fetches the full inventory of resources tied to that credential. + +**For VMware credentials**, revalidation refreshes: +- Virtual Machines (CPU, memory, disks, networks, datastores, power state, guest IPs) +- vCenter clusters and ESXi hosts +- Stale VMs, clusters, and hosts removed from the source are also pruned from vJailbreak + +**For OpenStack/PCD credentials**, revalidation refreshes: +- Compute flavors +- Networks +- Volume types +- For PCD credentials: PCD clusters, hosts, and host configs + +#### When Revalidation Runs + +- **On credential creation** — initial validation + resource fetch. +- **Periodically** — the controller reconciles every 1 hour by default to keep resources in sync. Default time to requeue creds can be changed in global setting page. +- **Manually** — click the refresh button on the Credentials page to trigger an immediate revalidation. + + +#### Important: VMware IP Discovery and Credential Revalidation + +VMware Tools and guest IP addresses can take some time to appear in vCenter after powering on a VM or making network changes. + +**Best Practice:** +- Make all necessary configuration changes to your VMs in the vCenter UI first. +- **Wait** until the IP addresses are clearly visible on the VM summary page in vCenter. +- Only then click **Add Credential** or **Revalidate** in vJailbreak. + +Revalidating too early may result in missing IP information, which can prevent proper IP preservation on the destination and cause migration issues. + +> **Note**: vJailbreak will show a warning for VMs where network interfaces are detected but IPs could not be discovered. \ No newline at end of file diff --git a/docs/src/content/docs/concepts/migration-options.md b/docs/src/content/docs/concepts/migration-options.md new file mode 100644 index 0000000..7d78986 --- /dev/null +++ b/docs/src/content/docs/concepts/migration-options.md @@ -0,0 +1,112 @@ +--- +title: Migration Options +description: Overview of Different Migration Options +--- + +vJailbreak provides a number of options to optimize and control the migration process. These options are available in the migration wizard under "Migration Options". + +## Copy options +There are several options available to control how data is copied during migration. + +### Data copy method +Determines how the data copy is done + +* **Copy live VMs, then power off** - This option copies the data from the live VMs to the OpenStack/PCD volumes. vJailbreak uses CBT (Change Block Tracking) to copy the data that is dirtied. Then, the VMs are powered off and the remaining changed blocks are copied to the OpenStack/PCD volumes (see cut over options below) + +* **Power off VMs, then copy** - This option powers off the VMs and then copies the data to the OpenStack/PCD volumes. There is no CBT involved in this case, it will be the faster option, but will impact the uptime of the application. Power off VMs are supported but may need user input to provide the IP address, Operating System type during migration. + +### Storage copy method +Determines the underlying mechanism used to transfer disk data. + +* **Normal** - Uses the traditional network-based copy via VMware's NFC protocol. Data is transferred from ESXi hosts to OpenStack Cinder volumes over the network. This method is limited to approximately 1 Gbps per VMDK due to NFC protocol constraints. **Requires VDDK.** + +* **Storage-Accelerated Copy** - Leverages storage array-level XCOPY operations for dramatically faster migrations. Instead of copying data over the network, this method offloads the copy to the storage array itself. **Does not require VDDK.** Requires: + - Supported storage array (Pure Storage or NetApp) + - Both VMware datastores and OpenStack Cinder backed by the same array + - ESXi SSH access configured + - VMs must be powered off during copy (cold migration only) + +* **vJailbreak Accelerated Copy** *(default)* - Attaches frozen snapshot disks directly to a Proxy VM running in vCenter (using VMware's hot-add mechanism) and streams data over NBD to the destination. Works with any datastore type (NFS, VMFS, vSAN) and does not require a shared storage array. **Does not require VDDK.** Requires: + - A registered Proxy VM in **Ready** state (Linux VM with `qemu-nbd` installed) + - SSH access from vJailbreak to the Proxy VM + - VMs must be powered off during copy (**cold migration only; hot/live copy is not supported**) + +:::caution[vJailbreak Accelerated Copy: cold migration only] +vJailbreak Accelerated Copy powers off the source VM before attaching its disks. It **cannot** be +used with the **"Copy live VMs, then power off"** data copy method. Select **"Power off VMs, then +copy"** when using this storage copy method. +::: + +:::tip[Running without VDDK?] +If VMware's public VDDK download pages are unavailable, use **vJailbreak Accelerated Copy** or +**Storage-Accelerated Copy**; neither method requires VDDK. **Normal (Standard) copy** is the +only method that requires VDDK. +::: + +:::tip +Storage-Accelerated Copy can be 10-100x faster than normal copy for large VMs. For a 1 TB disk, normal copy takes ~2.5 hours while Storage-Accelerated Copy can complete in 5-30 minutes. +::: + +See [vJailbreak Accelerated Copy](../vjailbreak-accelerated-copy/) for configuration instructions. + +See [Storage-Accelerated Copy](../storage-accelerated-copy/) for detailed configuration instructions. + +### Data copy start time +As the name implies, determines when the copy operation should start, typically used to start the migration during off-peak hours. + +## Cutover options +There are 3 options available + +* **Cutover during time window** - This option allows the user to specify a time window during which the VM would be powered off and the corresponding OpenStack/PCD VM would be configured and powered on. This window also involves copy of any remaining changed blocks to the OpenStack/PCD volumes since the last time the block were copied. + +* **Cutover immediately after data copy** - This option is simpler and follows the copy operation immediately after the copy is complete. This option is recommended for applications that have flexible uptime requirements and can be powered off anytime during the migration. + +* **Admin initiated cutover** - This option allows the user to manually trigger the cutover operation after the data copy is complete. + +## Data Copy Method Workflows + +### 1. Power off Live VM, then Copy + +#### Immediately Cutover +The VM is powered off on the source before the initial copy begins. Once the initial copy completes, the migration immediately performs the final sync and powers on the VM at the destination. + +#### Cutover During Time Window +The VM is powered off on the source before the initial copy begins. After the initial copy completes, the migration waits for the specified time window. When the time window arrives, it performs the final sync and powers on the VM at the destination. + +#### Admin Initiated Cutover +The VM is powered off on the source before the initial copy begins. After the initial copy completes, the migration waits for manual intervention. When the admin initiates cutover, the migration performs the final sync and powers on the VM at the destination. + +> **Note:** When "Power off VMs then copy" is selected, cutover timing options (immediate, time window, or admin-initiated) are disabled in the UI. + +### 2. Copy Live VM, then Power off + +#### Immediately Cutover +The initial copy is performed while the VM remains running on the source. Once the initial copy completes, the VM is powered off on the source, and the migration immediately performs the final sync and powers on the VM at the destination. + +#### Cutover During Time Window +The initial copy is performed while the VM remains running on the source. After the initial copy completes, the migration waits for the specified time window. When the time window arrives, the VM is powered off on the source, the final sync is performed, and the VM is powered on at the destination. + +#### Admin Initiated Cutover +The initial copy is performed while the VM remains running on the source. After the initial copy completes, the migration waits for manual intervention. When the admin initiates cutover, the VM is powered off on the source, the final sync is performed, and the VM is powered on at the destination. + +## Post migration options + +### Post migration script +A script to be executed after the migration is complete. This script is optional and can be used to perform post migration tasks such as starting the application, updating the application configuration, adding VM to domain controller etc. + +### Rename VM +An optional parameter. Renames the source VM in VMware to have a specific suffix, good option to indicate a VM is migrated to OpenStack/PCD. The default suffix is "_migrated_to_pcd". + +### Move to folder +An optional parameter. Moves the source VM in VMware to a specific folder, good option to group migrated VMs and keep it out of the hands of the user. +## Network persistence + +### Persist source network interfaces +When enabled, vJailbreak preserves the source VM's network interface names on the destination VM (for example, `eth0` or `ens3`). This prevents breaking guest configurations—such as firewall rules or legacy scripts—that depend on specific interface names. + +For statically configured interfaces, vJailbreak also preserves routes defined in configuration files, ensuring the guest retains its original network behavior after migration. + +To enable this behavior, check **Persist source network interfaces** under **Migration Options** in the migration form. + + +For more information, refer to the [Network Persistence](../network-persistence/) documentation. diff --git a/docs/src/content/docs/concepts/network-persistence.md b/docs/src/content/docs/concepts/network-persistence.md new file mode 100644 index 0000000..c407742 --- /dev/null +++ b/docs/src/content/docs/concepts/network-persistence.md @@ -0,0 +1,100 @@ +--- +title: Network Persistence +description: Guide to Linux and Windows network interface persistence post-migration +--- + +# Network Persistence Post-Migration + +This document details the mechanism for ensuring network interface persistence following a virtual machine migration for both Linux and Windows operating systems. + +## Prerequisites + +Network persistence is only applied when the **"Persist source network interfaces"** option is enabled in the migration form. This option must be selected during migration configuration to ensure that network interface names are preserved on the destination VM. + +## Linux Network Persistence + +The Linux network persistence mechanism operates on the first boot post-migration to restore network configuration to its pre-migration state. + +### Persistence Mechanism + +- **Statically Configured Interfaces**: The original names of network interfaces that were statically configured before migration are preserved. +- **DHCP Configured Interfaces**: Interfaces configured via DHCP may be renamed to a consistent pattern: `vjb`. +- **Interface with No Configuration**: Interfaces that had no configuration (e.g., were left unconfigured) will remain untouched but the name may change. + +### Supported Distributions + +| Distribution | Expected | Verified | +| --- | --- | --- | +| Ubuntu | Supported | Yes | +| OpenSuse | Supported | Yes | +| RHEL | Supported | Yes | +| CentOS | Supported | Yes | +| Rocky | Supported | No | + +## Windows Network Persistence + +The Windows network persistence mechanism operates on the first boot post-migration to restore network configuration to its pre-migration state. + +### Persistence Mechanism + +The network persistence script runs on the first boot post-migration. Its primary function is to restore the network configuration to its pre-migration state by performing the following actions: + +- **Windows Server 2016 and Above**: + - **Statically Configured Interfaces**: The original interface name, IP address, and gateway from the source are persisted on the destination, ensuring continuous network connectivity. + - **DHCP Configured Interfaces**: Interfaces configured via DHCP are renamed to a consistent pattern: `vjb_`. + +- **Windows Server 2012 and below**: + - **Statically Configured Interfaces**: The IP address from the source interface is preserved, but the interface configuration is converted to DHCP. The interface name and gateway are not preserved. + +### Supported Versions + +The network persistence mechanism has been validated and is supported on the following Windows Server operating systems: + +| Version | Expected | Verified | +| --- | --- | --- | +| Windows Server 2016 | Supported | Yes | +| Windows Server 2019 | Supported | Yes | +| Windows Server 2022 | Supported | Yes | +| Windows Server 2025 | Supported | Yes | +| Windows Server 2008 | Unsupported | No | +| Windows Server 2012 | Unsupported | No | + +:::caution +**Unsupported Windows Versions** + +Windows Server 2008 and Windows Server 2012 are **not supported** for network persistence. Post-migration, VMs running these versions will receive IP addresses via DHCP on all interfaces, regardless of the original network configuration. +::: + +## User Guidance for Virtio Installation + +The Windows Virtual Machine (VM) will undergo multiple reboots during the installation of necessary virtio drivers post-migration. + +:::caution +**Crucial Action**: The user must wait for the virtio installation and subsequent reboots to complete before attempting to log in. Interrupting the installation process by logging in prematurely can lead to an inconsistent network configuration state. +::: + +## Important Considerations + +:::caution +**Important: Routing Considerations** + +If a VM has multiple interfaces on the same subnet and has asymmetric routing table, the destination openstack platform may not support it and drop the packets. This may cause partial connectivity. This is mainly observed when a VM with asymmetric routing is having port-security enabled. + +**Recommendation:** +- To avoid asymmetric routing, ensure each interface is on a unique subnet or consolidate multiple IPs onto a single port, as multiple interfaces on the same subnet will cause connectivity issues. +::: + +:::note +For DHCP-enabled ports, connectivity and DHCP functionality are preserved, but the interface name may be renamed if this feature is not selected. +::: + +:::note +For cross-network migration, network persistence is currently not supported and will be blocked. +::: + +:::note +Network persistence is applied by `virt-v2v` during conversion. Windows VMs whose +system volume is on a dynamic disk (LDM) skip conversion, so persistence does not +run for them and the interfaces must be reconfigured inside the guest. See +[Windows Dynamic Disk (LDM) Migration](../../guides/how-to/windows-ldm-migration/). +::: diff --git a/docs/src/content/docs/concepts/network-storage-mapping.md b/docs/src/content/docs/concepts/network-storage-mapping.md new file mode 100644 index 0000000..92394fd --- /dev/null +++ b/docs/src/content/docs/concepts/network-storage-mapping.md @@ -0,0 +1,59 @@ +--- +title: Network & Storage Mapping +description: Overview of Network and Storage Mapping +--- + +A large scale VMWare migration may require a large number of VMs to be migrated. In such cases, it is recommended to use network and storage mapping to optimize the migration process and keep both the environments running at the same time while migration is progressing. Network and Storage mapping are part of the migration wizard and are required for the migration to proceed. + +## Network Mapping + +vJailbreak recognizes the different types of networks in VMware and OpenStack/PCD environments. +We recommend to create the OpenStack/PCD networks in advance such that some multi-VM applications can continue to run while the migration is in progress. + +:::note +If you enable **Persist source network interfaces**, network persistence may not work for cross network migration and will be blocked in such cases. Read more in [Migration Options](../migration-options/#persist-source-network-interfaces). +::: + +### VMware Network Types +For VMware environment, the networks are typically of type `vSphere Standard Port Group` or `vSphere Distributed Port Group`. Currently, vJailbreak supports only these two types of networks and not `NSX` created networks. + +Typical VMware networks use VLAn configuration to define the network properties. + +### OpenStack/PCD Network Types +For OpenStack/PCD environment, there are many more choices, refer to the PCD and OpenStack documentation on the various provider, physical and virtual networks. + +vJailbreak expects the user to create the OpenStack/PCD networks in advance. In a typical environment for each VMware network an OpenStack/PCD physical network is created that use the corresponding VLAN of the Port Group or Distributed Port Group. + +## Storage Mapping + +Unlike network mapping, storage mapping is different. For networking, interconnectivity is the key, for storage it is not. During migration vJailbreak 'copies' the data over from VMware to OpenStack/PCD and this can be used to your advantage as needed. + +### VMware Storage Types + +For VMware environments, the storage is typically of type `vSphere Datastore` with either `VMFS` or `NFS` as the storage type. vJailbreak supports both of these storage types. + +vJailbreak also supports RDM disks for cold migrations to Platform9 Private Cloud Director (PCD) when the SAN-backed LUN can be represented as a target volume and the required storage prerequisites are met. See the [RDM migration guide](../../guides/cli-api/migrating_rdm_disk_windows_cluster_machine_using_cli/) for the step-by-step workflow. This support is not currently available for OpenStack-targeted migrations. + +There are a few exceptions and unsupported configurations. + +* vJailbreak does not support `vCenter Storage Policies` or `vVols` at the time of writing this document. +* vJailbreak does not preserve snapshots during the copy. +* For Rolling Conversion or other vMotion-based workflows, RDM disks in physical mode remain unsupported. + +### OpenStack/PCD Volume Types +For OpenStack/PCD environment, the volumes are created using 'Cinder' and can be of type `NFS` `iSCSI`, `FiberChannel`. vJailbreak supports all of these types of storage with the help of the corresponding OpenStack/PCD Cinder drivers. The Cinder and corresponding volume types must be precreated before the migration starts. + +Since the migration involves copying the data from VMware to OpenStack/PCD, the storage types can be of different types, for example `NFS` datastore volume can be copied to `iSCSI` volume in OpenStack/PCD. + +## Storage-Accelerated Copy + +For environments where both VMware and OpenStack share the same storage array (Pure Storage or NetApp), vJailbreak supports **Storage-Accelerated Copy**. This method leverages storage array-level XCOPY operations to dramatically improve migration performance by offloading the data copy to the storage array itself. + +Instead of copying data over the network (limited to ~1 Gbps per VMDK), Storage-Accelerated Copy performs the copy directly on the storage array at array speeds, which can be considerably faster than normal copy. + + +See [Storage-Accelerated Copy](../storage-accelerated-copy/) for detailed configuration and usage instructions. + +:::note +Migration involves copying of the data from VMware to PCD, depending on the bandwidth and the network congestion, the migration can take a long time and needs more resources on the vJailbreak VM. See [scaling guide](../../guides/how-to/scaling/) for parallel migrations. +::: \ No newline at end of file diff --git a/docs/src/content/docs/concepts/storage-accelerated-copy.md b/docs/src/content/docs/concepts/storage-accelerated-copy.md new file mode 100644 index 0000000..93cc4e8 --- /dev/null +++ b/docs/src/content/docs/concepts/storage-accelerated-copy.md @@ -0,0 +1,600 @@ +--- +title: Storage Accelerated Copy +description: High-performance VM migration using storage array level XCOPY operations +--- + +Storage Accelerated Copy is an advanced data copy method that leverages storage array level XCOPY operations to dramatically improve migration performance. Instead of copying data over the network via the traditional NBD/NFC protocol, this method offloads the data copy to the storage array itself, achieving significantly faster transfer speeds. + +:::note[VDDK not required] +Storage-Accelerated Copy does not require VDDK at any stage. Disk data is copied directly by the +storage array via `vmkfstools` XCOPY. VDDK is never used. This method is unaffected by VDDK +availability. +::: + +## Overview + +### How It Works + +Traditional vJailbreak migrations copy VM disk data from VMware ESXi hosts to PCD Cinder volumes over the network using the NFC (Network File Copy) protocol. This approach is limited to approximately **1 Gbps per VMDK** due to VMware's NFC protocol constraints. + +Storage-Accelerated Copy bypasses this limitation by: + +1. Creating a target volume directly on the storage array +2. Importing the volume into PCD Cinder +3. Mapping the volume to the ESXi host +4. Using ESXi's `vmkfstools` to perform an XCOPY clone operation directly on the storage array +5. The storage array handles the data copy internally. + +### Benefits + +- **Dramatically faster migrations**: Array-level copy operations are significantly faster than network-based transfers +- **Reduced network load**: Data doesn't traverse the network between VMware and PCD. +- **Lower ESXi host CPU usage**: The storage array handles the heavy lifting +- **Ideal for large VMs**: Especially beneficial for VMs with large disks (hundreds of GB to TB) + +### Requirements + +- **Supported storage arrays**: Pure Storage FlashArray or NetApp ONTAP +- **Shared storage**: Both VMware datastores and PCD must be backed by the same storage array. +- **ESXi SSH access**: SSH access to ESXi hosts with root privileges +- **Storage connectivity**: ESXi hosts must be connected to the storage array via iSCSI (initiators configured) or Fibre Channel + +## Supported Storage Arrays + +| Vendor | Product | +|--------|---------| +| Pure Storage | FlashArray | +| NetApp | ONTAP | + +:::note +Additional storage vendors may be added in future releases. The storage SDK is designed to be extensible. +::: + +## Prerequisites + +Before using Storage-Accelerated Copy, ensure the following prerequisites are met: + +### 1. Storage Array Configuration + +- Storage array must be accessible from both VMware ESXi hosts and PCD compute nodes +- VMware datastores must be VMFS volumes backed by LUNs on the supported storage array +- PCD Cinder must be configured with a backend driver for the same storage array +- Cinder volume types must be created and mapped to the storage array backend + +### 2. ESXi SSH Access + +Storage-Accelerated Copy requires SSH access to ESXi hosts to execute `vmkfstools` commands. Follow these steps to set up SSH access: + +#### Step 1: Enable SSH on ESXi Hosts + +**Option A: Using vSphere Client (GUI)** + +1. Log in to vSphere Client +2. Navigate to the ESXi host +3. Click on the **Configure** tab +4. Under **System**, select **Services** +5. Find **SSH** in the list of services +6. Right-click on **SSH** and select **Start** +7. (Optional) Right-click again and select **Policy** → **Start and stop with host** to enable SSH automatically on boot + +**Option B: Using ESXi Host Client (Direct)** + +1. Log in to the ESXi host directly via web browser: `https://` +2. Navigate to **Host** → **Actions** → **Services** → **Enable Secure Shell (SSH)** + +**Option C: Using ESXi Shell (Console)** + +1. Access the ESXi host console (physical or via iLO/iDRAC) +2. Press `F2` to customize system/view logs +3. Log in with root credentials +4. Navigate to **Troubleshooting Options** +5. Select **Enable SSH** +6. Press `Enter` to confirm + +#### Step 2: Generate SSH Key Pair + +On your workstation or the vJailbreak VM, generate an SSH key pair: + +```bash +# Generate RSA key pair (recommended for ESXi compatibility) +ssh-keygen -t rsa -b 4096 -f ~/.ssh/esxi_migration_key -C "vjailbreak-migration" + +# When prompted: +# - Enter passphrase: Leave empty (press Enter) for passwordless authentication +# - Confirm passphrase: Press Enter again +``` + +This will create two files: +- `~/.ssh/esxi_migration_key` - Private key (keep this secure) +- `~/.ssh/esxi_migration_key.pub` - Public key (to be copied to ESXi) + +:::caution +**Use RSA keys, not Ed25519** + +Ed25519 keys are not reliably accepted for passwordless authentication on ESXi 8.x. Even with the public key correctly installed in `/etc/ssh/keys-root/authorized_keys` (correct permissions, `PubkeyAuthentication` enabled, SSH restarted), the host may keep prompting for a password. Use the 4096-bit RSA key shown above, which works for passwordless SSH across supported ESXi versions. +::: + +#### Step 3: Copy Public Key to ESXi Hosts + +**Option A: Using ssh-copy-id (Easiest)** + +```bash +# Copy public key to ESXi host +ssh-copy-id -i ~/.ssh/esxi_migration_key.pub root@ + +# Enter the root password when prompted +``` + +**Option B: Manual Copy** + +If `ssh-copy-id` is not available: + +```bash +# Display the public key +cat ~/.ssh/esxi_migration_key.pub + +# SSH into the ESXi host +ssh root@ + +# On the ESXi host, add the public key to authorized_keys +cat >> /etc/ssh/keys-root/authorized_keys << 'EOF' +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC... vjailbreak-migration +EOF + +# Set correct permissions +chmod 600 /etc/ssh/keys-root/authorized_keys +``` + +#### Step 4: Test SSH Connection + +Verify passwordless SSH access works: + +```bash +# Test SSH connection (should not prompt for password) +ssh -i ~/.ssh/esxi_migration_key root@ + +``` + +If successful, you should be able to login to esxi. +#### Step 5: Configure SSH Key in vJailbreak + +1. Navigate to **Storage Management** page +2. At the top, find the **ESXi SSH Key** section +3. Click **Configure** +4. Get your private key contents: + ```bash + cat ~/.ssh/esxi_migration_key + ``` +5. Copy the entire output (including `-----BEGIN OPENSSH PRIVATE KEY-----` and `-----END OPENSSH PRIVATE KEY-----`) +6. Paste into the UI textarea +7. Click **Save** + +#### Step 6: Repeat for All ESXi Hosts + +Repeat Steps 3-4 for **all ESXi hosts** in your vCenter cluster that host VMs you plan to migrate. The same SSH key pair can be used for all hosts. + +```bash +# Example: Copy to multiple hosts +for host in esxi-host1.example.com esxi-host2.example.com esxi-host3.example.com; do + echo "Configuring $host..." + ssh-copy-id -i ~/.ssh/esxi_migration_key.pub root@$host +done +``` + +:::caution +**Security Best Practices:** +- Use a dedicated SSH key pair for vJailbreak migrations (don't reuse existing keys) +- Store the private key securely and restrict access +- Consider disabling SSH on ESXi hosts after migrations are complete +- Use SSH key passphrases in production environments (requires additional configuration) +- Regularly rotate SSH keys +- Monitor SSH access logs on ESXi hosts +::: + +:::note +**Troubleshooting SSH Issues:** + +If SSH connection fails: +1. Verify SSH service is running: `ssh root@ "/etc/init.d/ssh status"` +2. Check firewall rules: ESXi firewall must allow SSH (port 22) +3. Verify authorized_keys permissions: Should be `600` or `400` +4. Check ESXi logs: `/var/log/auth.log` for authentication errors +5. Ensure the private key format is correct (OpenSSH format, not PuTTY format) +::: + +### 3. Network Connectivity + +| Source | Destination | Port | Protocol | Purpose | +|--------|-------------|------|----------|---------| +| vJailbreak | ESXi hosts | 22 | TCP | SSH for vmkfstools commands | +| ESXi hosts | Storage array | 3260 | TCP | iSCSI (if using iSCSI) | +| ESXi hosts | Storage array | Various | FC | Fibre Channel (if using FC) | +| vJailbreak | Storage array | 443 | TCP | Storage array API | + +## Configuration + +### Understanding Auto-Discovery + +When you add PCD credentials to vJailbreak, the system automatically discovers all storage volume backends configured in your PCD environment. For each detected storage backend (NetApp, Pure Storage, etc.), vJailbreak creates a placeholder ArrayCreds entry with status "Auto-discovered" and credentials marked as "Pending". + +#### How Auto-Discovery Works + +1. **PCD Configuration**: In PCD, you configure multiple storage volume backends under "Persistent Storage Connectivity" (Cluster Blueprint → Storage). Each volume backend represents a storage array with its driver type (NetApp Data ONTAP, Pure Storage iSCSI, NFS, etc.). + +2. **Backend Detection**: When PCD credentials are added to vJailbreak, the system queries the Cinder API to discover all configured volume backends and their properties: + - Volume Type (e.g., `netapp`, `vt-pure-iscsi`) + - Backend Name (e.g., `netapp`, `pure-iscsi-1`) + - Driver Type (e.g., `NetApp Data ONTAP`, `Pure Storage iSCSI`) + - Cinder Host string + +3. **Placeholder Creation**: For each discovered backend, vJailbreak automatically creates an ArrayCreds resource with: + - **Name**: Derived from the volume type and backend name (e.g., `netapp-netapp`, `vt-pure-iscsi-pure-iscsi-1`) + - **Vendor**: Automatically identified from the driver type + - **Source**: Marked as "Auto-discovered" + - **Credentials**: Status shows "Pending" (requires user input) + - **PCD Mapping**: Pre-populated with volume type, backend name, and Cinder host + +4. **User Completion**: Users then update these auto-discovered entries with the actual storage array credentials (hostname, username, password) to enable Storage Accelerated Copy. + +#### Storage Management Page + +The Storage Management page displays all auto-discovered storage backends: + +| Column | Description | +|--------|-------------| +| **Name** | Auto-generated name based on volume type and backend | +| **Vendor** | Storage array vendor (NetApp Storage, Pure Storage, N/A) | +| **Volume Type** | Cinder volume type name | +| **Backend Name** | Cinder backend name from configuration | +| **Source** | "Auto-discovered" for automatically detected backends | +| **Credentials** | "Pending" until user provides array credentials | +| **Actions** | Edit (to add credentials) and Delete | + +![Storage Management Page](../../../assets/storagemanagementpage.png) + +#### Example: PCD with Multiple Storage Backends + +In PCD, storage backends are configured in the **Cluster Blueprint** under **Persistent Storage Connectivity**. Each volume backend can have multiple configurations, and each configuration represents a connection to a storage array. + +![PCD Cluster Blueprint - Storage Configuration](../../../assets/clusterblueprint.png) + +In the example above, PCD has three volume backends configured: +1. **nfs** - NFS backend with driver "NFS" +2. **netapp** - NetApp backend with driver "NetApp Data ONTAP" +3. **vt-pure-iscsi** - Pure Storage backend with driver "Pure Storage iSCSI" + +Each backend can have multiple configurations (shown as "Volume Backend Configurations" with the + button). For example: +- The `nfs` volume backend might have one configuration named `nfs-backend` +- The `netapp` volume backend might have one configuration named `netapp` +- The `vt-pure-iscsi` volume backend might have multiple configurations: `pure-iscsi-1`, `pure-iscsi-2`, etc. + +**Important**: For each volume type, you can configure multiple storage arrays. This is useful when you have multiple Pure Storage or NetApp arrays in your environment, each serving different datastores. + +``` +Storage Volume Backend Configuration (Example): +├── nfs (Volume Type) +│ └── nfs-backend (Backend Configuration) +│ ├── Driver: NFS +│ └── Backend Name: nfs-backend +├── netapp (Volume Type) +│ └── netapp (Backend Configuration) +│ ├── Driver: NetApp Data ONTAP +│ └── Backend Name: netapp +└── vt-pure-iscsi (Volume Type) + ├── pure-iscsi-1 (Backend Configuration #1) + │ ├── Driver: Pure Storage iSCSI + │ └── Backend Name: pure-iscsi-1 + └── pure-iscsi-2 (Backend Configuration #2) + ├── Driver: Pure Storage iSCSI + └── Backend Name: pure-iscsi-2 +``` + +After adding PCD credentials to vJailbreak, the system automatically creates ArrayCreds placeholders for each backend configuration: +- `nfs-nfs-backend` (Vendor: N/A, Credentials: Pending) - *Cannot be used for Storage Accelerated Copy* +- `netapp-netapp` (Vendor: NetApp Storage, Credentials: Pending) +- `vt-pure-iscsi-pure-iscsi-1` (Vendor: Pure Storage, Credentials: Pending) +- `vt-pure-iscsi-pure-iscsi-2` (Vendor: Pure Storage, Credentials: Pending) + +:::tip +Only storage backends with supported vendors (Pure Storage and NetApp) can be configured for array-level XCOPY operations. NFS and other backends are still auto-discovered, but cannot be used for Storage Accelerated Copy. Support for additional storage vendors will be added in future releases. +::: + +## Using the UI + +Storage-Accelerated Copy can be configured entirely through the vJailbreak UI with automatic backend discovery: + +### Step 1: Add PCD Credentials + +If not already done: +1. Navigate to **Credentials** → **PCD/OpenStack** +2. Add your PCD credentials +3. vJailbreak will automatically discover all storage volume backends configured in PCD + +### Step 2: Configure Storage Array Credentials + +1. Navigate to **Storage Management** (Beta feature) +2. You'll see auto-discovered entries for each PCD storage backend: + - **Name**: Auto-generated (e.g., `netapp-netapp`, `vt-pure-iscsi-pure-iscsi-1`) + - **Vendor**: Auto-identified from driver type + - **Volume Type**: Pre-populated from PCD configuration + - **Backend Name**: Pre-populated from PCD configuration + - **Source**: "Auto-discovered" + - **Credentials**: "Pending" (requires your input) + +3. Click the **Edit** icon for a storage array entry + +![Storage Array Credentials Edit](../../../assets/credsedit.png) + +4. Fill in the storage array credentials: + - **Hostname/IP**: Storage array management IP address + - **Username**: Array administrator username + - **Password**: Array administrator password + - **Skip SSL Verification**: Enable for testing environments (disable in production) +5. Click **Save** +6. The system will: + - Validate the credentials + - Connect to the storage array + - Auto-discover VMware datastores backed by this array + - Update the status to show validation results + +### Step 3: Configure ESXi SSH Key + +1. At the top of the Storage Management page, find the **ESXi SSH Key** section +2. Click **Configure** if not already configured +3. Paste your ESXi SSH private key (see [ESXi SSH Access](#2-esxi-ssh-access) section for key generation steps) +4. Click **Save** + +### Step 4: Create Migration with Storage-Accelerated Copy + +1. When creating a migration plan, select **Storage-Accelerated Copy** as the storage copy method + +![Storage Accelerated Copy](../../../assets/trigger-mig.png) + +2. The UI will automatically map datastores to ArrayCreds if everything is configured correctly +3. Start the migration - it will use array-level XCOPY for data transfer + +:::note +**Auto-Discovery Benefits:** +- No manual typing of volume types, backend names, or Cinder host strings +- Automatic vendor identification from driver type +- Pre-populated PCD mapping configuration +- Automatic datastore-to-array mapping +- Reduced configuration errors +::: + +## Migration Workflow + +When Storage-Accelerated Copy is enabled, the migration follows this workflow: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Storage-Accelerated Copy Flow │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. Validate Prerequisites │ +│ ├── Storage provider credentials │ +│ ├── ESXi SSH key │ +│ └── Array connectivity │ +│ │ +│ 2. Connect to ESXi Host via SSH │ +│ │ +│ 3. Power Off Source VM (required for XCOPY) │ +│ │ +│ 4. For Each Disk: │ +│ ├── Create target volume on storage array │ +│ ├── Import volume to Cinder (manage existing) │ +│ ├── Create/update initiator group with ESXi IQN │ +│ ├── Map volume to ESXi host │ +│ ├── Rescan ESXi storage adapters │ +│ ├── Wait for target device to appear │ +│ ├── Execute vmkfstools XCOPY clone │ +│ └── Monitor clone progress │ +│ │ +│ 5. Convert Volumes (same as normal migration) │ +│ │ +│ 6. Create Target VM in PCD │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +## Migration Phases + +When using Storage-Accelerated Copy, you'll see these additional migration phases: + +| Phase | Description | +|-------|-------------| +| `ConnectingToESXi` | Establishing SSH connection to ESXi host | +| `CreatingInitiatorGroup` | Creating/updating initiator group on storage array | +| `CreatingVolume` | Creating target volume on storage array | +| `ImportingToCinder` | Importing volume to PCD Cinder | +| `MappingVolume` | Mapping volume to ESXi host | +| `RescanningStorage` | Rescanning ESXi storage adapters | +| `StorageAcceleratedCopyInProgress` | XCOPY clone operation in progress | + +## Limitations + +- **Cold migration only**: VMs must be powered off during the copy operation (no live migration support) +- **Shared storage required**: Source and destination must be on the same storage array +- **VMFS datastores only**: NFS datastores are not supported +- **No CBT support**: Change Block Tracking is not used; full disk copy is performed +- **Single array per datastore**: Each datastore can only be mapped to one ArrayCreds + +## Troubleshooting + +### Common Issues + +#### ESXi SSH Connection Failed + +``` +Error: failed to connect to ESXi via SSH +``` + +**Resolution**: +- Verify SSH is enabled on the ESXi host +- Check that the SSH private key is correctly stored in the `esxi-ssh-key` secret +- Ensure network connectivity between vJailbreak and ESXi host on port 22 + +#### Storage Array Connection Failed + +**Symptoms:** +``` +Error: failed to connect to storage array: authentication failed +``` + +**Solutions:** +1. Verify credentials in the Storage Management UI: + - Navigate to **Storage Management** + - Check the array credentials are correct + - Re-enter credentials if needed + +2. Test connectivity from vJailbreak to storage array: + - Ensure network connectivity on port 443 (HTTPS) + - Check firewall rules between vJailbreak and storage array + - Verify API access is enabled on the array + +3. Check array-specific requirements: + - **Pure Storage**: Ensure API token or username/password has sufficient permissions + - **NetApp**: Verify ONTAP management interface is accessible + +#### ESXi Device Not Found + +**Symptoms:** +``` +Error: timeout waiting for device naa.624a9370... to appear on ESXi host +``` + +**Solutions:** +1. Verify iSCSI initiator is configured on ESXi: + - Log into ESXi via SSH + - Run: `esxcli iscsi adapter list` + - Ensure iSCSI software adapter is enabled + +2. Check network connectivity from ESXi to storage array: + - Verify ESXi can reach storage array management IP + - Check iSCSI network configuration + - Verify VLAN/network segmentation allows iSCSI traffic + +3. Manually rescan storage: + ```bash + esxcli storage core adapter rescan --all + esxcli storage core device list | grep naa.624a9370 + ``` + +4. Verify LUN masking/mapping on the storage array: + - Check initiator group includes ESXi IQN + - Verify volume is mapped to the correct initiator group + - Check for iSCSI authentication issues (CHAP) + +#### Cinder Manage Volume Failed + +**Symptoms:** +``` +Error: failed to manage existing volume: volume not found on backend +``` + +**Solutions:** +1. Verify volume exists on the storage array: + - Check storage array management interface + - Confirm volume was created successfully + +2. Check Cinder backend configuration: + - Verify Cinder services are running in PCD + - Ensure volume backend name matches ArrayCreds configuration + - Check that the Cinder host string is correct + +3. Ensure volume naming matches backend expectations: + - **Pure Storage**: Volume name must match exactly + - **NetApp**: Full LUN path must be provided + +4. Review Cinder logs for detailed errors: + - Check PCD Cinder volume service logs + - Look for backend connection issues + - Verify the `cinderHost` field in ArrayCreds matches a running Cinder service + +#### vmkfstools Clone Failed + +**Symptoms:** +``` +Error: vmkfstools clone failed: Unable to create raw disk +``` + +**Solutions:** +1. Check ESXi SSH connectivity and authentication: + - Verify SSH key is configured correctly in vJailbreak + - Test SSH connection manually + - Check ESXi SSH service is running + +2. Verify vmkfstools is available: + ```bash + vmkfstools --version + ``` + +3. Check source VMDK accessibility: + ```bash + ls -lh /vmfs/volumes///.vmdk + ``` + +4. Verify target device is visible: + ```bash + ls -lh /vmfs/devices/disks/naa.* + ``` + +5. Check ESXi host logs: + ```bash + tail -f /var/log/vmkernel.log + ``` + +6. Ensure sufficient free space on ESXi: + - RDM descriptor files require space on the datastore + - Check datastore free space + +#### Clone Progress Stalled + +**Symptoms:** +``` +Error: clone progress stalled - no update for 5 minutes +``` + +**Solutions:** +1. Check storage array performance and load: + - Review array management interface for performance metrics + - Check for high I/O load or resource contention + - Verify no array-level issues or alerts + +2. Verify ESXi storage adapter health: + ```bash + esxcli storage core adapter list + ``` + +3. Review vmkfstools process on ESXi: + ```bash + ps | grep vmkfstools + ps -c | grep vmkfstools + ``` + - Check if process is still running + - Look for any error indicators + +4. Check for network issues: + - Verify stable connectivity between ESXi and storage array + - Check for packet loss or latency issues + +5. Consider increasing timeout settings: + - For very large disks, the operation may take longer than expected + - Monitor array performance to ensure copy is progressing + +### Checking ArrayCreds Status + +You can check the status of your storage array credentials in the UI: + +1. Navigate to **Storage Management** +2. Check the **Credentials** column - it should show "Valid" after successful configuration +3. The system will display discovered datastores for each array +4. Any validation errors will be shown in the status column + +## Best Practices + +1. **Validate prerequisites first**: Ensure all connectivity and credentials are working before starting migrations +2. **Schedule during maintenance windows**: VMs must be powered off during copy +3. **Monitor array performance**: Large migrations can impact array performance +4. **Use for large VMs**: The setup overhead makes this most beneficial for VMs with large disks +5. **Batch similar VMs**: Group VMs on the same datastore for efficient migrations diff --git a/docs/src/content/docs/concepts/user-management.md b/docs/src/content/docs/concepts/user-management.md new file mode 100644 index 0000000..0d1e486 --- /dev/null +++ b/docs/src/content/docs/concepts/user-management.md @@ -0,0 +1,111 @@ +--- +title: "User Credential Management" +description: "A guide on how to manage user credentials using vjailbreak CLI." +--- + +## Overview + +Use `vjbctl` to manage user credentials: list users, delete users, change passwords, and refresh credentials. + +## Assumptions + +Before you start, ensure the following prerequisites are fulfilled: + +- vJailbreak is installed and configured properly. + +## Command Reference + +The following commands are available for user credential management: + +```bash +# List users +vjbctl user list + +# create a new user +vjbctl user create + +# Delete a user +vjbctl user delete + +# Change a user's password +vjbctl user change-password + +# Refresh user credentials +vjbctl user refresh +``` + +> Tip: Append `--no-restart` to `vjbctl user` subcommands (e.g., `create`, `delete`, `change-password`) to defer applying changes. Then run `vjbctl user refresh` once to apply all pending changes for better efficiency. + +## Usage + +> Note: After any user change (e.g., password updates or deletions), run `vjbctl user refresh` for the changes to be reflected. + +### List users + +```bash +vjbctl user list +``` + +### Delete a user + +Delete a user and remove their credentials from the system: + +```bash +vjbctl user delete +``` + +### Change a user's password + +Change the password for an existing user: + +```bash +vjbctl user change-password +``` + +You will be prompted to enter the new password in the terminal. + +### Batch multiple changes efficiently + +To avoid refreshing after every change, you can batch multiple operations using `--no-restart` and then apply them all at once with a single refresh: + +```bash +# Defer applying changes while making multiple updates +vjbctl user create --no-restart +vjbctl user change-password --no-restart +vjbctl user delete --no-restart + +# Apply all pending changes +vjbctl user refresh +``` + +### Refresh user credentials + +Refresh user credentials for active users: + +```bash +vjbctl user refresh +``` + +## Examples + +The following sequence demonstrates a typical workflow for updating user credentials: + +```bash +# Inspect existing users +vjbctl user list + +# Create a new user +vjbctl user create john.doe +# For changes to be reflected, refresh +vjbctl user refresh + +# Update password for a specific user +vjbctl user change-password jane.doe +# For changes to be reflected, refresh +vjbctl user refresh + +# Remove a deprovisioned user +vjbctl user delete temp.user +# For changes to be reflected, refresh +vjbctl user refresh +``` diff --git a/docs/src/content/docs/concepts/vjailbreak-accelerated-copy.md b/docs/src/content/docs/concepts/vjailbreak-accelerated-copy.md new file mode 100644 index 0000000..597d679 --- /dev/null +++ b/docs/src/content/docs/concepts/vjailbreak-accelerated-copy.md @@ -0,0 +1,461 @@ +--- +title: vJailbreak Accelerated Copy +description: High-performance VM migration using a Proxy VM for direct disk attachment and NBD-based data transfer +--- + +vJailbreak Accelerated Copy is an advanced data copy method that attaches source VM disks directly to a dedicated Proxy VM and streams the data over NBD (Network Block Device) to the destination. Instead of copying data over the NFC protocol from ESXi, this method leverages vCenter's disk-attach capability to transfer data at near-disk speeds without requiring shared storage arrays. + +> **Underlying feature:** vJailbreak Accelerated Copy is powered by VMware's **hot-add** disk transport mechanism to attach source disks to the Proxy VM. + +:::danger[Cold migration only: hot data copy is not supported] +vJailbreak Accelerated Copy **does not support live (hot) migration**. The source VM is powered off +before its disks are attached to the Proxy VM. You must select **"Power off VMs, then copy"** as +the Data Copy Method when using vJailbreak Accelerated Copy. Attempting to use it with +**"Copy live VMs, then power off"** is not supported. +::: + +:::note[VDDK not required] +As of v0.4.10, vJailbreak Accelerated Copy does not require VDDK at any stage. Data is transferred +directly via the hot-add disk transport and NBD streaming. VDDK is never used. This makes it the +recommended copy method when VMware's VDDK download pages are unavailable. +::: + +## Overview + +### How It Works + +Traditional vJailbreak migrations copy VM disk data from VMware ESXi hosts to PCD Cinder volumes over the network using the NFC (Network File Copy) protocol, limited to approximately **1 Gbps per VMDK**. + +vJailbreak Accelerated Copy bypasses this limitation by: + +1. Powering off the source VM and then taking a snapshot +2. Attaching the frozen snapshot disks directly to a Proxy VM running in vCenter +3. Identifying each disk as a block device inside the Proxy VM using disk UUID matching +4. Exposing each disk as an NBD resource on the Proxy VM via `qemu-nbd` +5. Running `nbdcopy` on the vJailbreak VM to pull data from the Proxy VM directly to the destination Cinder volume + +### Benefits + +- **Faster migrations**: Direct block-device access avoids NFC protocol overhead +- **No shared storage required**: Works with any datastore — NFS, VMFS, vSAN +- **Lower ESXi host load**: Data is streamed from the Proxy VM, not the ESXi NFC daemon + +### Requirements + +- **Proxy VM**: A Linux VM running in the same vCenter with `qemu-nbd` and `openssh-server` installed +- **SSH access**: vJailbreak must be able to SSH into the Proxy VM as root +- **Open ports**: The Proxy VM must accept inbound TCP from the vJailbreak VM on **22** (SSH) and **10809–11808** (`qemu-nbd`, one port per disk copied in parallel) +- **disk.EnableUUID**: Must be set to `TRUE` on the Proxy VM in vCenter +- **PVSCSI controller**: The Proxy VM's first SCSI controller (**SCSI controller 0**) must be of type **VMware Paravirtual (PVSCSI)** +- **Datastore accessibility**: The HotAdd proxy must have access to the same datastore as the target virtual machine, and the VMFS version and data block sizes for the target VM must be the same as the datastore where the HotAdd proxy resides. +- **vCenter permissions**: Sufficient permissions to snapshot VMs and attach/detach disks + +## Prerequisites + +### 1. Proxy VM Requirements + +The Proxy VM must have the following utilities installed and running: + +| Utility | Purpose | +|---------|---------| +| `openssh-server` | SSH connectivity for vJailbreak to control the Proxy VM | +| `qemu-nbd` | Expose attached block devices as NBD resources | + +The Proxy VM must be a **Linux-based OS** (recommended: Ubuntu, Alpine, or Debian) with **root SSH access** enabled. + +### 2. vCenter Requirements + +- The Proxy VM must have **disk.EnableUUID = TRUE** set in vCenter VM settings +- The Proxy VM's **SCSI controller 0** must be of type **VMware Paravirtual (PVSCSI)** +- vCenter must allow disk attach/detach operations on the Proxy VM +- The Proxy VM must be powered on and reachable over SSH +- The Proxy VM must be on the same datastore as the source VM's disks, with a matching VMFS version and block size (see **Datastore accessibility** under [Requirements](#requirements)) + + +## Setting Up the Proxy VM + +### Option A: Deploy from the vJailbreak UI (Easiest) + +vJailbreak can deploy and register the Proxy VM in a single step directly from the UI: + +1. Navigate to **vJailbreak Accelerated Copy** in the left sidebar +2. Click **Add Proxy VM** and select **Deploy a new vJailbreak Proxy VM** +3. Select your VMware credentials and fill in the deployment target (datacenter, datastore, network, and optionally a cluster or host) +4. Enter a unique VM name and click **Deploy & Register VM** + +vJailbreak will import the pre-configured OVA into vCenter, power the VM on, generate and inject an SSH key pair automatically, and register the Proxy VM — no manual key setup required. The VM appears in the list with status **Deploying** and transitions to **Ready** once verification completes (typically 3–5 minutes). + +:::caution +The OVA image uses default credentials (`root` / `password`) for the initial SSH key injection step. Change the root password on the VM after deployment in production environments. +::: + +:::caution[ESXi/vCenter version requirement] +The bundled OVA uses virtual hardware version **vmx-21**, which requires **ESXi 8.0 U2 (vCenter 8.0 U2) or newer**. Deploying it to an older host fails with an "unsupported hardware family" error. On ESXi/vCenter 7.x, use **Option B** to register a manually created Linux VM instead. +::: + +### Option B: Register an Existing Linux VM + +Any Linux VM can serve as the Proxy VM provided it meets the requirements. Install the necessary utilities: + +**Ubuntu / Debian:** +```bash +sudo apt update +sudo apt install -y openssh-server qemu-utils +``` + +**Alpine:** +```bash +apk update +apk add openssh qemu-nbd +``` + +:::note +Root access is required for SSH and for running `qemu-nbd` commands on the Proxy VM. Ensure `PermitRootLogin yes` is set in `/etc/ssh/sshd_config` if root SSH is not already enabled. +::: + +### Configure disk.EnableUUID on the Proxy VM + +This setting is required for vJailbreak to match attached disks to their block devices inside the Proxy VM: + +1. In vSphere Client, right-click the Proxy VM and select **Edit Settings** +2. Click **VM Options** → **Advanced** → **Edit Configuration** +3. Find or add the key `disk.EnableUUID` and set the value to `TRUE` +4. Click **OK** and restart the VM if it was already running + +### Configure the SCSI Controller Type on the Proxy VM + +Source disks are attached to the Proxy VM's first SCSI controller, and vJailbreak can only match them to block devices when that controller is **VMware Paravirtual**: + +1. Power off the Proxy VM +2. In vSphere Client, right-click the Proxy VM and select **Edit Settings** +3. Under **Virtual Hardware**, locate **SCSI controller 0** +4. Set **Change Type** to **VMware Paravirtual** +5. Click **OK** and power the VM back on + +:::caution +Other controller types — including **LSI Logic SAS**, **LSI Logic Parallel**, and **BusLogic Parallel** — are not supported. Migrations using a Proxy VM without PVSCSI on SCSI controller 0 fail with `could not identify block device for disk `. See [Proxy VM Must Use a PVSCSI Controller](../../reference/known-limitations/#proxy-vm-must-use-a-pvscsi-controller). +::: + +## SSH Key Configuration + +:::note +This section applies to **Option B** (registering an existing VM). When using Option A (UI-based OVA deploy), SSH keys are generated and injected automatically — no manual key steps are needed. +::: + +When registering an existing VM, vJailbreak needs SSH access to the Proxy VM. The UI offers two ways to provide the key pair: + +### Sub-option 1: Let vJailbreak Generate the Key Pair + +1. In the **Add Proxy VM** drawer, select **Register an existing VM** +2. Select your VMware credentials and the VM +3. Under **SSH Access**, choose **Generate Key Pair** and click **Generate** +4. The UI displays the public key — copy it and add it to the Proxy VM's `authorized_keys`: + ```bash + # On the Proxy VM (as root) + echo "" >> ~/.ssh/authorized_keys + chmod 600 ~/.ssh/authorized_keys + ``` +5. Click **Register** + +vJailbreak stores the generated private key as a Kubernetes secret automatically. + +### Sub-option 2: Upload Your Own Private Key + +If you have an existing key pair already configured on the VM: + +1. Under **SSH Access**, choose **Upload Private Key** +2. Upload the private key file or paste its contents into the text area +3. Confirm the corresponding public key is already in the VM's `authorized_keys` +4. Click **Register** + +vJailbreak stores the uploaded private key as a Kubernetes secret and uses it during verification and migration. + +#### Generating a Key Pair Manually + +If you prefer to generate the key pair yourself outside of vJailbreak: + +```bash +ssh-keygen -t rsa -b 4096 -f proxy_vm_key -N "" +``` + +This produces two files: +- `proxy_vm_key` — private key (upload this into vJailbreak) +- `proxy_vm_key.pub` — public key (add this to the Proxy VM) + +On the Proxy VM, append the public key to root's `authorized_keys`: + +```bash +# On the Proxy VM (as root) +mkdir -p ~/.ssh +cat >> ~/.ssh/authorized_keys << 'EOF' + +EOF +chmod 600 ~/.ssh/authorized_keys +``` + +If you have temporary password SSH access, you can use `ssh-copy-id` from your workstation as a shortcut: + +```bash +ssh-copy-id -i proxy_vm_key.pub root@ +``` + +:::note +**SSH key requirements:** +- No passphrase — vJailbreak uses the key non-interactively +- Any standard PEM format is accepted: RSA, EC, PKCS#8, or OpenSSH. PuTTY `.ppk` format is not supported +- `PermitRootLogin` must be `yes` or `prohibit-password` in `/etc/ssh/sshd_config` on the Proxy VM +::: + +## Registering the Proxy VM in vJailbreak + +Once the Proxy VM is set up and the SSH key is ready: + +1. In the vJailbreak UI, navigate to **vJailbreak Accelerated Copy** in the left sidebar +2. Click **Add Proxy VM** +3. Fill in the form: + - **Name**: A unique identifier for this Proxy VM + - **VM Name**: The exact VM name as it appears in vCenter + - **VMware Credentials**: Select the VMware credentials that can see this VM + - **SSH Private Key**: Paste the contents of your private key file (e.g., `~/.ssh/proxy_vm_key`) +4. Click **Add** + +vJailbreak will verify the Proxy VM by: +- Confirming the VM exists in vCenter +- Retrieving the guest IP via VMware Tools +- Checking `disk.EnableUUID = TRUE` — if not set, vJailbreak will automatically enable it and reboot the Proxy VM, so onboarding may take longer than usual +- Establishing an SSH connection +- Verifying `qemu-nbd` is available + +The Proxy VM status will update to **Ready** once all checks pass. Any failed checks are reported with a specific error message in the UI. + +:::tip +If verification fails, address the reported issue (e.g., install missing utilities, fix SSH access) and click **Retry** to re-run the validation without re-entering the form. +::: + +## Using vJailbreak Accelerated Copy in a Migration + +### Step 1: Create a Migration + +1. Navigate to the **Migrations** page and click **New Migration** +2. Fill out the migration form with source VM and target configuration +3. For the **Data Copy Method**, select **vJailbreak Accelerated Copy** + +### Step 2: Select Proxy VM + +4. A **Proxy VM** dropdown appears — select a Proxy VM in **Ready** state +5. The UI will only show Proxy VMs that are verified and ready + +### Step 3: Start the Migration + +6. Review **Advanced Options** if needed (network/storage mappings) +7. Click **Start Migration** + +:::note +The selected Proxy VM must be in **Ready** state before the migration can proceed. If no Proxy VM is ready, register and verify one first. +::: + +## Migration Workflow + +When vJailbreak Accelerated Copy is selected, the migration follows this workflow: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ vJailbreak Accelerated Copy Flow │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. Validate Prerequisites │ +│ ├── Proxy VM is in Ready state │ +│ ├── SSH connectivity to Proxy VM │ +│ └── qemu-nbd available on Proxy VM │ +│ │ +│ 2. Provision Destination Resources (standard workflow) │ +│ ├── Create Cinder volumes in PCD │ +│ └── Attach destination disks to vJailbreak VM │ +│ │ +│ 3. Power Off Source VM, then Take Snapshot │ +│ │ +│ 4. For Each Source Disk: │ +│ ├── Attach frozen snapshot disk to Proxy VM │ +│ ├── Identify block device via disk UUID matching │ +│ ├── Find a free port on the Proxy VM │ +│ ├── Expose disk as NBD via qemu-nbd on that port │ +│ ├── Run nbdcopy on vJailbreak VM to destination disk │ +│ └── Detach and clean up disk from Proxy VM │ +│ │ +│ 5. Remove Source VM Snapshot │ +│ │ +│ 6. Disk Conversion (same as normal migration) │ +│ │ +│ 7. Create Target VM in PCD (standard post-copy flow) │ +│ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### Component Diagram + +The pieces involved and how they talk to each other: + +```mermaid +architecture-beta + service admin(internet)[Admin UI] + + group vjb(cloud)[vJailbreak Appliance] + service ctrl(server)[Controller] in vjb + service pod(server)[Migration Pod] in vjb + + group vmw(cloud)[VMware Environment] + service vc(server)[vCenter] in vmw + service proxy(server)[Proxy VM] in vmw + + group pcd(cloud)[Platform9 PCD] + service os(disk)[Cinder Volume] in pcd + + admin:R -- L:ctrl + ctrl:R -- L:vc + ctrl:B -- T:proxy + vc:B -- L:proxy + pod:L -- R:proxy + pod:B -- T:os +``` + +- **Admin UI → Controller**: manage Proxy VMs and MigrationPlans (Kubernetes API) +- **Controller → vCenter/ESXi**: deploy/register the Proxy VM (OVA import), read host inventory +- **Controller → Proxy VM**: push/verify SSH key, confirm `qemu-nbd` and `disk.EnableUUID` +- **vCenter/ESXi → Proxy VM**: hot-add the frozen source-VM disks +- **Migration Pod → Proxy VM**: SSH — identify block devices, start `qemu-nbd`, run `nbdcopy` +- **Migration Pod → Cinder Volume**: write the copied disk data to the destination + +### Sequence Diagram + +The same flow as a sequence diagram, including Proxy VM onboarding (only the operations specific to vJailbreak Accelerated Copy are shown — disk conversion and target VM creation follow the same path as every other copy method): + +```mermaid +sequenceDiagram + autonumber + actor Admin + participant Ctrl as vJailbreak Controller + participant vC as vCenter / ESXi + participant Proxy as Proxy VM + participant Pod as Migration Pod (v2v-helper) + participant OS as OpenStack (Cinder/Nova) + + Note over Admin,OS: Onboarding — Option A: Deploy new Proxy VM (OVA) + Admin->>Ctrl: Deploy & Register VM (OVA) + Ctrl->>vC: Import OVA, power on VM + Ctrl->>Proxy: Auto-generate & inject SSH keypair + Ctrl->>Proxy: Verify SSH, qemu-nbd, disk.EnableUUID + Ctrl->>Ctrl: Store private key as Secret "{proxyVM}-hot-add-ssh-key" + Ctrl-->>Admin: Proxy VM ready + + Note over Admin,OS: Onboarding — Option B: Register an existing VM + Admin->>Ctrl: Register VM + SSH key (generate or upload) + Note right of Admin: If generated, admin adds public key
to the VM's authorized_keys manually + Ctrl->>Proxy: Verify SSH, qemu-nbd, disk.EnableUUID + Ctrl->>Ctrl: Store private key as Secret "{proxyVM}-hot-add-ssh-key" + Ctrl-->>Admin: Proxy VM ready + + Note over Admin,OS: Migration — vJailbreak Accelerated Copy + Admin->>Ctrl: Create MigrationPlan (StorageCopyMethod=HotAdd) + Ctrl->>Pod: Launch v2v-helper (Proxy VM IP + SSH secret) + Pod->>vC: Power off source VM, take snapshot + Pod->>vC: Hot-add frozen VMDKs to Proxy VM + Pod->>Proxy: SSH — match disk WWID to block device + Pod->>Proxy: SSH — start qemu-nbd per disk + Proxy-->>Pod: nbdcopy streams disk over NBD + Pod->>OS: Write stream into Cinder volume + Pod->>vC: Detach disks, delete snapshot + Pod-->>Ctrl: Migration succeeded +``` + +## Limitations + +- **Cold copy only**: The source VM is powered off before disk attachment — live (hot) copy of the running VM's active disks is not supported +- **Same vCenter**: Proxy VM and source VM must be managed by the same vCenter instance +- **VMware Tools required**: The Proxy VM must have VMware Tools running so vJailbreak can retrieve its guest IP +- **PVSCSI controller only**: The Proxy VM's first SCSI controller (**SCSI controller 0**) must be **VMware Paravirtual (PVSCSI)**. Disk UUID matching does not work on other controller types, and migrations fail with `could not identify block device for disk `. See [Configure the SCSI Controller Type](#configure-the-scsi-controller-type-on-the-proxy-vm). +- **Concurrent disk attach can fail**: When several migrations reach the disk-attach step at the same time on the same Proxy VM, vCenter may reject some of the simultaneous reconfigure tasks and those migrations fail. This is a transient race — the migrations that attached first continue normally, and the failed ones succeed on retry. Stagger migration start times or spread migrations across multiple Proxy VMs to reduce the chance of it happening. +- **Maximum 60 disks per Proxy VM (including its own boot disk)**: vSphere allows at most **60** virtual disks per VM (4 SCSI controllers × 15 disks). The Proxy VM's own boot disk counts toward this total, so the constraint is **Proxy VM boot disk + attached source disks ≤ 60** — a Proxy VM with a single boot disk can have up to **59** source disks attached at any one time. This is a shared ceiling across **all** migrations using the same Proxy VM concurrently, not a per-migration limit. To migrate more disks in parallel, register additional Proxy VMs and distribute migrations across them. + +## Troubleshooting + +### Proxy VM Verification Failed + +**Symptoms:** Proxy VM stuck in `Pending` or `Failed` state with a validation error. + +**Resolution by error:** + +| Error | Resolution | +|-------|-----------| +| VM not found in vCenter | Verify the VM name exactly matches the vCenter inventory name | +| Guest IP not available | Ensure VMware Tools is installed and running on the Proxy VM | +| `disk.EnableUUID` not set | Set `disk.EnableUUID = TRUE` in VM advanced settings and reboot | +| SSH connection refused | Verify `sshd` is running and port 22 is reachable from vJailbreak | +| `qemu-nbd` not found | Install `qemu-utils` (Ubuntu) or `qemu-nbd` (Alpine) on the Proxy VM | + +### NBD Connection Failed During Copy + +``` +Error: failed to connect to NBD endpoint on proxy VM +``` + +**Resolution:** +1. Verify the Proxy VM is still running and SSH is accessible +2. Check that `qemu-nbd` started successfully — review v2v helper logs +3. Ensure the NBD ports (TCP **10809–11808**, one per disk copied in parallel) are not blocked by a firewall between the Proxy VM and vJailbreak +4. Confirm the Proxy VM's guest IP is correct (VMware Tools must be running) + +### Block Device Not Found in Proxy VM + +``` +Error: could not identify block device for disk +``` + +**Resolution:** +1. Verify `disk.EnableUUID = TRUE` is set on the Proxy VM (this is the most common cause) +2. Verify the Proxy VM's **SCSI controller 0** is of type **VMware Paravirtual** — no other controller type is supported. See [Configure the SCSI Controller Type](#configure-the-scsi-controller-type-on-the-proxy-vm) +3. Confirm the disk was actually attached — check vCenter → Proxy VM → Edit Settings → Hard Disks +4. SSH into the Proxy VM and run `lsblk` to list visible block devices +5. Check vCenter events for disk attach errors on the Proxy VM + +### Disk Attach Fails When Several Migrations Start Together + +**Symptoms:** A batch of migrations is started at once and some of them fail early with a vCenter error while attaching disks to the Proxy VM. The remaining migrations proceed into the copy phase normally. + +**Cause:** vCenter does not always handle simultaneous VM reconfigure (disk attach) tasks on the same Proxy VM gracefully, so some attach requests are rejected. This is a transient race condition, not a misconfiguration. + +**Resolution:** +1. Wait until the surviving migrations have entered the copy phase +2. [Retry](../../guides/how-to/retry_failed_migration/) the failed migrations — they normally succeed on the second attempt +3. To reduce the chance of the race, stagger migration start times instead of starting a large batch at once, or register additional Proxy VMs and distribute migrations across them + +### Snapshot Creation Failed + +``` +Error: failed to create snapshot on source VM +``` + +**Resolution:** +1. Verify the VMware credentials have snapshot creation permissions +2. Check if a snapshot with the same name already exists on the source VM — remove stale `vjailbreak-*` snapshots +3. Ensure the source VM's datastore has sufficient free space for the snapshot delta files + +### Migration Stuck After Snapshot + +If the migration is stuck after taking the snapshot and the source VM remains powered off: + +1. Check v2v helper logs for the last successful phase +2. If the Proxy VM became unavailable, the migration will not auto-recover — clean up manually: + ```bash + # Remove the snapshot from vCenter + govc snapshot.remove -vm "" "vjailbreak-hotadd-snap" + ``` +3. Detach any disks vJailbreak attached to the Proxy VM before retrying + +## Best Practices + +1. **Dedicate the Proxy VM**: Avoid running other workloads on the Proxy VM during migrations to ensure stable performance +2. **Match network placement**: Place the Proxy VM on a network with low latency to the vJailbreak VM for fast NBD transfers +3. **Verify before migrating**: Always confirm the Proxy VM shows **Ready** status before starting a migration +4. **Monitor disk space**: Snapshot delta files consume datastore space — ensure the source VM's datastore has at least 20% free space +5. **Use the recommended OVA**: The pre-built OVA is tested and configured correctly; custom VMs require manual validation of all prerequisites +6. **Rotate SSH keys**: Use a dedicated key pair for vJailbreak and rotate it periodically diff --git a/docs/src/content/docs/guides/CLI-API/migrating_rdm_disk_windows_cluster_machine_using_cli.md b/docs/src/content/docs/guides/CLI-API/migrating_rdm_disk_windows_cluster_machine_using_cli.md new file mode 100644 index 0000000..3cfe9ca --- /dev/null +++ b/docs/src/content/docs/guides/CLI-API/migrating_rdm_disk_windows_cluster_machine_using_cli.md @@ -0,0 +1,247 @@ +--- +title: "Migrating RDM Disk" +description: "A guide on how to migrate a Virtual Machine with RDM disks using the vjailbreak CLI." +--- + + +RDM disks are primarily used for clustered Windows machines. + +This guide walks you through the steps required to migrate a VM with **RDM (Raw Device Mapping) disks** using the CLI. + +RDM disk migration is only supported for **PCD version >= July 2025 (2025.7)** and is **not supported for OpenStack**. + +--- + +## Prerequisites + +Before you begin, ensure the following: + +1. **RDM disk is attached** to the Windows machine. +2. **vjailbreak** is deployed in your cluster. +3. **PCD Requirements**: + - Minimum version: **July 2025 (2025.7)**. + - For multipath support (connecting to SAN array): **October 2025 (2025.10)** - includes patched libvirt and QEMU packages. + - **Volume type must have multi-attach support enabled** in OpenStack. +4. All required fields (like `cinderBackendPool` and `volumeType`) are available from your `OpenstackCreds`. +5. Source Details are added on RDM VMs in VMware described [here](#on-vmware) +6. Storage array configured in PCD is same as the one configured in VMware. Usually SAN arrays have logical pools/isolation, that must be same as well. + +You can fetch `cinderBackendPool` and `volumeType` values using: + +By describing the OpenStack credentials in vjailbreak: + +```bash +kubectl describe openstackcreds -n migration-system +``` + +After describing the OpenStack credentials, look for `volumeTypes` and `volumeBackend`. Gather the `volumeTypes` and `volumeBackend` values that need to be patched as mentioned in [step 4 of Migration steps](#4-patch-rdm-disk-with-the-required-fields). + +**Alternatively** you can also gather details using openstack cli + +```bash +openstack volume backend pool list +openstack volume type list +``` + +Please refer to the following documents for commands to fetch volume backend pool and volume type lists: + +opnestack cli version >= 6.2.1 + +- [Volume Type List](https://docs.openstack.org/python-openstackclient/queens/cli/command-objects/volume-type.html#volume-type-list) +- [Volume Backend Pool List](https://docs.openstack.org/python-openstackclient/latest/cli/command-objects/volume-backend.html#volume-backend-pool-list) + +## RDM Validation settings + +In vjailbreak setting configmap we have a setting called `VALIDATE_RDM_OWNER_VMS` whose default value is `true`. + +This setting manadates all VM linked to RDM disk must be migrated in a single migration plan, to disable it set `VALIDATE_RDM_OWNER_VMS` to false + +## On VMware + +Perform the following steps on each VM from the cluster you are planning to migrate. + +- Add the following annotation to the VMware Notes field for the VM: + + ``` + VJB_RDM:{Name of Hardisk}:volumeRef:source-name=abac111 + ``` + + - VJB_RDM – Key prefix indicating this entry is an RDM (Raw Device Mapping) LUN reference. + - {Name of Hardisk} - Name of the RDM disk attached to the VM. Replace this placeholder with the actual disk name. + Disk Name is case sensitive. + - volumeRef – Denotes the reference section for the volume configuration. + source-name=abac111 – Specifies the LUN reference. + The key can be either source-id or source-name. + + The value is the LUN identifier (ID or Name) used to map the disk. + To obtain the source details ie `source-id`, `source-name`, you can run the following command against the SAN Array: + + ```bash + openstack block storage volume manageable list --os-volume-api-version 3.8 + ``` + +**Note: Not all SAN arrays are supported by the OpenStack block storage client, in such cases above command gives an empty output. If you cannot find your SAN array reference from the block storage client, contact your storage administrator to get the LUN reference by accessing the storage provider's interface.** + +RDM disk migration has been tested with two storage arrays: + +1. HPE Primera +2. NetApp ONTAP + +The `manageable list` command is only supported on HPE Primera. + +--- + +## Migration Steps + +### 1. Verify RDM Disk Resource + +Check if the **RDM disk resource** is created in Kubernetes: + +```bash +kubectl get rdmdisk -n migration-system +``` + +Ensure the added annotations `source-name` or `source-id` are reflected in the vjailbreak RDM disk custom resource. Use the VML ID of the RDM disk from VMware. + +If source details are not correct, edit the Notes section of VMware VM for correct value and wait for reconcilation ( few minutes ), to get source details updated. + +### 2. Ensure RDM disk reference is correctly populated in vmwaremachine + +For each VM's to be migrated, list vm details on vjailbreak using below command: + +```bash +kubectl describe vmwaremachine -n migration-system +``` + +Ensure vml id of all RDM disks to be migrated appear in the vmwaremachine custom resource. + +### 3. Detach the RDM Disk and Power Off the VM in VMware + +Since VMware does not allow snapshots of a VM with attached RDM disks, you must: + +- **Power off** the VM to be migrated. +- **Detach the RDM disk** from the VM (steps are mentioned below). + +Optional: Once the RDM disk is detached,you can list the vmwaremachine custom resource and ensure the VML ID of all RDM disks to be migrated appear in rdmDisk section of vmwaremachine custom resource in vjailbreak. + +```bash +kubectl describe vmwaremachine -n migration-system +``` + +**Note:** Once the RDM disk is detached, the `source-name` or `source-id` should not change, and the VMs that own the RDM disk should not change. If you need to detach the RDM disk from the VM and remove all RDM references from the VMs, you must handle it manually +
+ +Delete the `vmwaremachine` and `rdmdisk` custom resources on vjailbreak. After deletion wait for the configured reconciliation time, and re ensure that deleted resources are recreated by vjailbreak. + +**Commands to delete VMware machine and RDM disk:** + +```bash +kubectl delete vmwaremachine -n migration-system +``` + +```bash +kubectl delete rdmdisk -n migration-system +``` + +**Commands to verify VMware machine and RDM disk are recreated** + +```bash +kubectl describe vmwaremachine -n migration-system +``` + +```bash +kubectl describe rdmdisk -n migration-system +``` + +**Steps to detach RDM disks in VMware:** + +For each VM, go to **Edit Settings** and perform following steps. _Note down the details as you might need them in case you have to revert the migration._ + +1. Click on the cross icon near the RDM disks, and keep "Delete files from storage" **unchecked**. +2. Remove the SCSI controller used by these disks (this will be in Physical sharing mode). + +Note: Only remove the SCSI controller in Physical Sharing mode. Other volumes or non-RDM disks use different controllers (not in Physical Sharing mode), and those must not be deleted. + +![Detach RDM Disk in VMware](https://raw.githubusercontent.com/platform9/vjailbreak/refs/heads/gh-pages/docs/src/assets/vmware-removing-rdm-disk.png) + +This ensures that the snapshot and migration can proceed without errors. + +### 4. Patch RDM Disk with the Required Fields + +Edit each RDM disk to add `cinderBackendPool` and `volumeType`. Example: + +```bash +kubectl patch rdmdisk -n migration-system -p '{"spec":{"openstackVolumeRef":{"cinderBackendPool":"backendpool_name","volumeType":"volume_type"}}}' --type=merge +``` + +The volume type specified here must match the configuration the RDM disk volume has on the SAN array. Example: if the volume has de-duplication and compression enabled, the specified volume type on OpenStack side must have these settings enabled. + +### 5. Create Migration Plan + +Create a migration plan using the CLI. +Follow the detailed CLI steps here: + +[Migrating Using CLI and Kubectl](https://platform9.github.io/vjailbreak/guides/cli-api/migrating_using_cli_and_kubectl/) + +Note: + +- While creating migration plan , make sure actual VM name is passed in `spec.virtualMachines` of migrationplan and not vm custom resource name. +- Migration plan `spec.migrationStrategy.type` should be cold - RDM disk can only be migrated with cold migrationStrategy + +### 6. Wait for Disk to Become Available + +Confirm that the rdm disk is in **Available** state: + +```bash +kubectl get rdmdisk -n migration-system -o yaml +``` + +Look for: + +```yaml +status: + phase: Available +``` + +### 7. Ensure All the VMs in Cluster are Migrated + +1. Check that the RDM disk is available as a volume in PCD or OpenStack. + +2. Ensure all VMs in the cluster are migrated. + +### 8. Retrying failed migrations + +If VM migrations fails, but RDM disks have been successfully managed by Cinder [Step 6](#6-wait-for-disk-to-become-available), migration can be retried. + +### Rollback Plan - If Migration Fails + +### ⚠️ Caution: + +Once an RDM disk is managed in OpenStack or PCD, do not delete the corresponding volume from PCD/OpenStack during a rollback. +Deleting the volume will also remove the associated LUN reference from the storage array, resulting in irreversible data loss. + +To unmanage an RDM disk safely, use the following command instead of deleting it directly: + +`openstack volume delete --remote` + +1. Delete VMs created in PCD or OpenStack. +2. Remove the managed volume from OpenStack without deleting it from the SAN array: + + ```bash + openstack volume delete --remote + ``` + +- [Volume Delete](https://docs.redhat.com/en/documentation/red_hat_openstack_platform/10/html/command-line_interface_reference_guide/openstackclient_subcommand_volume_delete) + +3. Re-attach RDM disk in VMware to powered-off VMs: + + - Add the reference VMDK disks. + - Add **New Device > Existing Hard Disk**. This will add the disk as a new hard disk. + - Change the controller of this hard disk to **"New SCSI Controller"** which was created in the first step. + - For each VM, go to **Edit Settings** and add the SCSI controller for disk and select physical sharing mode. + + Repeat this process for all RDM disks. + +![Re-attach RDM disk on failure](https://raw.githubusercontent.com/platform9/vjailbreak/refs/heads/gh-pages/docs/src/assets/vmware-adding-back-disk.png) + +4. Power on all the VMs on VMware. diff --git a/docs/src/content/docs/guides/CLI-API/migrating_using_cli_and_kubectl.md b/docs/src/content/docs/guides/CLI-API/migrating_using_cli_and_kubectl.md new file mode 100644 index 0000000..cecaf2d --- /dev/null +++ b/docs/src/content/docs/guides/CLI-API/migrating_using_cli_and_kubectl.md @@ -0,0 +1,502 @@ +--- +title: "Use CLI to Migrate" +description: "Learn how to automate the migration of Virtual Machines from VMware to PCD using vJailbreak" +--- +vJailbreak comprises multiple Kubernetes Controllers which work on Custom Resources (CRs). When we perform the migrations through the UI, the UI itself takes care of creating these CRs for us, hence paving the way for migration to happen. In this tutorial, we will understand how we can migrate a VM using vJailbreak via CLI. + + +## Flow of information and resource creation + +Before moving to the action, let’s understand the various resources that will be created to perform a migration, and how they are related to each other. +![image](../../../../assets/information-flow.png) + +**Note**: In the diagram above, the specification/configuration flows towards the arrow, and the status travels backwards (from `v2v-helper` Pod up till `MigrationPlan`). + + +### Glossary + +Let’s get familiar with the resources mentioned in the diagram above. These resources are used by vJailbreak to perform the migration. + + + +* **NetworkMapping**: Defines how source VMware networks map to destination OpenStack networks. Required for configuring network interfaces for VM's during migration. +* **StorageMapping**: Maps source VMware datastores to OpenStack storage backends. +* **MigrationTemplate**: It defines a reusable set of configurations for migrating virtual machines (VM's) from VMware to OpenStack. +* **MigrationPlan**: MigrationPlan contains the VM's to be migrated, provides reference to migration template which needs to be followed, and also specifies the way VM's have to be migrated. A migration plan can be used for migrating multiple VM's in batches. +* **Migration**: For each VM, there is an individual migration custom resource, which provides reference to the migration plan to be followed and the pod which is going to execute the migration. Migration custom resource is maintained by the vJailbreak controller itself. +* **Job**: With migration custom resource in place, the vJailbreak controller creates a Kubernetes Job, which will in turn create the v2v-helper pod to execute migration for the particular VM mentioned in the Migration custom resource. +* **v2v-helper pods**: Based on the definition of the job created by vJailbreak controller, kubernetes creates a pod to perform the actual VM migration. These pods are temporary workloads that run the helper logic and handle all the migration steps such as image conversion, driver injection, guest customization, transfer, and creation of OpenStack resources. + +**Note**: `NetworkMapping`, `StorageMapping`, `MigrationTemplate`, `MigrationPlan`, and `Migration` are Custom Resources. + +Now that we have a basic understanding of the resources that will be useful during our journey, let’s start on the migration part. + + +## How to migrate a VM using CLI or kubectl? + + +### What are we going to do? + +First, we will gather some configuration details. We will use those pieces of configuration details together to create some custom resources, which will be then picked by the Migration Controller, and the migration will take place accordingly. While migration takes place, we will check various resources to monitor the progress of the migration. + +* [Gather Information](#gather-information) + * [Gather the credentials to VMware vCenter and OpenStack](#gather-the-credentials-to-vmware-vcenter-and-openstack) + * [Gather the details of the VM to be migrated](#gather-the-details-of-the-vm-to-be-migrated) + * [Gather the details of OpenStack networks and volumeTypes](#gather-the-details-of-openstack-networks-and-volumetypes) +* [Creation of Kubernetes resources](#creation-of-kubernetes-resources) + * [Create the StorageMapping custom resource](#create-the-storagemapping-custom-resource) + * [Create the NetworkMapping custom resource](#create-the-networkmapping-custom-resource) + * [(Optional) Explicitly select the target OpenStack flavor](#optional-explicitly-select-the-target-openstack-flavor) + * [Create the MigrationTemplate custom resource](#create-the-migrationtemplate-custom-resource) + * [Create the MigrationPlan custom resource](#create-the-migrationplan-custom-resource) +* [Monitor the progress](#monitor-the-progress) + * [Checking Migration (custom resource)](#migration-custom-resource) + * [Checking Migration Job](#migration-job) + * [Checking v2v-helper pod](#v2v-helper-pod) + + +### Assumptions + +Before we start, ensure the following prerequisites are fulfilled: + +* vJailbreak is installed and configured properly +* VMware vCenter credentials and OpenStack credentials are already set up +* User is familiar with Kubernetes and has access to the vJailbreak VM +* All the resources will be created in the same namespace + +Ensure that you follow the steps in sequence to gather information and create respective Kubernetes objects in sequence to initiate migration for a VM from VMware to PCD. + +### Gather Information + +For creating the resources, we will need some values to put into the YAML manifests. Let’s gather those values first. + + +#### Gather the credentials to VMware vCenter and OpenStack + +Check the resource name for the VMware vCenter credentials using the following command: + +``` +kubectl get vmwarecreds -n +``` + +**Note**: The default namespace for vJailbreak ecosystem is `migration-system`. + +Copy the name of the `VMwareCreds` resource that refers to the vCenter hosting the VM you want to migrate. This will be used when creating the MigrationTemplate. For example, if following is the output of the above command: + +``` +$ kubectl get vmwarecreds -n migration-system +NAME STATUS +vcenter-a Succeeded +``` + +Then, the name of the `VMwareCreds` would be `vcenter-a`. + +Similarly, check the resource name for the OpenStack credentials using the following command: + +``` +kubectl get openstackcreds -n +``` + + +Copy the name of the `OpenstackCreds` resource that refers to the OpenStack where you want to migrate the VM. This will also be used when creating the MigrationTemplate. + +For example, if following is the output of the above command: + +``` +$ kubectl get openstackcreds -n migration-system +NAME STATUS +openstack-a Succeeded +``` + +Then the name of the `OpenStackcreds` would be `openstack-a`. + + +#### Gather the details of the VM to be migrated + +Get the details of the VM using `VMwareMachine` resource and details are correct: +``` +kubectl get vmwaremachine -n -o yaml +``` + +From the YAML, note down the following: +* VM's name using `spec.vms.name` +* Get the datastores under `spec.vms.datastores`. This will be used for storage mapping. +* Get the networks under `spec.vms.networks`. This will be used for network mapping. +* (Optional) Get the Operating System type under `spec.vms.osFamily`. The reason for keeping it optional is that vJailbreak can auto-detect the OS most of the time, if the VM is turned on in VMware. For cold migration or migration of a VM that is in powered off state, this field will be required. +* (Optional) Note the VM's `spec.vms.cpu` and `spec.vms.memory`. If you don't explicitly choose a target flavor (see [Explicitly select the target OpenStack flavor](#optional-explicitly-select-the-target-openstack-flavor) below), vJailbreak uses these values to auto-select the closest matching OpenStack flavor at migration time. + +For example, if following is the output of the above command: + +``` +$ kubectl get vmwaremachines -n migration-system vm-1 -o yaml + +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: VmwareMachine +metadata: + name: vm-1 + namespace: migration-system + creationTimestamp: "2025-05-27T09:27:34Z" + generation: 1 + labels: + pcd-vm-openstack-a: d3f3d5c7-f6d8-410e-bb9f-a7afe4j8nhg + vmwarecreds.k8s.pf9.io-vcenter-a: "true" +spec: + vms: + clusterName: cluster-1 + esxiName: esxi-1 + ipAddress: + cpu: 1 + memory: 4096 + name: vm-1 + osFamily: linuxGuest + vmState: running + datastores: + - datastore-1 + - datastore-2 + disks: + - Hard disk 1 + - Hard disk 2 + networks: + - network-1 + - network-2 +``` + +Then: +* The name of the datastores would be: `datastore-1` and `datastore-2` +* The name of the networks would be: `network-1` and `network-2` +* The OS family would be: `linuxGuest` + +After gathering the information about the source VM’s datastores and networks, we will need to gather the information about the networks and volume types present on the OpenStack, so that we can create proper StorageMapping and NetworkMapping configurations. + + +#### Gather the details of OpenStack networks and volumeTypes + +Using the name of the OpenstackCreds resource that we recently checked, run the following command to get the configuration: + +``` +kubectl get openstackcreds -n -o yaml +``` + + +From the YAML, note down the following: +* List of the networks available on OpenStack at `status.openstack.networks` +* List of volume types available on OpenStack at `status.openstack.volumeTypes` + +For example, if following is the output of the above command: + +``` +$ kubectl get openstackcreds -n migration-system openstack-a -o yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: OpenstackCreds +metadata: + name: openstack-a + # … +spec: + # … +status: + openstack: + networks: + - vlan1 + - vlan2 + - vlan3 + volumeTypes: + - lvm + - lvm-class-2 + - lvm-class-3 + +``` + +Then: +* VMware networks `network-1` and `network-2` can be translated to OpenStack networks `vlan1`/`vlan2`/`vlan3` +* VMware datastores `datastore-1` and `datastore-2` can be translated to OpenStack volume types `lvm`/`lvm-class-2`/`lvm-class-3` + +We will see these translations in action when we create NetworkMapping and StorageMapping resources in the next section, respectively. + + +### Creation of Kubernetes resources + +Now that we have gathered the information that we required, let’s start creating the Kubernetes resources to initiate the migration. + + +#### Create the StorageMapping custom resource + +Let’s create the `StorageMapping` custom resource to ensure that virtual disks are placed correctly in the destination environment. Below is how its manifest would look like: + + +##### Manifest + +In the manifest, we need to provide a mapping between the source datastores that we captured from the `VMwareMachine` (namely `datastore-1` and `datastore-2`), and will map them with the target volume types that we captured from the `OpenstackCreds`. + +``` +$ cat storage-mapping.yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: StorageMapping +metadata: + name: storagemapping + namespace: migration-system +spec: + storages: + - source: datastore-1 + target: lvm + - source: datastore-2 + target: lvm +``` + +##### How to apply this configuration? + +``` +kubectl apply -f storage-mapping.yaml +``` + +Read more about `StorageMapping` in [the CRDs reference document](https://platform9.github.io/vjailbreak/reference/reference/#datastore-mapping). + + +#### Create the NetworkMapping custom resource + +Now, let’s create a `NetworkMapping` custom resource to define how networks will be translated from VMware to OpenStack. Below is how its Kubernetes manifest would look like. + + +##### Manifest + +In the manifest, we need to provide target networks for both the networks that we captured from the VM (namely `network-1` and `network-2`), and will map them with the target networks that we captured from the `OpenstackCreds`. + +``` +$ cat network-mapping.yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: NetworkMapping +metadata: + name: networkmapping + namespace: migration-system +spec: + networks: + - source: network-1 + target: vlan1 + - source: network-2 + target: vlan2 +``` + +##### How to apply this configuration? + +``` +kubectl apply -f network-mapping.yaml +``` + +Read more about `NetworkMapping` in [the CRDs reference document](https://platform9.github.io/vjailbreak/reference/reference/#network-mapping). + + +#### (Optional) Explicitly select the target OpenStack flavor + +By default, vJailbreak picks the target VM's OpenStack flavor automatically: it looks for a flavor whose vCPU and RAM exactly match the source VM, or the next best match if there is no exact match. This "best guess" match is based on size alone, so if your OpenStack environment has multiple flavors with the same vCPU/RAM but different extra specs or tags (for example, different host aggregates, GPU passthrough, or hotplug support), vJailbreak may not pick the one you intended. + +To avoid this, you can explicitly pin a VM to a specific flavor by setting `spec.targetFlavorId` on its `VMwareMachine` custom resource, before creating the `MigrationPlan`. + +First, find the flavor ID you want to use. Using the OpenStack CLI against your target OpenStack/PCD environment, list the available flavors: + +``` +openstack flavor list +``` + +This prints each flavor's ID, name, vCPU count, RAM, and disk size, for example: + +``` ++--------------------------------------+--------------+------+------+-----------+-------+-----------+ +| ID | Name | RAM | Disk | Ephemeral | VCPUs | Is Public | ++--------------------------------------+--------------+------+------+-----------+-------+-----------+ +| d2a1e2d0-1111-4a3e-9c3e-abc123456789 | m1.medium.gpu| 8192 | 40 | 0 | 4 | True | +| f8b2c3d1-2222-4b4f-8d4f-def456789012 | m1.medium | 8192 | 40 | 0 | 4 | True | ++--------------------------------------+--------------+------+------+-----------+-------+-----------+ +``` + +Identify the flavor that matches what you want (by name and/or by inspecting its extra specs/tags with `openstack flavor show `), and copy its `ID` column. + +Once you've identified the flavor ID you want (`d2a1e2d0-1111-4a3e-9c3e-abc123456789` in this example), patch the `VMwareMachine` resource for the VM being migrated: + +``` +kubectl patch vmwaremachine -n migration-system --type merge -p '{"spec":{"targetFlavorId":"d2a1e2d0-1111-4a3e-9c3e-abc123456789"}}' +``` + +You can confirm it was set correctly with: + +``` +kubectl get vmwaremachine -n migration-system -o jsonpath='{.spec.targetFlavorId}' +``` + +**Note**: `targetFlavorId` is read once, when vJailbreak creates the migration ConfigMap for the VM (as part of `MigrationPlan` processing). Set it on the `VMwareMachine` before creating the `MigrationPlan` for that VM. If it is left blank, vJailbreak falls back to the automatic best-match flavor selection described above. + +#### Create the MigrationTemplate custom resource + +Now that we have `StorageMapping` and `NetworkMapping` resources in place, let’s create a `MigrationTemplate`. As the name suggests, `MigrationTemplate` can be created once for a set of `VMwareCreds`, `OpenstackCreds` and the corresponding `NetworkMapping` and `StorageMapping`. This can be reused between various `MigrationPlan` resources. + + +##### Manifest + +In the `MigrationTemplate`, we need to provide the following details: +* Name of the `NetworkMapping` resource +* Name of the `StorageMapping` resource +* `VMwareCreds` resource name +* `OpenstackCreds` resource name +* Type of the OS (Optional) + +``` +$ cat migration-template.yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: MigrationTemplate +metadata: + name: migrationtemplate + namespace: migration-system +spec: + networkMapping: networkmapping + storageMapping: storagemapping + osFamily: linux # valid values: linux, windows (optional — auto-detected when VM is powered on) + source: + vmwareRef: vcenter-a + destination: + openstackRef: openstack-a +``` + + +##### How to apply this configuration? + +``` +kubectl apply -f migration-template.yaml +``` + +Read more about `MigrationTemplate` in [the CRDs reference document](https://platform9.github.io/vjailbreak/reference/reference/#migrationtemplate). + + +#### Create the MigrationPlan custom resource + +After creating the `MigrationTemplate`, we need to create the `MigrationPlan` to configure how migration will be executed. Whether it will be a hot migration, or cold migration? When will the cutover happen? Will the cutover happen automatically or manually? etc. Let’s create a `MigrationPlan` resource. + + +##### Manifest + +In the manifest, we will provide the name of the `MigrationTemplate` to be used, and various different configurations related to migration execution. + +``` +$ cat migration-plan.yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: MigrationPlan +metadata: + name: migrationplan + namespace: migration-system +spec: + migrationTemplate: migrationtemplate + migrationStrategy: + type: hot + adminInitiatedCutOver: false + performHealthChecks: false + healthCheckPort: "443" + virtualMachines: + - - vm-1 +``` + +To know more about all the supported fields for advanced use-cases, refer to the [MigrationPlan section of the CRD reference document](https://platform9.github.io/vjailbreak/reference/reference/#migrationplan). + +##### Optional MigrationPlan Fields + +The example above shows the minimum required fields. The following optional fields are also supported: + +```yaml +spec: + migrationTemplate: migrationtemplate + + # Retry the migration automatically if it fails + retry: false + + # Shell script executed on first boot of the destination VM + firstBootScript: | + echo "Add your startup script here!" + + # Schedule data copy and cutover windows (RFC 3339 datetime) + migrationStrategy: + type: hot + dataCopyStart: "2024-06-01T02:00:00Z" # when to start copying data + vmCutoverStart: "2024-06-01T04:00:00Z" # start of cutover window + vmCutoverEnd: "2024-06-01T05:00:00Z" # end of cutover window + adminInitiatedCutOver: false + performHealthChecks: false + healthCheckPort: "443" + + # Post-migration actions on the source VM in VMware + postMigrationAction: + renameVm: true + suffix: "_migrated_to_pcd" # appended to source VM name + moveToFolder: true + folderName: "migrated" # VMware folder to move source VM into + + # Advanced: override volume types, networks, or ports per VM + advancedOptions: + granularVolumeTypes: + - lvm + granularNetworks: + - vlan1 + granularPorts: + - "port-uuid-1" + + virtualMachines: + - - vm-1 +``` + +##### How to apply this configuration? + + +``` +kubectl apply -f migration-plan.yaml +``` + +Now that we have created all the required resources, let’s see what are the additional resources being created in the background, how do they interconnect, how to find out where the migration of a particular VM is happening, and how to check its progress. + + +### Monitor the progress + + +#### Migration (custom resource) + +Whenever a `MigrationPlan` is created, vJailbreak controller creates a `Migration` custom resource for individual VM's mentioned in the `MigrationPlan`. + + +##### Check the Migration custom resource + +Following the standard naming conventions used by vJailbreak, the `Migration` custom resource has a name like `migration-`. We can confirm if the `MigrationPlan` and VM name are correct or not. To check the specifications of a migration custom resource, run the following command: + + +``` +kubectl get migration -n migration- -o yaml +``` + +* MigrationPlan will be available under: `metadata.labels.migrationplan` or `spec.migrationPlan` +* VM’s name will be placed under: `spec.vmName` +* Once sure that the migration resource is related to the VM we want to track the migration of, check the pod prefix under: `spec.podRef` + + +#### Migration Job + +Migration Job is a Kubernetes Job responsible for migrating one VM from VMware to OpenStack. It runs a pod which performs all tasks required to migrate a single VM including disk extraction, conversion, transfer, and VM creation in the target OpenStack environment. These jobs are created dynamically and are not declared by the user. + + +##### Check the Migration Job + + +* Following the standard naming conventions followed by vJailbreak, the job has a name like `v2v-helper-`. +* We can check the status of the job under the `status` section. + + +#### v2v-helper pod + +v2v-helper pod is the Kubernetes Pod created by Migration Job. It is the runtime unit that performs the actual VM migration, including disk extraction, transformation, and transfer from vSphere to OpenStack. It is the execution engine for migrating one VM. It runs the v2v-helper utility to perform these actions. + + +##### Check the v2v-helper pod logs + + + +* After getting the pod name from the migration custom resource, you can check the logs of the pod using the following command: + +``` +kubectl logs -n +``` + +* To tail the live logs, we can use the `-f` flag. + +This way, we can automate the migration for n numbers of Virtual Machines, from VMware to OpenStack, using vJailbreak via kubectl. diff --git a/docs/src/content/docs/guides/Cluster-Conversion/cluster-conversion.md b/docs/src/content/docs/guides/Cluster-Conversion/cluster-conversion.md new file mode 100644 index 0000000..4945037 --- /dev/null +++ b/docs/src/content/docs/guides/Cluster-Conversion/cluster-conversion.md @@ -0,0 +1,87 @@ +--- +title: Cluster Conversion +description: Configuration and steps to perform cluster conversion +--- + +The following outlines the steps to use the vJailbreak RollingConversion feature, which is exclusively available and compatible with Platform9 Private Cloud Director (PCD). + +## Pre-migration checks + +Before starting the RollingConversion process, ensure the following checks are completed: + +1. Ensure vCenter setup is available +* vCenter credentials must possess sufficient privileges to isolate ESXi hosts, place them into maintenance mode, and subsequently remove them from the inventory. These privileges are supplementary to existing permissions. The precise set of required permissions will be communicated imminently. +2. Ensure PCD (Platform9 Private Cloud Director) setup is available and properly configured. +* Ensure the ClusterBlueprint is configured. +** Within the Rolling Conversion form in vJailbreak UI, it is necessary to specify the desired network configuration to be implemented on the host subsequent to its conversion and onboarding to Platform Cloud Director (PCD). +* Verify network setup on PCD aligns with the ClusterBlueprint specifications. +* Make sure one host is already added to accommodate vJailbreak VM and its agents +3. Ensure Ubuntu MAAS setup is available and configured +* Only Ubuntu 22.04 is currently supported for PCD hosts within the Private Cloud Director (PCD). +* Ensure that the appropriate PXE image is configured based on requirements; a standard Ubuntu 22 image is recommended. +* It is presumed that the ESX hosts are pre-configured as "Machines" in the "Allocated or Deployed" state within MAAS. +* **IMPORTANT**: All ESXi hosts added to MAAS must have IPMI details configured. vJailbreak uses IPMI to control the power state and boot order of the ESXi hosts during the conversion process. Verify the accuracy of the IPMI configuration for all the MAAS machines before starting the rolling conversion. +4. Backup current configurations and data related to vCenter. +5. Ensure vMotion configured correctly on vCenter +* I.e, If an ESX is put in maintenance mode, VMs should be able to move off that ESX +6. Make sure that all non-vmotion compatible VMs are migrated to the destination PCD or moved off to a single ESXI +* When we put an ESXi in maintenance mode, we expect all the VMs to move off of that ESXi and wait for it to become empty. +* Even if a single VM is present on that ESXI, we cannot really re-flash it safely. +7. Ensure enough additional hosts on vCenter (Number of hosts will differ on the exact setup configuration) +* NOTE: We put the ESX hosts in maintenance mode one by one, so the VMs on these hosts are moved off until the host becomes empty. This host is further converted to PCD host. The extra hosts are required to accommodate the moved off VMs +* NOTE: It is not required to have the equal number of extra hosts, ideally just one. +8. Check available disk space on target systems within the PCD environment. + +#### List of incompatible configurations for reference +* VMs with PCIe passthrough or SR-IOV devices +* VMs using local host devices (e.g. USB, CD/DVD) +* VMs with RDM disks in physical mode +* VMs with Fault Tolerance (FT) enabled +* VMs with CPU features not compatible across hosts +* VMs without Enhanced vMotion Compatibility (EVC) in mixed-CPU clusters +* VMs using host-only or non-shared network/storage +* VMs with VMCI or special device interfaces +* VMs in suspended state (for live vMotion) +* VMs with outdated VMware Tools or hardware version + +## Configuration + +Create VMware and OpenStack/PCD credential with "PCD" configuration only PCD is supported for rolling upgrade + +### MAAS configuration + +* **MAAS URL** - the MAAS system should be reachable +* **API Key** additionally the MAAS system should be configured to allow the vJailbreak VM to access it with the key +* **OS** the os configuration that vJailbreak would use in MAAS to direct the MAAS to boot the ESXi into PCD hypervisor + +#### Import the ESXi into MAAS + +If you have ESXi already deployed through MAAS, you can skip this step, else you will need to import ESXis into MAAS so that MAAS can recognize them. + +## How cluster conversion works +After you submit the rolling conversion form, vJailbreak will take the following actions in sequence. + +1. Verify your creds, especially **openstack-creds** for checking if they are **PCD creds** or not +2. Verify Cluster information submitted in the form. +3. Prepare a **special cloud-init script** that will run **post conversion** of this host to a ubuntu machine. +4. Go through the list of VMs specified in the rolling conversion form +5. Formulate and save a **list of ESXis to be converted**. +6. Trigger the conversion process of these ESXi sequentially. +7. Each conversion process includes following steps + 1. Put that ESXi in **maintenance mode** + 2. Wait for all VMs on that ESXi to move off of this ESXi to other hosts **(by DRS/vMotion)** + 3. Once the ESXi is empty, vJailbreak starts the process of converting it to PCD host + 1. Fetch list of all available **“Machines”** in MAAS + 2. Find the correct **“Machine”** for the current ESXi, by checking the **hardwareUUID** received both from vCenter API and MAAS API + 3. Once Machine is found, we fetch its **IPMI configuration** from MAAS, and use to set this machine **PXE (net) boot** + 4. **Release** the machine in MaaS + 5. **Deploy** the machine via MaaS, using the special cloud-init created in earlier steps + 4. Now vJailbreak waits for MaaS to boot that machine to an **ubuntu image** and run the **cloud-init** that we provided. + 5. Its polling mechanism checks the list of PCD hosts and verifies the **hardwareUUID** from MaaS with **hostID** from PCD. (HostID is forcefully set to **hardwareUUID** in the cloud-init) + 6. Once vJailbreak sees the host in PCD in “unauthorised” state, vJailbreak makes API calls to PCD + 1. To apply the “Host Network Configuration” selected for this host during the “**Rolling Conversion Form”** submission + 2. To provide hypervisor role to this host, with an input of the **clusterName** used as **Target** in the “**Rolling Conversion Form”** + 7. Wait for the PCD host to **converge**. + 8. Mark the ESXi Conversion as **Successful**. +8. If at least one ESXi conversion is successful, vJailbreak will start to migrate VMs (from the list in “**Rolling Conversion Form”**) to PCD (to the specified target Cluster) +9. RollingConversion is marked successful if all (selected) ESXi are converted and all (selected) VMs are moved to PCD. \ No newline at end of file diff --git a/docs/src/content/docs/guides/Cluster-Conversion/maas-enablement.md b/docs/src/content/docs/guides/Cluster-Conversion/maas-enablement.md new file mode 100644 index 0000000..5021967 --- /dev/null +++ b/docs/src/content/docs/guides/Cluster-Conversion/maas-enablement.md @@ -0,0 +1,36 @@ +--- +title: Add ESXi to MAAS +description: Configuration and steps to to add ESXi to MAAS +--- + +vJailbreak uses MAAS to manage the ESXi hosts and boot them into PCD hypervisor. +MAAS has a state machine for each machine that it manages. The machines by default are +assumed to be 'empty' and available for deployment. The machines are managed by MAAS by booting them with ephemeral image to discover the hardware. + +## Prerequisites + +**IMPORTANT**: Before adding ESXi hosts to MAAS, ensure that IPMI (Intelligent Platform Management Interface) is configured and accessible for each ESXi host. vJailbreak requires IPMI access to control the power state and boot order of the ESXi hosts during the rolling conversion process. Without proper IPMI configuration, the conversion process will fail. + +Verify the following: +* IPMI is enabled on each ESXi host +* IPMI credentials (username/password) are available +* IPMI interface is network accessible from both the MAAS server and the vJailbreak VM +* IPMI power type is supported by MAAS (e.g., `ipmi`, `LAN_2_0`) + +## Adding ESXi to MAAS + +To enlist an ESXi host into MAAS, you will need to add it to MAAS without MAAS rebooting it +inadvertently. The process in detail is described [[here](https://maas.io/docs/reference-release-notes-maas-3-1#p-11417-enlist-deployed-machines)]. + +This requires you to have a MAAS cli. The MAAS cli can be used to add any machine in the +'deployed' state. + +``` +$ maas $profile machines create deployed=true hostname=mymachine \ +architecture=amd64 mac_addresses=00:16:3e:df:35:bb power_type=ipmi \ +power_parameters_power_address= \ +power_parameters_power_user= \ +power_parameters_power_pass= +``` + +**Note**: Replace `power_type=manual` with appropriate IPMI power type and include IPMI credentials as shown above. \ No newline at end of file diff --git a/docs/src/content/docs/guides/How-to/ai_analysis.md b/docs/src/content/docs/guides/How-to/ai_analysis.md new file mode 100644 index 0000000..37a4114 --- /dev/null +++ b/docs/src/content/docs/guides/How-to/ai_analysis.md @@ -0,0 +1,133 @@ +--- +title: AI-Powered Migration Failure Analysis +description: Use the built-in AI assistant to diagnose failed migrations and get remediation steps +--- + +vJailbreak includes an AI analysis feature that inspects failed migration logs, Kubernetes resource conditions, and known failure patterns to identify root causes and suggest fix steps — without manual log triage. + +:::note +AI analysis is experimental and requires an Anthropic API key configured in **Settings → AI**. +::: + +## Prerequisites + +- vJailbreak v0.4.8 or later +- A failed migration (phase: `Failed` or `ValidationFailed`) +- An Anthropic API key — get one at [console.anthropic.com](https://console.anthropic.com) + +## Setup + +### 1. Configure the Anthropic API key + +1. Navigate to **Settings → AI** in the vJailbreak UI. +2. Enter your Anthropic API key (`sk-ant-...`). +3. Click **Save API Keys**. + +The key is stored in a Kubernetes Secret in `migration-system` and never exposed after saving. The AI service restarts automatically to pick up the new key. + +### 2. Verify the AI service is running + +```bash +kubectl -n migration-system get pods -l app=vjailbreak-ai +``` + +The pod should be in `Running` state. If not, check logs: + +```bash +kubectl -n migration-system logs -l app=vjailbreak-ai +``` + +## Using AI Analysis + +### From the migration detail page + +1. Open a failed migration from the **Migrations** list. +2. Click the **AI Analysis** tab (marked *Experimental*). +3. Click **Analyse with AI**. + +The AI collects: +- Migration CR conditions (primary signal) +- v2v-helper pod exit code and logs +- Controller logs +- Credential validation status +- MigrationPlan spec and template config + +It then returns a structured result: + +| Field | Description | +|-------|-------------| +| **Root Cause** | One-sentence description of the failure | +| **Fix Steps** | Ordered remediation steps (max 5) | +| **Confidence** | `high` / `medium` / `low` / `none` | +| **Doc References** | Links to relevant documentation | + +### From the migration logs drawer + +In the migration logs drawer, an **AI Analysis** tab appears alongside the **Logs** tab. Click it to run the same analysis without leaving the log view. + +### Follow-up questions + +After the initial analysis, ask follow-up questions in the text field at the bottom. The AI retains the analysis context for the conversation. + +Example questions: +- "What does exit code 137 mean and how do I fix it?" +- "How do I check if CBT is enabled?" +- "Can I retry without changing the VDDK version?" + +### Feedback + +Use the **thumbs up / thumbs down** buttons to rate the analysis. Feedback helps improve future analyses. + +### Opening a GitHub issue + +If the root cause is identified, click **Open GitHub Issue** to pre-fill an issue with the migration conditions and error excerpt. If confidence is `none`, the AI provides a checklist of data to collect before filing the issue. + +## Confidence levels + +| Level | Meaning | +|-------|---------| +| `high` | Pattern matched exactly; fix is known and confirmed by logs | +| `medium` | Phase is clear but exact cause uncertain, or logs are partial | +| `low` | Phase identified only; logs missing or too ambiguous | +| `none` | Cannot determine phase or cause from available signals | + +When confidence is `low` or `none`, the first fix step is always diagnostic (gather more data before acting). + +## Known patterns the AI detects + +| Symptom | Root cause | Fix | +|---------|-----------|-----| +| `"exec: already started"` | nbdkit/VDDK process init race or stale socket | Verify VDDK path, clean up and retry | +| Pod exit code 137 | OOM kill | Larger VM flavor or reduce concurrent migrations | +| Pod exit code 139 | Segfault in virt-v2v / libguestfs | Check VDDK version vs ESXi version compatibility | +| `"Failed to connect"` + VMware/ESXi | DNS not resolving for ESXi host | Add ESXi entries to `/etc/hosts` on vJailbreak VM | +| `"permission denied"` or `"401"` + OpenStack | Expired or wrong credentials | Revalidate OpenStack credentials | +| `"No space left on device"` | Scratch disk too small for VMDK | Expand `/var` or configure larger scratch space | +| `"CBT"` / `"Changed Block Tracking"` not enabled | Hot migration prerequisite missing | Enable CBT on source VM in vCenter | +| `"VDDK error"` + error code | VDDK library mismatch or license issue | Verify VDDK version matches ESXi; check VDDK path | + +## Troubleshooting the AI feature + +### "Anthropic API key not configured" + +Navigate to **Settings → AI** and save a valid API key. + +### "AI service unavailable" + +```bash +kubectl -n migration-system get pods -l app=vjailbreak-ai +kubectl -n migration-system logs -l app=vjailbreak-ai --tail=50 +``` + +Check that the `ANTHROPIC_API_KEY` environment variable is populated in the pod — it is injected from the `vjailbreak-ai-secret` Secret. + +### Analysis returns confidence "none" + +The AI could not identify the root cause from available logs. Collect the following before filing an issue: + +- Full debug logs (use the **Download** button in the migration logs drawer) +- `journalctl -u libvirtd -n 200` from the vJailbreak VM +- ESXi host version and vCenter version +- Whether CBT is enabled on the source VM +- VDDK library path: `ls -la /home/ubuntu/vmware-vix-disklib-distrib/` +- vJailbreak version: `kubectl -n migration-system get deployment migration-controller-manager -o jsonpath='{.spec.template.spec.containers[0].image}'` diff --git a/docs/src/content/docs/guides/How-to/building.md b/docs/src/content/docs/guides/How-to/building.md new file mode 100644 index 0000000..1011c9a --- /dev/null +++ b/docs/src/content/docs/guides/How-to/building.md @@ -0,0 +1,24 @@ +--- +title: Build vJailbreak +description: How to compile vJailbreak +--- + +vJailbreak is intended to be run in a Kubernetes environment (k3s) on the appliance VM. In order to build and deploy the Kubernetes components, follow the instructions in `k8s/migration` to build and deploy the custom resources in the cluster. + +:::tip[Did you know?] +Manually building vJailbreak is not required for deployment, only development. +::: + +In order to build v2v-helper, + + make v2v-helper + +In order to build migration-controller, + + make vjail-controller + +In order to build the UI, + + make ui + +Change the image names in the makefile to push to another repository. \ No newline at end of file diff --git a/docs/src/content/docs/guides/How-to/enable_kvm_nested_virtualization.md b/docs/src/content/docs/guides/How-to/enable_kvm_nested_virtualization.md new file mode 100644 index 0000000..c910c34 --- /dev/null +++ b/docs/src/content/docs/guides/How-to/enable_kvm_nested_virtualization.md @@ -0,0 +1,121 @@ +--- +title: Enable KVM for virt-v2v (Nested Virtualization) +description: How to enable nested virtualization so virt-v2v inside the v2v-helper pod can use KVM acceleration instead of falling back to slow TCG emulation. +--- + +## Overview + +vJailbreak runs `virt-v2v` inside a Kubernetes pod (`v2v-helper`) to convert VM disks and inject drivers. By default, `virt-v2v` uses QEMU as its conversion backend. QEMU can run in two modes: + +| Mode | How it works | Performance | +|------|--------------|-------------| +| **KVM** (hardware-accelerated) | Uses `/dev/kvm` — the host kernel's KVM module | Fast | +| **TCG** (software emulation) | Pure software fallback, no `/dev/kvm` required | ~10–20× slower | + +When `/dev/kvm` is not visible inside the pod, QEMU falls back to TCG and logs: + +``` +qemu-kvm: Could not access KVM kernel module: No such file or directory +qemu-kvm: falling back to tcg +``` + +Conversions still succeed but take significantly longer. + +## How vJailbreak exposes `/dev/kvm` + +The v2v-helper pod already mounts the host `/dev` directory into the pod at `/dev` via a `hostPath` volume. This means **if `/dev/kvm` exists on the vJailbreak VM, it is automatically visible inside the pod** — no additional configuration is required. + +The condition that must be met is that the vJailbreak VM itself has access to KVM, which requires nested virtualization to be enabled at every layer of the stack. + +⚠️ **Caution — this affects all VMs on the compute host, not just vjailbreak.** + +## Enabling Nested Virtualization + +### Step 1 — Enable nested KVM on the compute host +On each compute host that will run vjailbreak VMs and agents: + +``` +cat /sys/module/kvm_intel/parameters/nested # expect Y (or 1) +# AMD: cat /sys/module/kvm_amd/parameters/nested +``` + +If disabled, enable it persistently and reload: + +``` +# Intel +echo "options kvm-intel nested=1" | sudo tee /etc/modprobe.d/kvm-nested.conf +# AMD +# echo "options kvm-amd nested=1" | sudo tee /etc/modprobe.d/kvm-nested.conf + +sudo modprobe -r kvm_intel && sudo modprobe kvm_intel # or reboot the host +``` + +### Step 2 — Set the CPU mode in `nova_override.conf` + +`[libvirt]` section of `/opt/pf9/etc/nova/conf.d/nova_override.conf` on each +compute host. + +**Recommended — `host-passthrough`** (passes `vmx`/`svm` through automatically): + +``` +[libvirt] +cpu_mode = host-passthrough +``` + +**Alternative — `host-model`** (must add the flag explicitly; `host-model` does +**not** expose `vmx`/`svm` by default): + +``` +[libvirt] +cpu_mode = host-model +cpu_model_extra_flags = vmx # use svm on AMD +``` + +> **Note:** `host-passthrough` ties the VM to a host with a closely matching +> CPU, model, and microcode for live migration. For vjailbreak — a +> short-lived, migration-tool VM — this is rarely a concern. + +### Step 3 — Apply + +Restart Nova on the compute host, then hard-reboot the vjailbreak VM so it picks +up the new CPU definition (a soft reboot is not enough): + +``` +sudo systemctl restart pf9-ostackhost +openstack server reboot --hard +``` + +### Step 4 — Verify + +Inside the vjailbreak VM, confirm the flag is present: + +``` +grep -E -o 'vmx|svm' /proc/cpuinfo | sort -u # expect vmx (Intel) or svm (AMD) +ls /dev/kvm # device node should exist +``` + +If `virt-v2v` still falls back to TCG (no acceleration), check that +`/dev/kvm` is present and that `kvm-ok` (if available) reports acceleration can +be used. + +### Pod (automatic) + +No changes are needed. The pod's `/dev` hostPath mount makes `/dev/kvm` visible to the `v2v-helper` container as soon as it exists on the vJailbreak VM. + +## Verifying KVM is in use + +After enabling nested virtualization and restarting a migration, inspect the v2v-helper pod logs. You should **not** see the TCG fallback warning. Instead, look for QEMU initializing with KVM: + +```bash +kubectl -n migration-system logs -v2v-helper | grep -i kvm +``` + +A healthy KVM-accelerated run shows no `falling back to tcg` messages and noticeably faster conversion times. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +|---------|--------------|-----| +| `Could not access KVM kernel module` in logs | `/dev/kvm` missing on vJailbreak VM | Enable nested virt on the outer hypervisor and load the KVM module | +| `kvm_intel`/`kvm_amd` module fails to load | Virtualization extensions not exposed by hypervisor | Configure the outer hypervisor to pass through CPU virt flags | +| `/dev/kvm` present on VM but not in pod | Unlikely — the hostPath mount covers all of `/dev` | Confirm the pod spec has the `/dev` hostPath volume (default in vJailbreak) | diff --git a/docs/src/content/docs/guides/How-to/firstboot_script_doc.md b/docs/src/content/docs/guides/How-to/firstboot_script_doc.md new file mode 100644 index 0000000..c1e3c9a --- /dev/null +++ b/docs/src/content/docs/guides/How-to/firstboot_script_doc.md @@ -0,0 +1,204 @@ +--- +title: Firstboot Script +description: Guide on using firstboot scripts during VM migration +--- + + +## Overview +The Firstboot Script feature allows users to run custom scripts automatically on virtual machines (VMs) immediately after they are migrated to Platform9 Cloud Director (PCD) or OpenStack environments. This capability is essential for automating post-migration configurations, installations, and other setup tasks that need to be performed on the VM upon its first boot. + +Following are some use cases for Firstboot Scripts: +1. Installing or updating required software +2. Removing VMware-specific tools or drivers +3. Applying system or network configuration +4. Running environment-specific initialization tasks +5. Executing multiple setup steps sequentially after migration + +The feature supports **multiple script blocks**, **OS-specific targeting**, and **independent execution** of user-provided scripts. + + +### Allowed Script Formats + +User-provided script content depends on the guest operating system. + +1. **WindowsGuests**: `Powershell` (.ps1) +2. **LinuxGuests**: `sh`, `bash` (.sh) + +--- + +## Multiple Script Blocks + +You can include **multiple script blocks** in a single migration plan. +Separate each script block using the delimiter: + +``` +### NEXT SCRIPT ### +``` + +**Example:** + +```text +// WINDOWS-SCRIPT: +Write-Host "Running Windows script part 1" + +### NEXT SCRIPT ### + +// WINDOWS-SCRIPT: +Write-Host "Script 2 failing intentionally" +throw "Failure" + +### NEXT SCRIPT ### + +// WINDOWS-SCRIPT: +Write-Host "Script 3 still runs" +``` + +Each block runs independently. If one script block fails, **later blocks will still execute**. + + +### Execution Rules + +| Script Tag | Execution Behavior | +|------------------|-------------------------------------| +| `WINDOWS-SCRIPT:`| Runs only on Windows VMs | +| `LINUX-SCRIPT:` | Runs only on Linux VMs | +| No tag | Runs on all VMs | + +> **For migration plans containing both Windows and Linux VMs, OS tags are strongly recommended.** + +--- + +## Adding a Firstboot Script in the Migration Form + +To configure a post-migration firstboot script: + +1. Open the **Migration Form** +2. Navigate to the **Migration Options** section +3. Enable **Enable Script** under **Post Migration Script** +4. Paste the script content into the script field +5. Separate multiple scripts using `### NEXT SCRIPT ###` +6. Use OS tags if the migration plan includes different operating systems +7. Start the migration + +![img1](../../../../../public/images/firstboot-form.png) +![img1](../../../../../public/images/firstboot-form-1.png) + + +> **Note:** +> Untagged script blocks run on all selected VMs. + +--- + +## How Firstboot Scripts Work + +### End-to-End Execution Flow + +1. The user enables **Post Migration Script** in the migration form. +2. The script content is stored in the migration plan as `firstBootScript`. +3. The migration controller generates a **per-VM ConfigMap** containing the script. +4. The ConfigMap is mounted into the **v2v-helper pod** at `/home/fedora/scripts`. +5. During conversion, the helper reads the script and splits it into blocks using `### NEXT SCRIPT ###`. +6. Script blocks are filtered based on OS tags. +7. The system prepares OS-specific execution: + +| OS | Execution Model | +|---------|-----------------| +| Linux | Scripts are embedded into a generated wrapper | +| Windows | Scripts are converted into PowerShell parts and executed through a scheduler | + +8. When the migrated VM boots for the first time, the prepared scripts execute automatically but needs multiple reboots to complete. + +:::caution[Not available for LDM system volumes] +As step 5 shows, firstboot scripts are prepared **during conversion**. Windows VMs +whose system volume is on a dynamic disk (LDM) skip conversion, so the Post +Migration Script is not installed and will not run. Anything it would have done +must be performed manually inside the guest. See +[Windows Dynamic Disk (LDM) Migration](../windows-ldm-migration/). +::: + + +## Linux Execution Model + +For **Linux guests**, applicable script blocks are combined into a generated wrapper script. + +The wrapper: +- Executes each user script block using Bash +- Continues execution even if one block fails +- Logs warnings when failures occur + +--- + +## Windows Execution Model + +For **Windows guests**, applicable script blocks are converted into **PowerShell script parts**. + +Example generated scripts: +``` +user_firstboot_part_001.ps1 +user_firstboot_part_002.ps1 +``` + +These scripts are executed using a **Windows Firstboot Scheduler**. + +--- + +## Windows Firstboot Scheduler + +The **Windows Firstboot Scheduler** orchestrates execution of built-in and user-provided scripts. +It ensures scripts run safely and continue even if reboots occur. + +### Scheduler Responsibilities +The scheduler: +- Executes scripts sequentially +- Tracks execution progress +- Survives system reboot +- Retries failed scripts +- Continues later scripts even if earlier user scripts fail + +### Scheduler Files + +| File | Purpose | +|-------------------------------------------|----------------------------------| +| `C:\firstboot\0-Firstboot-Scheduler.ps1` | Main scheduler script | +| `C:\firstboot\Firstboot-Scheduler.log` | Scheduler execution log | +| `C:\firstboot\Firstboot-Scheduler_init.log`| Scheduler initialization log | +| `C:\firstboot\Firstboot-Scheduler.state` | Execution state tracking | +| `C:\firstboot\scripts.json` | Script metadata | + +--- + +## Troubleshooting + +### Windows Guests +Primary troubleshooting locations: + +| File | Purpose | +|-------------------------------------------|--------------------------------------| +| `C:\firstboot\Firstboot-Scheduler.log` | Scheduler execution logs | +| `C:\firstboot\Firstboot-Scheduler_init.log`| Scheduler initialization logs | +| `C:\firstboot\Firstboot-Scheduler.state` | Scheduler execution state | +| `C:\firstboot\scripts.json` | Script metadata | + +You may also check the guestfs log: + +``` +C:\Program Files\Guestfs\Firstboot\log.txt +``` + +This log confirms that the injected firstboot mechanism started successfully. + +### Linux Guests +Check the firstboot execution log with elevated privileges: + +``` +/root/virt-sysprep-firstboot.log +``` + +This log contains: +- Output from each script block +- Errors encountered during execution +- Warnings for failed script blocks + +## Link to Readily Available Firstboot Scripts +1. [Windows VMware tools Removal Script](https://github.com/platform9/vjailbreak/blob/main/scripts/firstboot/windows/vmware-tools-deletion.bat) - A script to remove VMware tools/Drivers from Windows VMs +2. [VMware Residual Artifacts Documentation](https://platform9.github.io/vjailbreak/guides/troubleshooting/vmware_residual_artifacts/) - Scan-based record of leftover VMware artifacts after uninstallation in v0.4.1 diff --git a/docs/src/content/docs/guides/How-to/gpo_migration.md b/docs/src/content/docs/guides/How-to/gpo_migration.md new file mode 100644 index 0000000..4a4e004 --- /dev/null +++ b/docs/src/content/docs/guides/How-to/gpo_migration.md @@ -0,0 +1,54 @@ +--- +title: Group Policy Object (GPO) VM Migration +description: Guide for disabling Group Policy settings that may interfere with VM migration operations +--- + +## Overview + +Some Group Policy Object (GPO) settings can interfere with driver injection, which is critical for a successful VM migration. **GPO is a Windows-specific feature** that manages system settings for Windows VMs. Temporarily disabling these policies ensures that driver injection can proceed reliably. + +### What is Affected by GPO + +**Driver installation cannot be done properly, leading to:** +- Blue Screen of Death (BSOD) +- Windows VM getting stuck in boot loop +- Migration failures due to hardware driver conflicts + +### What to Do + +**Option 1: Disable via GUI - Step by Step** + +1. Open Group Policy Editor (`gpedit.msc`) + +2. Navigate to Driver Policies + + Go to: + ``` + Computer Configuration + → Administrative Templates + → System + → Device Installation + ``` + +3. Disable ALL restrictive policies here + + Check and set these to Not Configured (or Disabled where applicable): + + - 🚫 **Critical ones (must fix)** + - `Prevent installation of devices not described by other policy settings` → Set to Not Configured + - `Prevent installation of devices that match any of these device IDs` → Not Configured + - `Prevent installation of devices using drivers that match these device setup classes` → Not Configured + +**Option 2: Sure Shot Way - Remove GPO via PowerShell** +```powershell +Remove-Item -Recurse -Force "C:\Windows\System32\GroupPolicy" -ErrorAction SilentlyContinue +Remove-Item -Recurse -Force "C:\Windows\System32\GroupPolicyUsers" -ErrorAction SilentlyContinue +Remove-Item -Recurse -Force "HKLM:\Software\Policies" -ErrorAction SilentlyContinue +Remove-Item -Recurse -Force "HKCU:\Software\Policies" -ErrorAction SilentlyContinue + +gpupdate /force +``` + +:::warning +The PowerShell method completely removes all local Group Policy settings. Use this only when GUI methods fail or when you need to ensure complete GPO removal. +::: \ No newline at end of file diff --git a/docs/src/content/docs/guides/How-to/injecting_custom_env.md b/docs/src/content/docs/guides/How-to/injecting_custom_env.md new file mode 100644 index 0000000..232a5c6 --- /dev/null +++ b/docs/src/content/docs/guides/How-to/injecting_custom_env.md @@ -0,0 +1,71 @@ +--- +title: "Inject Environment Variables" +description: "Enabling environment variable injection for the VJB pods using a Kubernetes ConfigMap" +--- + +Injecting environment variables into the VJB pods is a feature that allows users to inject environment variables into the VJB pods using a Kubernetes ConfigMap. +## Injecting Environment Variables During vJailbreak VM Provisioning +1. **Cloud-init populates environment variables** + + Users must provide environment variables in the `/etc/pf9/env` file during provisioning, typically using a cloud-init script. + + Example cloud-init configuration using `write_files`: + ```yaml + write_files: + - path: /etc/pf9/env + content: | + http_proxy=http://: + https_proxy=http://: + no_proxy=localhost,127.0.0.1 + permissions: '0644' + ``` + +2. **ConfigMap creation from /etc/pf9/env** + + A helper script or manual command reads `/etc/pf9/env` and creates a Kubernetes ConfigMap named `pf9-env`. + This is done while the vjailbreak VM is being provisioned. + ```bash + kubectl create configmap pf9-env --from-env-file=/etc/pf9/env -n migration-system + ``` + + You can either populate the `/etc/pf9/env` file via cloud-init or manually. + + If done manually please follow the steps mentioned in [Injecting Environment Variables Post-Provisioning](#injecting-environment-variables-post-provisioning): + + + Now this will be picked up by the v2v-helper pod and the proxy variables will be available in the pod and it would be respected by the v2v-helper pod. + +## Injecting Environment Variables Post-Provisioning + +If you would like to inject environment variables after the vjailbreak VM has been provisioned, follow these steps: +1. **Delete the existing ConfigMap** + + ```bash + kubectl delete configmap pf9-env -n migration-system + ``` +2. **Populate the `/etc/pf9/env` file with whatever env variables needed** + + ```bash + echo "http_proxy=http://:" >> /etc/pf9/env + echo "https_proxy=http://:" >> /etc/pf9/env + echo "no_proxy=localhost,127.0.0.1" >> /etc/pf9/env + ``` + +3. **Create a new ConfigMap** + + ```bash + kubectl create configmap pf9-env --from-env-file=/etc/pf9/env -n migration-system + ``` + +4. **Restart the controller manager deployment** + + ```bash + kubectl rollout restart deployment migration-controller-manager -n migration-system + ``` + +5. **Trigger a new migration for the envs to be reflected in the pod** + + Trigger via UI or via api. + + + \ No newline at end of file diff --git a/docs/src/content/docs/guides/How-to/migration_templates.md b/docs/src/content/docs/guides/How-to/migration_templates.md new file mode 100644 index 0000000..22d5735 --- /dev/null +++ b/docs/src/content/docs/guides/How-to/migration_templates.md @@ -0,0 +1,145 @@ +--- +title: Migration Templates +description: Save a migration configuration as a reusable template, then apply it to new migrations instead of filling in the form every time +--- + +Operators who migrate many VMs usually reuse the same configuration: the same source vCenter, the same destination cluster, the same network and storage mappings, the same copy method and cutover policy. A **Migration Template** saves that configuration once so every later migration can start from it. + +A template stores everything the migration form asks for **except which VMs to migrate**. You pick the VMs fresh each time. + +Templates only pre-fill the form. They do not change how a migration runs — a migration started from a template behaves exactly like one configured by hand. + +## Where templates live + +Open **Migrations** in the vJailbreak UI. It has two tabs, each with a count badge: + +- **Migrations** — the existing migration list. +- **Templates** — saved templates. + +The Templates tab toolbar sits inline with the tabs and offers: + +| Control | Behavior | +|---|---| +| **Search** | Matches the template name and description | +| **Filter** (funnel icon) | Filters by migration type — Hot copy, Cold copy, or Mock copy. Choose **Clear filter** to remove it. | +| **Sort** | **Newest** (default) or **Name** | +| **Grid / list toggle** | Card grid (default) or a dense table | + +## Prerequisites + +At least one VMware credential and one PCD credential must exist. Until both are present, **Create New Template** and **Save as template** are disabled. + +## What a template saves + +| Group | Saved values | +|---|---| +| Source | VMware credential, source vCenter cluster | +| Destination | PCD credential, target PCD cluster | +| Mappings | Network mappings, storage mappings, datastore-to-array-credential mappings | +| Copy | Storage copy method (standard, vJailbreak Accelerated Copy with its proxy VM, or Storage Accelerated Copy), migration type (hot, cold, or mock), data copy start time | +| Cutover | Cutover option — immediate, time window, or admin-initiated — with its start and end times | +| Placement | Security groups, server group, DHCP fallback, disconnect source network | +| Post-migration | First boot script, post-migration actions (rename VM, move to folder) | +| Metadata | Preserve source tags, custom metadata key-value pairs | +| Advanced | Network persistence, remove VMware Tools, periodic sync and its interval, image profiles, network conflict acknowledgement, GPU flavor, data-only mode, guest OS family | + +**Not saved**, by design: + +- The VM selection, and anything attached to a specific VM — assigned IPs, MAC preservation, and per-VM flavor. + +## Create a template + +There are three ways to create one. + +### From a migration you are configuring + +1. Click **Start Migration** and fill in the form. +2. Click **Save as template** at the bottom left of the form. +3. Enter a name, optionally a description, and click **Save template**. + +The form stays open — saving a template does not start or cancel the migration you were configuring. + +### From the Templates tab + +Use this when your goal is the template itself, not a migration. + +1. Open the **Templates** tab and click **Create New Template**. +2. The migration form opens in **Create Template** mode. It differs from the normal form in three ways: + - There is no **Select VMs** step. + - The mapping step lists every network and datastore in the selected source cluster, instead of only those used by selected VMs. + - Partial mappings are allowed — you do not have to map everything for the template to be valid. +3. Click **Create Template**, name it, and save. + +No migration or migration plan is created by this flow. + +### By cloning + +Click the clone icon on a template card or list row. The copy is named ` (copy)`, or ` (copy) 2` if that name is taken. The original is untouched. + +### Naming rules + +Template names must be unique. + +## Use a template + +Click **Use** on a template card, list row, or in its detail drawer. The **Start Migration** form opens with the template's configuration applied, and every field remains editable. + +You still choose the VMs. Because mappings are saved for the whole cluster while a migration maps only what the selected VMs use, vJailbreak keeps the template's mappings aside and applies them as they become relevant: + +- Before you select any VM, saved mappings are not shown — none of their sources are in play yet. +- As you select VMs, each saved mapping whose source network or datastore appears, and whose target still exists on the destination, is applied automatically. +- De-selecting a VM removes the mappings only that VM needed. Re-selecting it brings them back. +- A mapping you delete by hand stays deleted, and is not re-applied when the VM list changes. + +Submitting the form creates an ordinary migration. The template is not modified, and no link is kept between the two. + +## View template details + +Click a card or row (anywhere except an action button) to open the detail drawer: + +- Name, description, and created date +- **Source & Destination** — source credentials, destination credentials, tenant or project, target cluster +- **Network & Storage Mappings** — every mapping pair, plus the storage copy method +- **Migration Options** — migration mode, cutover, guest OS +- **Advanced options** — each option that is set, with its value + +The drawer also carries **Use**, **Edit**, **Clone**, and **Delete** actions. + +## Edit a template + +Templates are editable in place, so infrastructure changes do not force you to delete and recreate them. + +1. Click the pencil icon on the card or list row, or **Edit** in the detail drawer. +2. The form opens in **Edit Template** mode, pre-filled the same way **Use** pre-fills it. As in Create Template mode, there is no VM step. +3. Change what you need and click **Save Changes**. + +The same template is updated — no duplicate is created, and the name and position in the list stay the same. You can also change the name and description in the save dialog. + +If someone else changed the same template while your form was open, the save fails with an error instead of silently overwriting their change. Reopen the template and reapply your edits. + +## Delete a template + +Click the trash icon on the card or list row, or **Delete** in the detail drawer, then confirm. Deleting a template does not affect migrations already created from it — they hold their own copy of the configuration and continue to run and display normally. + +## Templates from the command line + +Saved templates are `MigrationBlueprint` custom resources in the `migration-system` namespace: + +```bash +kubectl -n migration-system get migrationblueprints +kubectl -n migration-system get migrationblueprint -o yaml +``` + +These are separate from the internal, per-session `MigrationTemplate` objects that the migration form creates and cleans up on its own. See [vJailbreak CRDs](../../../reference/reference/#migrationblueprint) for the field list. + +## Troubleshooting + +| Symptom | Cause | What to do | +|---|---|---| +| **Create New Template** and **Save as template** are disabled | No VMware or no PCD credential exists | Add both credentials first | +| Cluster dropdowns are empty after clicking **Use** | The saved source or target cluster no longer exists, or its name changed | Select the clusters manually. +| Saved mappings do not appear after **Use** | Expected until VMs are selected | Select VMs. Mappings apply as their source networks and datastores come into scope. | +| A mapping never reappears after being removed | Mappings deleted by hand are not re-applied | Add the mapping again manually | +| Saving reports *"A template named … already exists"* | Names are unique, ignoring case | Pick a different name | +| **Save Changes** fails on an edit | The template changed elsewhere since the form opened | Reopen the template and reapply the change | +| Templates tab shows "No templates match" | A search term or type filter is active | Clear the search box, or choose **Clear filter** in the filter menu | \ No newline at end of file diff --git a/docs/src/content/docs/guides/How-to/network-traffic-separation.md b/docs/src/content/docs/guides/How-to/network-traffic-separation.md new file mode 100644 index 0000000..aea4bd0 --- /dev/null +++ b/docs/src/content/docs/guides/How-to/network-traffic-separation.md @@ -0,0 +1,81 @@ +--- +title: Configuring Dedicated Data Network for Migrations +description: How to separate vCenter management traffic from disk-copy data traffic on the vJailbreak VM to avoid network contention and asymmetric routing. +--- + +In environments where security requirements mandate separate networks for control-plane and storage traffic (e.g., a VMware underlay with a dedicated storage network that differs from the PCD host underlay), a single shared network path for both vCenter API calls and disk-copy data can cause congestion, dropped transfers, or asymmetric routing failures. + +This guide explains the traffic separation design and the routing configuration required on the vJailbreak VM. + +## Architecture Overview + +![VMware to KVM migration dedicated data network diagram](../../../../../public/images/network-traffic-separation.png) + +The core design: keep vCenter's control-plane traffic and vJailbreak's disk-copy traffic on **physically distinct paths** rather than sharing one network. + +### ESXi Side + +The ESXi host exposes two interfaces: + +| Interface | Role | +|-----------|------| +| **iface1** | Management — vCenter API calls, general connectivity | +| **iface2** | NFC data copy — tagged with `nfc_flag`, bound to VMware's NFC (Network File Copy) service. ESXi streams disk data through this interface during migrations. | + +This split is enforced at the ESXi interface level. Management and data movement never contend for the same physical path. + +### vJailbreak VM Side + +The vJailbreak VM mirrors the same split with two bridges and bonds: + +| Path | Bridge / Bond | Segment | Traffic | +|------|--------------|---------|---------| +| Management | Bridge-1 / Bond0 | seg1 (default) | vCenter API, management, internet | +| Storage | Bridge-2 / Bond1 | seg2 | Disk data copy from ESXi NFC service | + +## Critical: Routing Table Configuration + +This is the most operationally important detail. + +When vJailbreak issues a disk-copy request, ESXi responds using **iface2's IP** — because iface2 is the interface tagged for NFC traffic. If the vJailbreak VM's outbound route for that request is not explicitly pinned to the storage interface (Bridge-2 / Bond1), the following problems occur: + +- **Asymmetric routing** — request exits via the management interface, response arrives via the storage interface; the connection is dropped. +- **Silent fallback** — data copy traffic falls back onto the management network, starving it of bandwidth. +- **Transfer failures** — the NBD copy fails or times out due to IP mismatch on ESXi's NFC response path. + +### Required Routing Rule + +On the vJailbreak VM, add an explicit route so that any traffic destined for ESXi's NFC/data-copy service (iface2's IP subnet) is routed out through the storage interface: + +```bash +# Example: route ESXi storage subnet via the storage interface +ip route add /24 via dev +``` + +Replace: + +- `` — the subnet of ESXi's iface2 (NFC-tagged interface) +- `` — the gateway on the storage network segment +- `` — the interface name for Bridge-2/Bond1 on the vJailbreak VM + +To make this persistent across reboots, add the route to your network configuration (e.g., `/etc/netplan/*.yaml` or `/etc/network/interfaces` depending on the OS). + +### Verifying the Route + +After adding the route, confirm that traffic to the ESXi NFC IP exits via the correct interface: + +```bash +ip route get +``` + +Expected output should show the storage interface (Bond1/Bridge-2), not the management interface. + +## Summary + +| Concern | Solution | +|---------|----------| +| Management traffic contending with data copy | Separate physical interfaces on both ESXi and vJailbreak VM | +| ESXi NFC responses routed incorrectly | Explicit `ip route` rule on vJailbreak VM pinning NFC subnet to storage interface | +| Route lost on reboot | Persist route in OS network configuration | + +Without this routing configuration, bulk disk-copy traffic will bleed onto the management network regardless of the physical interface separation, because ESXi's NFC response will always use iface2's IP and the OS will drop the asymmetric connection. diff --git a/docs/src/content/docs/guides/How-to/networking-101.md b/docs/src/content/docs/guides/How-to/networking-101.md new file mode 100644 index 0000000..20b19db --- /dev/null +++ b/docs/src/content/docs/guides/How-to/networking-101.md @@ -0,0 +1,356 @@ +--- +title: One-Stop Migration Networking +description: Find your migration networking situation, apply the right settings, and know exactly what the OpenStack port and the guest OS will look like afterward. +--- + +Network settings are the most common cause of failed or surprising migrations. This guide is organized by situation: find the scenario that matches yours, apply the settings, and check the expected outcome before you migrate. + +Every scenario reports two separate outcomes: + +- **The OpenStack port** — the address and MAC that OpenStack assigns to the network port. +- **Inside the guest** — what the migrated VM's own network configuration looks like on first boot. + +These two can differ. A preserved IP address on the port does not always mean the guest is statically configured to match it. + +:::note +For the underlying mechanism behind interface-name persistence and per-OS guest behavior, see [Network Persistence](../../../concepts/network-persistence/). +::: + +## Advanced options for migration + +| Setting | What it controls | +| --- | --- | +| **Preserve IP** | Carry the discovered source IP onto the OpenStack port. | +| **IP address box** | Leave as-is (discovered IP), type one IPv4 address, or leave empty. | +| **Preserve MAC** | Carry the source MAC, or let OpenStack generate a new one. | +| **Fallback to DHCP** | On = accept an OpenStack-assigned address rather than fail. Off = stop the migration on conflict or mismatch. | +| **Persist source network interfaces** | Restore the original interface names inside the guest. Only available when Preserve IP is on. | + +## Table of scenarios + +Find the situation that matches yours, read across the row for the settings to apply, then follow the link to the detailed block — expected outcome, port and guest behavior, and the caveats. + +| Scenario | Preserve IP | IP address box | Preserve MAC | Fallback to DHCP | Persist source network interfaces | Details | +| --- | --- | --- | --- | --- | --- | --- | +| **A** — Same subnet: keep the exact IP and MAC, fail if the address is taken | **On** | Leave as-is | **On** | Off | **On** | [Scenario A →](#a--keep-the-same-ip-and-the-same-mac) | +| **B** — Specific new IP | Off | Type one IPv4 address | Either | Off, or on if a DHCP address is acceptable | Unavailable | [Scenario B →](#b--assign-a-specific-new-ip-address) | +| **C** — Everything on DHCP | Off on every NIC | Empty | Either | **On** — required | Unavailable | [Scenario C →](#c--force-everything-onto-dhcp) | +| **D** — Different subnet | Off | Empty, or an address valid in the new subnet | Either | **On** — required | Unavailable | [Scenario D →](#d--the-destination-is-on-a-different-subnet) | +| **E** — Same subnet, bulk move: prefer the IP but accept DHCP rather than fail | **On** | Leave as-is | **On** | **On** | **On** | [Scenario E →](#e--prefer-the-same-ip-but-accept-dhcp-rather-than-fail) | +| **F** — L2-only network | No effect on the port | Ignored | Either — on to keep the MAC | Greyed out | Your choice | [Scenario F →](#f--the-destination-is-an-l2-only-network) | +| **G** — Same IP, new MAC | **On** | Leave as-is | Off | Your choice | Your choice | [Scenario G →](#g--keep-the-ip-but-let-openstack-pick-a-new-mac) | +| **H** — Source VM powered off | Forced off (greyed out) | Type an address, or empty for DHCP | Either — usually on | On if the address box is empty | Unavailable | [Scenario H →](#h--the-source-vm-is-powered-off) | +| **I** — Port with no address | Off | Empty | Either | Off | Unavailable | [Scenario I →](#i--create-the-port-with-no-address-at-all) | +| **J** — Preserve IP and MAC, Persist Network off | **On** | Leave as-is | **On** | Your choice | Off | [Scenario J →](#j--preserve-ip-and-mac-but-persist-network-is-off) | + +**Reading the table** + +- **Either** / **Your choice** — the setting does not change the outcome the scenario describes. +- **Unavailable** — the UI disables **Persist source network interfaces** whenever Preserve IP is off. +- **Greyed out** — the UI disables the setting for this scenario; you cannot change it. +- **Leave as-is** — do not edit the box; it already shows the discovered IP. + +:::caution +Scenarios **C** and **I** share every setting except **Fallback to DHCP**, and Scenarios **A**, **E**, and **J** share every setting except **Fallback to DHCP** and **Persist source network interfaces**. Those single differences change the result substantially, so confirm them against the detailed block before you migrate. +::: + +--- + +## A — Keep the same IP and the same MAC + +**Use this when** + +- The destination network carries the same subnet as the source. +- The VM is powered on and its IP was discovered correctly. +- DNS records, firewall rules, or MAC-locked licenses depend on the address surviving the move. + +**Settings** + +| Setting | Value | +| --- | --- | +| Preserve IP | On | +| IP address box | Leave as-is (shows the discovered IP) | +| Preserve MAC | On | +| Fallback to DHCP | Off — you want the migration to stop rather than silently change the address | +| Persist source network interfaces | On — keeps the original interface names too | + +**What you get** + +- **OpenStack port:** the same IP address, provided nothing else in the network already holds it. +- **Port MAC:** the same MAC address. +- **Inside the guest:** unchanged. A static NIC stays static on the same address; a DHCP NIC stays on DHCP. The original interface names are restored. + +:::caution +If the address is already in use, the migration fails with a port conflict. That is deliberate — it stops you from creating a duplicate. Consider ticking **Disconnect source network** so the original VM releases the address first. + +**Persist source network interfaces** must be on for the "unchanged" guarantee above. If it is off, the guest outcome depends on the OS — see [Scenario J](#j--preserve-ip-and-mac-but-persist-network-is-off). +::: + +## B — Assign a specific new IP address + +**Use this when** + +- You are re-addressing the VM as part of the move and already know the address it should get. + +**Settings** + +| Setting | Value | +| --- | --- | +| Preserve IP | Off | +| IP address box | Type the new address — one IPv4 address only | +| Preserve MAC | Either — your choice | +| Fallback to DHCP | Off if a wrong address should stop the migration; on if a DHCP address is an acceptable substitute | +| Persist source network interfaces | Unavailable | + +**What you get** + +- **OpenStack port:** the address you typed, provided it belongs to a subnet on the destination network. + - If it does not belong to a subnet: the migration fails (Fallback off), or the port gets an OpenStack-assigned address (Fallback on). +- **Inside the guest:** DHCP configuration on the assigned address. + +:::caution +**One address per NIC.** Two addresses are rejected with "Multiple IPs are not supported when Preserve IP is disabled". + +A typed address carries no prefix length, so the guest assumes `/24`. Verify the netmask if your subnet is not a `/24`. +::: + +## C — Force everything onto DHCP + +**Use this when** + +- You want a clean start: OpenStack allocates all addresses and no source addressing is carried over. + +**Settings** + +| Setting | Value | +| --- | --- | +| Preserve IP | Off on every NIC | +| IP address box | Empty | +| Preserve MAC | Either — your choice | +| Fallback to DHCP | On — required, see below | +| Persist source network interfaces | Unavailable | + +**What you get** + +- **OpenStack port:** an address allocated by OpenStack. +- **Port MAC:** preserved or newly generated, per your choice. +- **Inside the guest:** DHCP. + +:::caution +**Fallback to DHCP must be on.** With it off, this same combination produces a port with no address at all — see [Scenario I](#i--create-the-port-with-no-address-at-all). +::: + +## D — The destination is on a different subnet + +**Use this when** + +- The destination OpenStack network does not carry the source VM's subnet. + +This is the most common cause of failed migrations. + +**Settings** + +| Setting | Value | +| --- | --- | +| Preserve IP | Off | +| IP address box | Empty (or type an address valid in the new subnet — see [Scenario B](#b--assign-a-specific-new-ip-address)) | +| Preserve MAC | Either — your choice | +| Fallback to DHCP | On — required | +| Persist source network interfaces | Unavailable — the UI greys it out automatically | + +**What you get** + +- **OpenStack port:** an address allocated by OpenStack from the destination subnet. +- **Port MAC:** the same MAC if Preserve MAC is on, otherwise a newly generated one. +- **Inside the guest:** DHCP. + +:::caution +**Preserve IP on** with **Fallback to DHCP off** is the failure case — the port cannot be created and the migration stops, but only if the source NIC actually had an address. A NIC with no discovered IP is fine. + +**Preserve IP on** with **Fallback to DHCP on** is tolerable: you still land on a DHCP address and keep the MAC without editing each NIC. Turning Preserve IP off is clearer. +::: + +## E — Prefer the same IP, but accept DHCP rather than fail + +**Use this when** + +- Same subnet as Scenario A, but you are migrating in bulk and would rather a few VMs come up on a different address than have the whole batch stop. + +**Settings** + +| Setting | Value | +| --- | --- | +| Preserve IP | On | +| IP address box | Leave as-is | +| Preserve MAC | On | +| Fallback to DHCP | On | +| Persist source network interfaces | On | + +**What you get** + +- **OpenStack port:** the same IP where possible; an OpenStack-assigned address where not. +- **Port MAC:** the same MAC address. +- **Inside the guest:** the original static configuration where the address was preserved; DHCP where it was not. + +:::caution +You will not be stopped when an address changes. Check the migration report afterward to see which VMs changed address. + +As with Scenario A, the guest outcome above assumes **Persist source network interfaces** is on. If it is off, see [Scenario J](#j--preserve-ip-and-mac-but-persist-network-is-off). +::: + +## F — The destination is an L2-only network + +**Use this when** + +- The destination OpenStack network is tagged as an L2 network and has no subnets. + +**Settings** + +| Setting | Value | +| --- | --- | +| Preserve IP | No effect on the port — no fixed IPs can be assigned | +| IP address box | Ignored | +| Preserve MAC | Works normally — keep it on if you need the MAC | +| Fallback to DHCP | Greyed out | +| Persist source network interfaces | Your choice | + +**What you get** + +- **OpenStack port:** created with no fixed IP addresses. +- **Port MAC:** preserved or newly generated, per your choice. +- **Inside the guest (Ubuntu with netplan):** a wildcard configuration puts every interface on DHCP. + +:::caution +Nothing in vJailbreak assigns addresses on an L2 network. Make sure a DHCP server is reachable on that segment, or plan to configure the guest by hand. +::: + +## G — Keep the IP but let OpenStack pick a new MAC + +**Use this when** + +- The source MAC clashes with something in the destination, or you are deliberately re-issuing hardware addresses. + +**Settings** + +| Setting | Value | +| --- | --- | +| Preserve IP | On | +| IP address box | Leave as-is | +| Preserve MAC | Off | +| Fallback to DHCP | Your choice — as in Scenarios A and E | +| Persist source network interfaces | Your choice | + +**What you get** + +- **OpenStack port:** the same IP address. +- **Port MAC:** a newly generated address. The UI shows a warning triangle next to the NIC to confirm this. +- **Inside the guest:** DHCP. The guest cannot match its old static settings to a hardware address it has never seen. + +:::caution +Anything license-locked to the MAC address will break. + +Interface names inside the guest may change, because names are recovered by matching on the MAC. +::: + +## H — The source VM is powered off + +**Use this when** + +- The VM is not running, so VMware Tools reported no addresses. + +**Settings** + +| Setting | Value | +| --- | --- | +| Preserve IP | Forced off and greyed out — nothing to preserve | +| IP address box | Type the address you want, or leave empty for DHCP | +| Preserve MAC | Available, and usually worth keeping on | +| Fallback to DHCP | On if you left the address box empty | +| Persist source network interfaces | Unavailable | + +**What you get** + +- **OpenStack port:** your typed address, an OpenStack-assigned one, or no address if the box was empty with Fallback off. +- **Port MAC:** preserved. The MAC is read from the virtual NIC rather than the guest, so it survives a powered-off migration. +- **Inside the guest:** no IP configuration is injected if no address was requested. + +:::tip +If you know the VM's address, power it on briefly before migrating so vJailbreak can discover it — that unlocks [Scenario A](#a--keep-the-same-ip-and-the-same-mac). +::: + +## I — Create the port with no address at all + +**Use this when** + +- You intend to configure addressing yourself after the migration, and want the port attached but unaddressed. + +Rarely used. If you reached this by accident, you probably wanted [Scenario C](#c--force-everything-onto-dhcp). + +**Settings** + +| Setting | Value | +| --- | --- | +| Preserve IP | Off | +| IP address box | Empty | +| Preserve MAC | Either — your choice | +| Fallback to DHCP | Off | +| Persist source network interfaces | Unavailable | + +**What you get** + +- **OpenStack port:** created and attached, with no fixed IP. +- **Port MAC:** preserved or newly generated, per your choice. +- **Inside the guest:** that interface is skipped entirely. No address, no DHCP client, nothing. + +:::caution +The VM will boot with an unconfigured NIC and no network reachability on it. Have console access ready. +::: + +## J — Preserve IP and MAC, but Persist Network is off + +**Use this when** + +- You want the address and MAC carried over, but do not need the original interface names restored. +- Or **Persist source network interfaces** was simply left at its default (off) and you want to know what to expect. + +This is the case Scenarios A and E refer to. The port outcome is identical to them — only the guest differs. + +**Settings** + +| Setting | Value | +| --- | --- | +| Preserve IP | On | +| IP address box | Leave as-is | +| Preserve MAC | On | +| Fallback to DHCP | Your choice — as in Scenarios A and E | +| Persist source network interfaces | Off | + +**What you get** + +- **OpenStack port:** exactly as in Scenario A or E — the preserved IP, or a DHCP address if Fallback rescued it. +- **Port MAC:** the same MAC address. +- **Inside the guest:** this is where it differs, and it depends on the guest OS rather than on the source configuration. + +**Guest outcome by OS** + +| Guest OS | Source NIC was on DHCP | Source NIC was static | +| --- | --- | --- | +| Ubuntu 17.10 and newer | Converted to a static address — pinned to whatever address it happened to hold when discovered | Stays static on the same address | +| Ubuntu older than 17.10 | Stays on DHCP. Only interface-name rules are written | Stays static, per the guest's own configuration | +| RHEL family 6 and older | Legacy interface handling runs — verify after boot | Legacy interface handling runs — verify after boot | +| RHEL / CentOS / Rocky 7+, SUSE, other Linux | Nothing is written. The guest keeps its own configuration | Nothing is written. The guest keeps its own configuration | +| Windows | Nothing is written. The guest keeps its own configuration | Nothing is written. The guest keeps its own configuration | + +On Ubuntu 17.10 and newer, the existing `/etc/netplan` directory is moved aside to `/etc/netplan-bkp` and replaced with a single generated file. Interfaces are renamed `vj0`, `vj1`, and so on, matched by MAC address. The netmask comes from what VMware Tools reported; if it is unknown, `/24` is assumed. + +:::caution +On **Ubuntu 17.10 and newer** this rewrites the guest's networking wholesale. A NIC that was on DHCP comes back statically pinned to the address it held at discovery time. If you want it to stay on DHCP, use [Scenario C](#c--force-everything-onto-dhcp). + +On **RHEL 7+, SUSE, other Linux, and Windows**, nothing is written at all. If the conversion renamed the interface, the guest's own configuration will point at a device that no longer exists. Have console access ready. + +On a **multi-homed VM**, only one interface gets a default route, and which one is not predictable. Verify routing after migration. +::: + +:::note +Whether the source NIC was on DHCP or static is recorded but never acted on directly by vJailbreak. It matters only because it determines what the guest's own untouched configuration says. +::: diff --git a/docs/src/content/docs/guides/How-to/ntp_timezone.md b/docs/src/content/docs/guides/How-to/ntp_timezone.md new file mode 100644 index 0000000..7baa837 --- /dev/null +++ b/docs/src/content/docs/guides/How-to/ntp_timezone.md @@ -0,0 +1,134 @@ +--- +title: Configure Time Zone and NTP Servers +description: Set the vJailbreak appliance time zone and NTP servers from Global Settings, and verify they applied to the host and to system pods +--- + +The vJailbreak appliance runs in UTC with default public NTP pools unless you change it. Two settings in the UI change that: + +- **Time zone** — the appliance's system time zone. It sets the timestamps in migration logs, controller logs, Grafana dashboards, and the schedule of the version-checker cron job. +- **NTP servers** — the time sources the appliance synchronizes against. Needed in air-gapped or restricted networks where the default public pools are unreachable. + +Keeping the appliance's clock accurate matters beyond readable logs: clock drift makes Changed Block Tracking timestamps unreliable during hot migrations. + +Both settings are applied to the appliance host itself, not to migrated VMs. + +## Configure the settings + +1. Open **Global Settings**. +2. On the **General** tab, pick a **Timezone**. The dropdown is searchable and lists common IANA zones with their current UTC offset. +3. On the **Advanced** tab, enter **NTP Servers** — hostnames or IPv4 addresses, separated by commas or new lines. For example: `ntp1.corp.local, ntp2.corp.local`. +4. Click **Save**. + +Saving writes both values to the `vjailbreak-settings` ConfigMap and then applies them to the host. You will see *"Applying time settings..."* followed by *"Time settings applied successfully."* + +### What each combination does + +| Time zone | NTP servers | Result | +|---|---|---| +| Set | Set | Host uses that zone, synchronizing against your servers | +| Set | Empty | Host uses that zone, synchronizing against the default public pools | +| Empty | Set | Host stays on UTC, synchronizing against your servers | +| Empty | Empty | Host reverts to UTC and time synchronization is turned off | + +Clearing both fields is therefore a full reset to the appliance defaults. + +### Validation + +The form rejects a save when an entry is malformed: + +- A time zone must be one of the listed zones. If the appliance already holds a zone that is not in the list, it appears as `(Legacy) ` so it is not silently discarded. +- Each NTP entry must be a hostname or an IPv4 address. URLs, entries containing `/`, and malformed hostnames are rejected with *"Invalid NTP server "…". Use hostnames or IPv4 addresses, separated by commas or new lines."* + +## Both fields lock while migrations run + +If any migration is in a non-terminal phase — anything other than Succeeded, Failed, Validation Failed, or Unknown — the **Timezone** and **NTP Servers** fields are disabled, with the tooltip *"Cannot change timezone while migrations are in progress."* + +Applying time settings restarts the controller, SDK, and UI pods, which would disrupt a running migration. The migration list is polled every 30 seconds, so the fields unlock shortly after the last migration reaches a terminal phase. + +**Reset to Defaults** respects the same rule: while a migration is running it resets every other setting but leaves the time zone and NTP servers at their current values. With no migration running, it resets them along with everything else. + +## Verify the settings + +On the appliance host: + +```bash +# Time zone, and whether the clock is synchronized +timedatectl +timedatectl show --property=NTPSynchronized + +# The NTP servers vJailbreak wrote +cat /etc/systemd/timesyncd.conf.d/99-vjailbreak.conf +``` + +The conf file looks like this: + +```ini +[Time] +NTP=ntp1.corp.local ntp2.corp.local +``` + +From Kubernetes: + +```bash +# What was saved +kubectl -n migration-system get configmap vjailbreak-settings \ + -o jsonpath='{.data.TIMEZONE}{"\n"}{.data.NTP_SERVERS}{"\n"}' + +# What pods will inherit +kubectl -n migration-system get configmap pf9-env -o jsonpath='{.data.TZ}{"\n"}' + +# What a running pod actually has +kubectl -n migration-system exec deploy/migration-controller-manager -- printenv TZ +``` + +A rolling restart takes a couple of minutes to finish, so give the pods time before concluding that `TZ` did not propagate. + +## Configure from the command line + +The two values live in the `vjailbreak-settings` ConfigMap: + +| Key | Format | Empty means | +|---|---|---| +| `TIMEZONE` | IANA zone, for example `Asia/Calcutta` | Use UTC | +| `NTP_SERVERS` | Hostnames or IPv4 addresses, space separated | Use the default public pools | + +```bash +kubectl -n migration-system patch configmap vjailbreak-settings --type merge \ + -p '{"data":{"TIMEZONE":"Asia/Calcutta","NTP_SERVERS":"ntp1.corp.local ntp2.corp.local"}}' +``` + +:::caution +Editing the ConfigMap only records the values. Nothing reaches the host until the apply step runs, which the UI triggers on **Save**. After editing the ConfigMap directly, open Global Settings and save, or call the apply endpoint yourself: + +```bash +curl -X POST http:///dev-api/sdk/vpw/v1/settings/apply-time-settings \ + -H 'Content-Type: application/json' -d '{}' +``` +::: + +Values written by hand skip the form's validation. Invalid NTP entries are dropped when the settings are applied, and only the valid ones reach the conf file. + +## Troubleshooting + +| Symptom | Cause | What to do | +|---|---|---| +| Timezone and NTP Servers fields are greyed out | A migration is in a non-terminal phase | Wait for it to finish, or cancel it. The fields unlock within about 30 seconds. | +| **Reset to Defaults** left the time zone unchanged | Expected while a migration is running | Reset again once no migration is active | +| Save reports *"Failed to apply time settings on the host … Settings were saved; click Save again to retry the apply."* | The values were stored, but the host apply failed | Click **Save** again. If it keeps failing, check the API response. | +| UI reports success, but `timedatectl` still shows the old zone | The host notification is best-effort and its failure is not surfaced in the UI | Check the logs for `timesettings:` warnings. A zone that is syntactically valid but not installed on the host fails at this step. | +| An NTP server you entered is missing from the conf file | It failed validation and was dropped | Check the logs for `ignoring invalid NTP server entries`. Re-enter it as a plain hostname or IPv4 address. | +| Pods still show the old `TZ` | The rolling restart has not finished | Wait, then re-check with `kubectl rollout status` | +| The conf file is gone after a save | Expected when the NTP Servers field is empty | Re-enter your servers, or leave it empty to use the default pools | + +Logs for the apply step: + +```bash +kubectl -n migration-system logs deploy/migration-vpwned-sdk | grep timesettings +``` + +## Notes and limitations + +- These settings change the **appliance**, not the VMs being migrated. Migrated guests keep their own time configuration. +- Failures after the conf file is written — host notification, service restart, pod restarts, cron job patch — never fail the request and are not shown in the UI. The success message means the settings were saved and applied as far as possible. +- A time zone that is not installed on the appliance is stored and reported as applied, but the host time zone does not change. Pick a zone from the dropdown to avoid this. +- Invalid NTP entries are silently dropped at apply time. The UI blocks them first, so this affects only values written directly to the ConfigMap. diff --git a/docs/src/content/docs/guides/How-to/perform_admin_cutover.md b/docs/src/content/docs/guides/How-to/perform_admin_cutover.md new file mode 100644 index 0000000..bcedadc --- /dev/null +++ b/docs/src/content/docs/guides/How-to/perform_admin_cutover.md @@ -0,0 +1,48 @@ +--- +title: "Perform Admin Cutover" +description: "Guide to perform admin cutover over UI and CLI" +--- + +The **Admin Cutover** feature in vJailbreak is used to finalize a migration after the data transfer is complete. +It can be triggered in two ways: +1. From the **vJailbreak UI** +2. Using **kubectl patch** on the corresponding `Pod` of the migration. + + +## Admin Cutover from the UI +1. Navigate to the migration you want to perform the admin cutover for, in the migration Options column, +Select the cutover options and select Admin Initiated Cutover as shown in the Image below. +![Admin Cutover](../../../../../public/images/admin_cutover_form.png) + +Once the migration is done with copying data, the status of the migration will change to `waitforAdminCutover`. +You can then click on the **Admin Cutover** button to trigger the cutover process. +![Admin Cutover Button](../../../../../public/images/admin_cutover_button.png) + +2. A confirmation dialog will appear. Click on the **Confirm** button to proceed with the admin cutover. +![Admin Cutover Confirmation](../../../../../public/images/admin_cutover_confirmation.png) + +3. After confirming, the migration will start the cutover process. +![Admin Cutover In Progress](../../../../../public/images/admin_cutover_run.png) + + +## Admin Cutover using kubectl patch +You can also trigger the admin cutover using the `kubectl patch` command. +1. First, identify the name of the migration `Pod` you want to perform the admin +cutover for. You can do that by doing the following things: + * Get the name of the migration namespace. You can find it in the vJailbreak UI under the migration details. + ```bash + kubectl get migration -n migration-system | grep -i + migration-name AwaitingAdminCutOve vjb 1h + ``` + * Get the podRef of migration object + ```bash + kubectl get migration -n migration-system -o jsonpath='{.spec.podRef}' + + ``` + +2. Once you have identified the migration `Pod`, you can trigger the admin cutover by +executing the following command: +```bash +kubectl patch pod -n migration-system -p '{"metadata":{"labels":{"startCutover":"yes"}}}' +``` +Replace `pod-name` with the name of your migration `Pod` and `migration-system` with the name of your migration namespace. \ No newline at end of file diff --git a/docs/src/content/docs/guides/How-to/profiles.md b/docs/src/content/docs/guides/How-to/profiles.md new file mode 100644 index 0000000..4c87716 --- /dev/null +++ b/docs/src/content/docs/guides/How-to/profiles.md @@ -0,0 +1,155 @@ +--- +title: Profiles +description: Use profiles to apply OpenStack volume image metadata properties to migrated VM boot volumes before migration. +--- + +Profiles let us define a named set of OpenStack Cinder `volume_image_metadata` properties and apply them to VM boot volumes during migration. This gives us control over how the migrated VM boots and runs in OpenStack — for example, setting the firmware type, disk bus driver, video model, or guest agent settings — without having to configure each migration individually. + +Navigate to the **Profiles** section in the sidebar to view and perform all CRUD operations on profiles. + +## Default Profiles + +vJailbreak ships with two built-in profiles that are created by default as a template: + +### default-linux + +Applies to Linux VMs (`linuxGuest`). + +| Property | Value | +|---|---| +| `hw_qemu_guest_agent` | `yes` | +| `hw_video_model` | `virtio` | +| `hw_pointer_model` | `usbtablet` | + +### default-windows + +Applies to Windows VMs (`windowsGuest`). + +| Property | Value | +|---|---| +| `hw_qemu_guest_agent` | `yes` | +| `hw_video_model` | `virtio` | +| `hw_pointer_model` | `usbtablet` | +| `hw_disk_bus` | `virtio` | +| `os_type` | `windows` | + +![img1](../../../../../public/images/profiles_page.png) + +:::note +Default profiles are marked with a star icon in the Profiles list. We can edit them to change their properties, but their names cannot be changed. +::: + +## Creating a Profile + +1. On the Profiles page, click **Add Profile**. +2. Fill in the following fields: + + - **Profile Name** — A unique name using lowercase letters, numbers, and hyphens (e.g., `windows-uefi-q35`). The name cannot be changed after creation. + - **OS Family** — The type of VM this profile applies to: + - `Windows` — Only applied to Windows VMs + - `Linux` — Only applied to Linux VMs + - `Any (applies to all VMs)` — Applied to every VM regardless of OS + - **Description** — Optional. A short note shown in the profile list. + - **Image Properties** — One or more key-value pairs of OpenStack volume image metadata. Type directly into the key field or select from the list of known property keys. + +3. Click **Create Profile**. + +### Known Image Property Keys + +The property key field in the profile form offers autocomplete suggestions for common OpenStack volume image metadata keys. The hints shown alongside each key are example values. + +| Key | Example Values | +|---|---| +| `hw_firmware_type` | `uefi`, `bios` | +| `hw_machine_type` | `q35`, `pc-i440fx` | +| `hw_disk_bus` | `virtio`, `scsi`, `ide` | +| `hw_scsi_model` | `virtio-scsi`, `buslogic` | +| `hw_tpm_model` | `tpm-crb`, `tpm-tis` | +| `hw_tpm_version` | `1.2`, `2.0` | +| `os_secure_boot` | `required`, `disabled`, `optional` | +| `os_require_quiesce` | `yes`, `no` | +| `os_type` | `windows`, `linux` | +| `hw_qemu_guest_agent` | `yes`, `no` | +| `hw_video_model` | `virtio`, `qxl`, `vga` | +| `hw_cdrom_bus` | `sata`, `ide`, `virtio` | +| `hw_boot_menu` | `true`, `false` | +| `hw_pointer_model` | `usbtablet`, `ps2mouse` | + +![img1](../../../../../public/images/create_profile.png) + +:::note +We are not limited to these keys. Any valid OpenStack Cinder `volume_image_metadata` key can be used. +::: + +## Selecting Profiles During Migration + +In the migration form, **Step 4 (Security Groups, Server Group & Profiles)** includes a **Profiles** dropdown. We can select one or more profiles to apply during the migration. + +The dropdown shows only profiles that match the OS family of the VMs we have selected: +- If our migration includes Windows VMs, profiles with OS family `Windows` or `Any` are shown. +- If our migration includes Linux VMs, profiles with OS family `Linux` or `Any` are shown. + +Step 4 is optional. If we do not select any profiles, no additional image metadata is applied to the boot volumes. + +![img1](../../../../../public/images/select_profile.png) + +## How Multiple Profiles Are Merged + +When we select more than one profile, their properties are combined into a single set before being applied. The merge follows these rules: + +- All unique keys from all selected profiles are included. +- If two profiles set the **same key to the same value**, there is no conflict and the value is applied once. +- If two profiles set the **same key to different values**, the UI will show a conflict error and prevent adding the second profile until the conflict is resolved. + +This ensures that the final set of properties applied to the boot volume is predictable and unambiguous. + +:::note +Properties are applied **in addition to** any metadata that vJailbreak sets automatically (for example, `hw_firmware_type: uefi` for UEFI VMs). If a profile specifies a key that conflicts with automatically set metadata, the profile value will override it. +::: + +## Using Profiles via CLI + +A `VolumeImageProfile` is a standard Kubernetes custom resource in the `migration-system` namespace. We can create and manage profiles using `kubectl`. + +**View all profiles:** + +```bash +kubectl get volumeimageprofiles -n migration-system +``` + +**View a profile's details:** + +```bash +kubectl get volumeimageprofile default-windows -n migration-system -o yaml +``` + +**Create a custom profile:** + +```yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: VolumeImageProfile +metadata: + name: windows-uefi-q35 + namespace: migration-system +spec: + osFamily: windowsGuest + description: Windows VMs with UEFI firmware and Q35 machine type + properties: + hw_firmware_type: uefi + hw_machine_type: q35 + os_secure_boot: disabled +``` + +```bash +kubectl apply -f windows-uefi-q35.yaml +``` + +To reference a profile in a `MigrationPlan`, add its name to `spec.advancedOptions.imageProfiles`: + +```yaml +spec: + advancedOptions: + imageProfiles: + - default-windows + - windows-uefi-q35 +``` diff --git a/docs/src/content/docs/guides/How-to/retry_failed_migration.md b/docs/src/content/docs/guides/How-to/retry_failed_migration.md new file mode 100644 index 0000000..de5912b --- /dev/null +++ b/docs/src/content/docs/guides/How-to/retry_failed_migration.md @@ -0,0 +1,76 @@ +--- +title: Retry a Failed Migration +description: Retry a failed migration from the vJailbreak UI, edit its configuration before retrying, or retry several failed migrations at once +--- + +When a migration ends in the **Failed** phase, you can retry it directly from the vJailbreak UI instead of building the migration again from scratch. A retry reopens the original configuration in the migration form, so you can correct whatever caused the failure — flavor, network or storage mapping, target cluster, cutover settings, or IP assignments — and start again. + +## What Retry does + +vJailbreak offers two retry actions: + +| Action | Where to find it | What it does | +|---|---|---| +| **Retry** | Row action in the **Migrations** table, or the **Retry** button on a migration's detail page | Opens the migration form pre-filled with the failed migration's configuration. You can change settings before submitting. | +| **Retry Selected** | Toolbar of the **Migrations** table, after selecting rows | Retries every selected failed migration with its existing configuration. No editing. | + +Use **Retry** when the migration failed because of a configuration problem. Use **Retry Selected** when several migrations failed for the same temporary reason — for example, an ESXi host or a target service was briefly unreachable — and nothing needs to change. + +## Prerequisites + +- The migration is in the **Failed** phase. The Retry button appears in no other phase. +- The migration is retryable. Migrations for VMs with **RDM (Raw Device Mapping) disks** cannot be retried from the UI, because the shared RDM state prevents an automatic retry. For these VMs the Retry button is visible but disabled. +- The resources the original migration used still exist: its migration plan, migration template, VMware and OpenStack credentials, and the source VM in the inventory. If any of them is missing, the retry form opens with a banner naming the missing item, and Retry stays disabled. + +## Retry a single migration + +1. Go to **Migrations** and locate the failed migration. +2. Click the **Retry** action in the row, or open the migration's detail page and click **Retry**. +3. The migration form opens in retry mode, titled **Retry Migration**, and loads the original configuration. +4. Review the settings and change what you need. See [What you can edit](#what-you-can-edit). +5. Click **Retry**. + +vJailbreak returns you to the migrations list and starts a new migration for that VM using the configuration you submitted. + +:::note +Clicking **Cancel** closes the form without changing anything. A cancelled retry never modifies the original migration, plan, or template. +::: + + +### What you can edit + +**Locked during a retry** + +- The VM being retried. A retry always applies to exactly one VM, and the VM list shows only that VM. +- Source and destination credentials, and the source cluster. + +**Editable** + +- **Target PCD cluster.** Changing it **clears the network and storage mappings**, because mappings are specific to a cluster. Select new mappings before submitting. +- Everything else — mappings, flavor, storage copy method, migration options, advanced options, and IP or MAC overrides. + +## Retry several migrations at once + +1. Go to **Migrations** and select the failed rows using the checkboxes. +2. The **Retry Selected (N)** button appears in the toolbar. It appears **only when every selected migration** is failed and retryable. If the selection includes a migration that is not failed, or a VM with RDM disks, the button is hidden. +3. Click **Retry Selected**. A confirmation dialog appears, stating that the migrations will be retried without changing their configurations and that source VMs will not be modified. +4. Click **Retry** to confirm. + +Each selected migration restarts with its existing configuration. Plans, templates, and mappings are left untouched. + + +## Troubleshooting + +| Symptom | Cause | What to do | +|---|---|---| +| Retry is disabled and the tooltip mentions RDM disks | The VM has RDM disks and cannot be retried from the UI | Restart the migration manually. See [Migrating an RDM disk Windows cluster machine using the CLI](../../cli-api/migrating_rdm_disk_windows_cluster_machine_using_cli/). | +| Banner: *"Migration plan … no longer exists"* | The plan was deleted after the migration failed | Create a new migration for that VM | +| Banner naming a VMware or OpenStack credential | The credential was deleted | Recreate the credential, then retry | +| Banner: *"Source VM … is no longer present in the inventory"* | The VM is missing from the VMware inventory, usually after a re-sync or after the VM was removed | Refresh the inventory or add the VM back, then retry | +| **Retry failed** banner after submitting | A step of the retry did not complete | The banner shows the underlying error. If the original plan could not be updated, nothing new was created and the original plan is intact — correct the problem and retry again. | +| Network and storage mappings are empty | Expected after changing the target cluster | Select mappings for the new cluster | + +## Limitations + +- A retry always creates a plan containing a single VM. You cannot retry several VMs into one shared plan with edits. +- **Retry Selected** cannot change configuration. If a migration fails again after a bulk retry, retry it individually and correct its settings. diff --git a/docs/src/content/docs/guides/How-to/scaling.md b/docs/src/content/docs/guides/How-to/scaling.md new file mode 100644 index 0000000..b7df281 --- /dev/null +++ b/docs/src/content/docs/guides/How-to/scaling.md @@ -0,0 +1,94 @@ +--- +title: Scale vJailbreak +description: You can scale up vJailbreak to perform more parallel migrations +--- + +vJailbreak can be scaled to perform multiple migrations in parallel by deploying additional `agents`, enabling greater efficiency and workload distribution. + +Additional agents can be created in the Agents tab of the vJailbreak dashboard using the "Scale Up" button. You will need to choose the destination OpenStack credentials, the size of the agent VM(s), and the number of agent nodes up to a maximum of 5 per scale up. Additional agent nodes can be scaled up in batches of 5, providing the flexibility to change agent VM sizes to help with throttling network traffic. + +:::caution +It is entirely possible to fully saturate a 10Gb network with many parallel migrations! +::: + +## Agent Node Sizing and Migration Capacity + +:::caution +The sizing recommendations below apply to **agent nodes only**. The primary vJailbreak VM hosts additional services (controller, UI, Prometheus, Grafana, etc.) in addition to running migrations, and therefore requires separate capacity planning with additional overhead. Agent nodes are dedicated worker nodes that primarily run migration pods. +::: + +Each migration running on an agent node consumes the following resources: + +| Resource | Request | Limit | +|----------|---------|-------| +| CPU | 1 core | 2 cores | +| Memory | 1 GiB | 3 GiB | +| Ephemeral Storage | 3 GiB | 3 GiB | + +### Calculating Concurrent Migrations per Agent + +The number of concurrent migrations an agent node can handle depends on its available resources. While Kubernetes uses **resource requests** for scheduling decisions, the actual resource consumption during migration is closer to the **limits**. Therefore, consider both when planning capacity: + +**Scheduling Capacity (based on requests):** +- Maximum Concurrent Migrations = min(Available CPU / 1 core, Available Memory / 1 GiB, Available Storage / 3 GiB) + +**Actual Runtime Capacity (based on limits):** +- Maximum Concurrent Migrations = min(Available CPU / 2 cores, Available Memory / 3 GiB, Available Storage / 3 GiB) + +For safe capacity planning, use the **limits-based calculation** to ensure migrations have sufficient resources during peak usage. + +### Recommended Agent Flavors + +Below are recommended OpenStack flavors for agent nodes based on desired migration capacity. Reserve approximately **20-25% of resources** for system overhead (OS, K3s, monitoring, etc.): + +| Agent Flavor | vCPUs | RAM | Storage | Concurrent Migrations (per agent) | Use Case | +|--------------|-------|-----|---------|-----------------------------------|----------| +| **Small** | 8 | 16 GiB | 60 GiB | 2-3 | Small-scale migrations, testing | +| **Medium** | 16 | 32 GiB | 100 GiB | 5-7 | Standard production workloads | +| **Large** | 32 | 64 GiB | 200 GiB | 10-14 | High-throughput migrations | +| **X-Large** | 48 | 96 GiB | 300 GiB | 15-21 | Maximum parallel migrations | + +:::note +Agent nodes require a **minimum of 60 GiB disk storage**. Flavors with less than 60 GiB are not supported. +::: + +**Example Calculation for Medium Flavor (16 vCPU, 32 GiB RAM):** +- Available CPU after overhead: ~12 cores → 12 / 2 = 6 migrations +- Available Memory after overhead: ~24 GiB → 24 / 3 = 8 migrations +- **Effective capacity: 6 concurrent migrations** (limited by CPU) + +### Best Practices + +- **Network bandwidth** is often the bottleneck. Monitor network utilization and adjust agent count/size accordingly. +- **Storage I/O** on the agent node should be sufficient for temporary disk operations during migration. +- Start with **Medium** flavors and scale up based on observed resource utilization and network capacity. +- Distribute migrations across multiple smaller agents rather than one large agent for better fault tolerance. +- Monitor agent resource usage via the vJailbreak dashboard or Prometheus metrics to optimize sizing. + +Agent nodes can be scaled down by selecting the agent and using the "Scale Down" button. + +## Scaling in L2-Only Networks (PCD) + +Agent scale-up **is supported** in L2-only network environments in PCD. + +### How it works + +1. **vJailbreak recognises the L2-only network** and creates the agent VM on it without asking PCD to assign an IP address. +2. **The agent VM waits for its IP address.** On first boot it waits for the external DHCP server on that network to give it an IP address and a default route. It keeps retrying every minute for as long as it takes, so a slow lease is not a problem. +3. **The agent joins vJailbreak.** As soon as the guest has an IP address, the agent setup runs and the agent joins the primary vJailbreak VM. Everything it needs is already built into the image, so it does not require internet access. +4. **The agent becomes available.** While the agent is still coming up, the Agents tab shows no IP address for it. Once it reports `Ready`, its IP address appears and it starts picking up migrations. + +:::note +If an agent stays in `VMCreated` and never becomes `Ready`, the guest most likely never received a DHCP lease or a default route. Open the agent VM console and check `/var/log/pf9-install.log` — the wait loop logs which of the two conditions is still missing. +::: + +## Logging into Agent VMs + +Agent VMs use the same login process as the primary vJailbreak VM: +- **Username**: `ubuntu` +- **Default Password**: `password` +- On first login, you will be prompted to change the password immediately. + +:::note +VDDK libraries are automatically synced from the primary vJailbreak VM to all agent nodes. You only need to upload VDDK to the primary vJailbreak VM. +::: diff --git a/docs/src/content/docs/guides/How-to/stream_logs.md b/docs/src/content/docs/guides/How-to/stream_logs.md new file mode 100644 index 0000000..f4d49b5 --- /dev/null +++ b/docs/src/content/docs/guides/How-to/stream_logs.md @@ -0,0 +1,356 @@ +--- +title: Stream Logs +description: How to setup rsyslog to stream logs from vJailbreak VMs to fluentd and integrate with Loki +--- +In this guide, we will cover how to enable the streaming of syslogs from vJailbreak VMs to fluentd and integrate with Loki running on the PCD-CE. +For this tutorial you will need working knowledge of K3s, fluentd and Loki. + +### Prerequisites + +- A running vJailbreak instance with SSH enabled. +- A running fluentd instance on a remote host (in this case PCD-CE Management host). +- A running grafana/loki-stack instance for this we are using PCD-CE. + +### Setup vJailbreak VM for rsyslog + +1. Enable K3s to write to `syslog` +2. Install rsyslog on the vJailbreak VM - If not already present +3. Configure rsyslog to forward syslogs to fluentd. +4. Configure loki to read logs from fluentd log directory. + +### Enable syslog for k3s + +To the service section add the following: + +```shell +sudo vi /etc/systemd/system/k3s.service +``` + +```shell +[Service] +Type=notify +NotifyAccess=all +``` + +To the `ExecStart` section add the following: + +```shell +ExecStart=/usr/local/bin/k3s \ + server --log=/var/log/syslog \ + '--disable' \ + 'traefik' \ +``` + +```shell +sudo systemctl daemon-reload +sudo systemctl restart k3s +``` + +### Configure rsyslog + +1. Edit the rsyslog configuration file +2. Add the following configuration: + +```shell +sudo sh -c 'echo "*.* @:5140" >> /etc/rsyslog.d/90-fluentd.conf' +``` +!note: Change `` to the IP address of the fluentd host + +3. Restart rsyslog + +```shell +sudo systemctl restart rsyslog +``` + +4. Test rsyslog + +```shell +sudo journalctl -f +``` + +### Install fluentd + +1. Install fluentd on the PCD-CE Management host +2. Test fluentd + +```shell +$ ulimit -n +65536 +``` + +Please add the following lines to your `/etc/security/limits.conf` file: + +```shell +root soft nofile 65536 +root hard nofile 65536 +* soft nofile 65536 +* hard nofile 65536 +``` + +3. Setup sysctl conf + +Edit `/etc/sysctl.conf` and add the following + +```shell +net.core.somaxconn = 1024 +net.core.netdev_max_backlog = 5000 +net.core.rmem_max = 16777216 +net.core.wmem_max = 16777216 +net.ipv4.tcp_wmem = 4096 12582912 16777216 +net.ipv4.tcp_rmem = 4096 12582912 16777216 +net.ipv4.tcp_max_syn_backlog = 8096 +net.ipv4.tcp_slow_start_after_idle = 0 +net.ipv4.tcp_tw_reuse = 1 +net.ipv4.ip_local_port_range = 10240 65535 +fs.inotify.max_user_instances = 1024 +# If forward uses port 24224, reserve that port number for use as an ephemeral port. +# If another port, e.g., monitor_agent uses port 24220, add a comma-separated list of port numbers. +# net.ipv4.ip_local_reserved_ports = 24220,24224 +net.ipv4.ip_local_reserved_ports = 24224 +``` + +Then check if these are in effect + +```shell +$ sysctl -p +net.core.somaxconn = 1024 +net.core.netdev_max_backlog = 5000 +net.core.rmem_max = 16777216 +net.core.wmem_max = 16777216 +net.ipv4.tcp_wmem = 4096 12582912 16777216 +net.ipv4.tcp_rmem = 4096 12582912 16777216 +net.ipv4.tcp_max_syn_backlog = 8096 +net.ipv4.tcp_slow_start_after_idle = 0 +net.ipv4.tcp_tw_reuse = 1 +net.ipv4.ip_local_port_range = 10240 65535 +fs.inotify.max_user_instances = 1024 +net.ipv4.ip_local_reserved_ports = 24224 +``` + +4. Install fluentd + +```shell +sudo curl -fsSL https://toolbelt.treasuredata.com/sh/install-ubuntu-jammy-fluent-package5.sh | bash +``` + +5. Restart fluentd + +```shell +sudo systemctl restart fluentd +``` + +6. Test fluentd + +```shell +sudo systemctl status fluentd +``` + +### Configure fluentd + +1. Edit the fluentd configuration file `/etc/fluentd/fluentd.conf` +2. Add the following configuration: + +```shell + + @type syslog + port 5140 + bind 0.0.0.0 + tag system + + + + @type stdout + +``` + +Ref: https://docs.fluentd.org/how-to-guides/parse-syslog + +3. Restart fluentd + +```shell +sudo systemctl restart fluentd +``` + +Generally, the logs should now show up in `/var/log/fluent/fluentd.log`. + +### Verify + +1. Check that rsyslog is running + +```shell +sudo systemctl status rsyslog +``` + +2. Check that fluentd is running + +```shell +sudo systemctl status fluentd +``` + +3. Check that syslogs from vJailbreak is being sent to the fluentd + +```shell +vjb$ logger -p vjailbreak.notice "This is a test message from Rsyslog - Hello Openstack!" +``` + +4. Check that fluentd is receiving the logs + +```shell +pcd$ tail -f /var/log/fluent/fluentd.log +``` + +### Setup Loki on PCD-CE + +1. Login to the PCD-CE Management Host +2. Then export the kubeconfig + +```shell +export KUBECONFIG=/etc/rancher/k3s/k3s.yaml +``` + +3. Install Loki using helm + Use the following loki-config.yaml + +``` +loki: + image: + tag: 2.9.3 + enabled: true + +grafana: + enabled: false + +promtail: + enabled: true + + config: + server: + http_listen_port: 3101 + grpc_listen_port: 0 + positions: + filename: /tmp/positions.yaml + clients: + - url: http://loki:3100/loki/api/v1/push + snippets: + extraScrapeConfigs: |- + - job_name: fluentd + static_configs: + - targets: + - localhost + labels: + job: fluentd + __path__: /hostlogs/fluent/*.log + pipeline_stages: + - match: + selector: '{job="fluentd"}' + stages: + - regex: + expression: '.*' + - timestamp: + source: time + format: RFC3339 + - output: + source: message + + extraVolumes: + - name: host-logs + hostPath: + path: /var/log/fluent + type: Directory + - name: tmp + emptyDir: {} + + extraVolumeMounts: + - name: host-logs + mountPath: /hostlogs/fluent + readOnly: true + - name: tmp + mountPath: /tmp + + serviceAccount: + create: true + +rbac: + create: true + +persistence: + enabled: true + size: 10Gi + storageClassName: "" + accessModes: + - ReadWriteOnce +``` + +```shell +helm upgrade --namespace pcd-community --install loki grafana/loki-stack -f loki-config.yaml +``` + +4. Check if all the loki pods are `running` + +```shell +kubectl get pods -n pcd-community | grep loki +``` + +5. Add Loki as a data source in Grafana + +- Add it manually in the grafana UI + Configuration > "Add Datasource" > Loki > "url: http://loki:3100" > "Save & Test" +- Add it using a configmap + + Add the configmap + + ```shell + kubectl apply -f loki-datasource.yaml + ``` + + Restart the deployment + + ```shell + kubectl rollout restart deployment prometheus-stack-grafana + ``` + +Go to "Explore" > "Loki" to start exploring the logs. + +6. You can use the query below to browse the logs + +```shell +{job="fluentd"} |= `` +``` + +### Flow of Logs +```mermaid +architecture-beta + group vJailbreak(server)[vJailbreak VM] + service k3s(logos:kubernetes)[K3s] in vJailbreak + service syslog(disk)[Syslog] in vJailbreak + service rsyslogd(internet)[rsyslogd] in vJailbreak + + group PCD(server)[PCD] + service fluentd(disk)[fluentd] in PCD + service loki(database)[Loki] in PCD + service grafana(logos:grafana)[Grafana] in PCD + + k3s:L -- R:syslog + syslog:B -- T:rsyslogd + rsyslogd:R -- R:fluentd + fluentd:B -- T:loki + loki:L -- R:grafana + +``` + +### Version of tools used + +1. Fluentd + +```shell +fluentd --version +fluent-package 5.2.0 fluentd 1.18.0 (46372ddd521870f6a203baefb5a598209486d0bc) +``` + +2. Loki & grafana + +```shell +NAME CHART APP VERSION +grafana grafana-8.11.1 11.6.0 +loki loki-stack-2.10.2 v2.9.3 +``` diff --git a/docs/src/content/docs/guides/How-to/upgrade_vjailbreak.md b/docs/src/content/docs/guides/How-to/upgrade_vjailbreak.md new file mode 100644 index 0000000..6838f4a --- /dev/null +++ b/docs/src/content/docs/guides/How-to/upgrade_vjailbreak.md @@ -0,0 +1,74 @@ +--- +title: Upgrade vJailbreak +description: How to upgrade vJailbreak to a later version +--- + +vJailbreak supports an in-place upgrade feature to go from one version to other higher version. This feature is supported starting from v0.4.0 as the base to subsequent versions. + +During the upgrade, only container images, ConfigMaps, and Custom Resource Definitions (CRDs) are modified. We currently do not support the upgrade of the base vJailbreak image or existing Custom Resources (CRs). Therefore, a pre-upgrade cleanup of these resources is required. + +## Upgrade Process + +### 1. Check for Updates +Look for the **Upgrade Available** button at the bottom left of the vJailbreak navigation sidebar. Clicking this button will open the Upgrade vJailbreak modal. + +![Check for Updates](../../../../../public/images/upgrade_available.png) + +### 2. Pre-Upgrade Cleanup +Before upgrading, vJailbreak requires a cleanup of existing resources to ensure a smooth transition. The pre-upgrade checklist includes: +- Delete MigrationPlans +- Delete RollingMigrationPlans +- Scale down Agents +- Delete VMware credentials +- Delete PCD credentials +- Delete Custom Resources + +Click the **Cleanup** button to initiate this process. Wait for all items to show a green checkmark and the "Cleanup completed successfully" message to appear. + +![Pre-Upgrade Cleanup](../../../../../public/images/cleaning_up_resources.png) +![Cleanup completed successfully ](../../../../../public/images/cleaned_up_sucessfully.png) + +### 3. Select Version +Once the cleanup is successful, click the **Select a version...** dropdown and choose the target version you wish to upgrade to (e.g., `v0.4.1`). + +![Select Version](../../../../../public/images/upgrade_modal.png) + +### 4. Initiate Upgrade +With the version selected, the **Upgrade** button will become enabled. Click it to start the upgrade process. + +![Initiate Upgrade](../../../../../public/images/upgrade_in_progress.png) + +### 5. Wait for Completion +You will see an "Upgrading" spinner and a warning: **Processing. Please do not close or refresh this page.** Wait for the process to complete. + +![Wait for Completion](../../../../../public/images/upgrade_completed.png) + +Once the upgrade is marked as successfully completed, the UI will hold on the screen for 3 seconds before automatically refreshing. + +:::tip[Recommendation] +For safety, it is highly advised to perform a **hard refresh** of your browser before using the UI immediately after an upgrade. +::: + +:::caution[Important] +Do not close or refresh your browser window while the upgrade is in progress, as this may interrupt the operation. +::: + +## Check for update before scheduled interval + +If you do not want to wait for the next scheduled check, you can manually trigger the `vjailbreak-version-checker` CronJob to detect if a newer version (e.g., v0.4.1) is available. + +Temporarily modify the schedule to trigger it. Example (run after 5 minutes): +```yaml +# Change the schedule line in the cronjob to: +schedule: "*/5 * * * *" +``` + +After the CronJob runs, a new pod will be created. Check the pod logs to verify whether an upgrade is available: +```bash +kubectl get pods -n migration-system +kubectl logs -n migration-system +``` + +If v0.4.1 is available, the logs will indicate the upgrade availability. + +![Trigger CronJob](../../../../../public/images/cronjob-logs-available.png) \ No newline at end of file diff --git a/docs/src/content/docs/guides/How-to/virtio_doc.md b/docs/src/content/docs/guides/How-to/virtio_doc.md new file mode 100644 index 0000000..75279c3 --- /dev/null +++ b/docs/src/content/docs/guides/How-to/virtio_doc.md @@ -0,0 +1,51 @@ +--- +title: Inject VirtIO Windows Driver +description: Adds support for user-uploaded virtio-win.iso files used during Windows VM migrations. If the ISO is present at /home/ubuntu/virtio-win/virtio-win.iso, it is used directly and propagated to agents. If missing, vJailbreak attempts to download it. Migration fails gracefully if both methods are unavailable. +--- + +:::note +This feature is available from vJailbreak v0.1.13 and later. +::: + +## How to use user-provided virtio-win.iso + +Users can upload the `virtio-win.iso` to the following path on vJailbreak master node: + +```bash +/home/ubuntu/virtio-win/virtio-win.iso +``` + +:::note +In vjailbreak VM there is already a folder named `virtio-win` at `/home/ubuntu/`. Please make sure to upload to that directory and the name of the file should be `virtio-win.iso`. +::: + + +## How it works + +If the user has scaled up vJailbreak, the ISO is propagated to all the agents. +When a Windows VM migration is initiated: + +The migration logic checks for `/home/ubuntu/virtio-win/virtio-win.iso` on the source node. + +- **If found:** + - The ISO is used for injecting **VirtIO drivers** into the migrated disk. + - The ISO is **automatically propagated** to all agent nodes if needed. +- **If not found:** + - vJailbreak attempts to **download** the ISO from a known upstream source (e.g., [fedoraproject.org](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/)). +- **If both methods fail:** + - Migration fails gracefully with a clear error message. + +:::note[Does not apply to LDM system volumes] +Offline driver injection requires `virt-v2v` to write into the guest filesystem. +Windows VMs whose system volume is on a dynamic disk (LDM) skip conversion +entirely, so this ISO is not used for them — the VirtIO drivers must be installed +on the **source** VM instead. See +[Windows Dynamic Disk (LDM) Migration](../windows-ldm-migration/). +::: + + + + + + + diff --git a/docs/src/content/docs/guides/How-to/vjailbreak_settings.md b/docs/src/content/docs/guides/How-to/vjailbreak_settings.md new file mode 100644 index 0000000..f481853 --- /dev/null +++ b/docs/src/content/docs/guides/How-to/vjailbreak_settings.md @@ -0,0 +1,310 @@ +--- +title: "Use vJailbreak Settings" +description: "How to modify global settings for vJailbreak using the vjailbreak-settings ConfigMap" +--- + +The vjailbreak-settings ConfigMap provides a centralized way to customize and override default settings in the vJailbreak system. This guide shows you how to use this ConfigMap effectively. + +## Overview + +The vjailbreak-settings ConfigMap allows you to: +- Override system-wide default values +- Enable or disable optional features +- Configure global system behaviors +- Set resource limits and operational parameters + +## Checking for the ConfigMap + +In vJailbreak v0.3.0 and above, the `vjailbreak-settings` ConfigMap should already exist in your cluster. If it doesn't exist, you're likely using an older version and should upgrade to v0.3.0 or above. + +To check if the ConfigMap exists: + +```bash +kubectl get configmap vjailbreak-settings -n migration-system +``` + +If you receive an error that the ConfigMap doesn't exist, please upgrade your vJailbreak installation to the latest version. + +## Available Settings + +The vjailbreak-settings ConfigMap supports the following settings: + +| Setting | Description | Default Value | Example Values | +|---------|-------------|---------------|---------------| +| `AUTO_FSTAB_UPDATE` | Automatically update fstab during migration | `false` | `true`, `false` | +| `AUTO_PXE_BOOT_ON_CONVERSION` | Automatically configure PXE boot during conversion | `false` | `true`, `false` | +| `CHANGED_BLOCKS_COPY_ITERATION_THRESHOLD` | Number of iterations to copy changed blocks during hot migration | `20` | Any positive integer | +| `CLEANUP_PORTS_AFTER_MIGRATION_FAILURE` | Automatically cleanup OpenStack ports after migration failure | `false` | `true`, `false` | +| `CLEANUP_VOLUMES_AFTER_CONVERT_FAILURE` | Automatically cleanup OpenStack volumes after conversion failure | `false` | `true`, `false` | +| `DEFAULT_MIGRATION_METHOD` | Default method for VM migration | `cold` | `hot` (migrate while VM is running), `cold` (power off VM before migration) | +| `DEPLOYMENT_NAME` | Name of the vJailbreak deployment | `vJailbreak` | Any string | +| `NTP_SERVERS` | NTP servers the appliance synchronizes against. | Empty (default public pools) | Space-separated hostnames or IPv4 addresses (e.g., `ntp1.corp.local ntp2.corp.local`) | +| `OPENSTACK_CREDS_REQUEUE_AFTER_MINUTES` | Interval in minutes to requeue OpenStack credentials validation | `60` | Any positive integer | +| `PERIODIC_SYNC_INTERVAL` | Interval for periodic sync during admin cutover | `1h` | Duration format (e.g., `30m`, `1h`, `2h`) | +| `PERIODIC_SYNC_MAX_RETRIES` | Maximum number of retries for periodic sync | `3` | Any positive integer | +| `PERIODIC_SYNC_RETRY_CAP` | Maximum duration to retry periodic sync | `3h` | Duration format (e.g., `1h`, `3h`, `6h`) | +| `POPULATE_VMWARE_MACHINE_FLAVORS` | Automatically populate flavor recommendations for VMware machines | `true` | `true`, `false` | +| `TIMEZONE` | System time zone of the vJailbreak appliance. | Empty (UTC) | IANA time zone (e.g., `Asia/Calcutta`, `America/New_York`) | +| `VALIDATE_RDM_OWNER_VMS` | Validates that all VMs linked to an RDM disk are migrated in a single migration plan | `true` | `true`, `false` | +| `VCENTER_LOGIN_RETRY_LIMIT` | Number of retries for vCenter login attempts | `5` | Any positive integer | +| `VCENTER_SCAN_CONCURRENCY_LIMIT` | Maximum number of vCenter VMs to scan concurrently | `10` | Any positive integer | +| `VM_ACTIVE_WAIT_INTERVAL_SECONDS` | Interval to wait for VM to become active (in seconds) | `20` | Any positive integer | +| `VM_ACTIVE_WAIT_RETRY_LIMIT` | Number of retries to wait for VM to become active | `15` | Any positive integer | +| `VMWARE_CREDS_REQUEUE_AFTER_MINUTES` | Interval in minutes to requeue VMware credentials validation | `60` | Any positive integer | +| `VOLUME_AVAILABLE_WAIT_INTERVAL_SECONDS` | Interval to wait for volume to become available (in seconds) | `10` | Any positive integer | +| `VOLUME_AVAILABLE_WAIT_RETRY_LIMIT` | Number of retries to wait for volume to become available | `15` | Any positive integer | +| `HTTP_TIMEOUT_SECONDS` | Timeout in seconds for HTTP requests made by the controller | `30` | Any positive integer | +| `V2V_HELPER_POD_CPU_REQUEST` | CPU request for v2v-helper migration pods | `1000m` | Kubernetes CPU quantity (e.g., `500m`, `2000m`) | +| `V2V_HELPER_POD_CPU_LIMIT` | CPU limit for v2v-helper migration pods | `2000m` | Kubernetes CPU quantity | +| `V2V_HELPER_POD_MEMORY_REQUEST` | Memory request for v2v-helper migration pods | `1Gi` | Kubernetes memory quantity (e.g., `512Mi`, `2Gi`) | +| `V2V_HELPER_POD_MEMORY_LIMIT` | Memory limit for v2v-helper migration pods | `3Gi` | Kubernetes memory quantity | +| `V2V_HELPER_POD_EPHEMERAL_STORAGE_REQUEST` | Ephemeral storage request for v2v-helper migration pods | `3Gi` | Kubernetes storage quantity (e.g., `5Gi`, `20Gi`) | +| `V2V_HELPER_POD_EPHEMERAL_STORAGE_LIMIT` | Ephemeral storage limit for v2v-helper migration pods | `3Gi` | Kubernetes storage quantity | + +## Modifying Settings + +You can modify the ConfigMap to change settings using one of the following methods: + +### Method 1: Edit with kubectl + +```bash +kubectl edit configmap vjailbreak-settings -n migration-system +``` + +Then add or modify the data values as needed: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: vjailbreak-settings + namespace: migration-system +data: + AUTO_FSTAB_UPDATE: "false" + AUTO_PXE_BOOT_ON_CONVERSION: "false" + CHANGED_BLOCKS_COPY_ITERATION_THRESHOLD: "30" + CLEANUP_PORTS_AFTER_MIGRATION_FAILURE: "false" + CLEANUP_VOLUMES_AFTER_CONVERT_FAILURE: "false" + DEFAULT_MIGRATION_METHOD: "cold" + DEPLOYMENT_NAME: "vJailbreak" + OPENSTACK_CREDS_REQUEUE_AFTER_MINUTES: "60" + PERIODIC_SYNC_INTERVAL: "1h" + PERIODIC_SYNC_MAX_RETRIES: "3" + PERIODIC_SYNC_RETRY_CAP: "3h" + POPULATE_VMWARE_MACHINE_FLAVORS: "true" + VALIDATE_RDM_OWNER_VMS: "true" + VCENTER_LOGIN_RETRY_LIMIT: "5" + VCENTER_SCAN_CONCURRENCY_LIMIT: "150" + VM_ACTIVE_WAIT_INTERVAL_SECONDS: "30" + VM_ACTIVE_WAIT_RETRY_LIMIT: "20" + VMWARE_CREDS_REQUEUE_AFTER_MINUTES: "60" + VOLUME_AVAILABLE_WAIT_INTERVAL_SECONDS: "10" + VOLUME_AVAILABLE_WAIT_RETRY_LIMIT: "15" +``` + +### Method 2: Using kubectl patch + +You can modify individual settings without editing the entire ConfigMap: + +```bash +kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"VM_ACTIVE_WAIT_INTERVAL_SECONDS":"30","VM_ACTIVE_WAIT_RETRY_LIMIT":"20"}}' +``` + +### Method 3: From a file + +Create a file with your settings: + +```bash +cat > vjailbreak-settings.yaml << EOF +apiVersion: v1 +kind: ConfigMap +metadata: + name: vjailbreak-settings + namespace: migration-system +data: + AUTO_FSTAB_UPDATE: "false" + AUTO_PXE_BOOT_ON_CONVERSION: "false" + CHANGED_BLOCKS_COPY_ITERATION_THRESHOLD: "30" + CLEANUP_PORTS_AFTER_MIGRATION_FAILURE: "false" + CLEANUP_VOLUMES_AFTER_CONVERT_FAILURE: "false" + DEFAULT_MIGRATION_METHOD: "cold" + DEPLOYMENT_NAME: "vJailbreak" + OPENSTACK_CREDS_REQUEUE_AFTER_MINUTES: "60" + PERIODIC_SYNC_INTERVAL: "1h" + PERIODIC_SYNC_MAX_RETRIES: "3" + PERIODIC_SYNC_RETRY_CAP: "3h" + POPULATE_VMWARE_MACHINE_FLAVORS: "true" + VALIDATE_RDM_OWNER_VMS: "true" + VCENTER_LOGIN_RETRY_LIMIT: "5" + VCENTER_SCAN_CONCURRENCY_LIMIT: "150" + VM_ACTIVE_WAIT_INTERVAL_SECONDS: "30" + VM_ACTIVE_WAIT_RETRY_LIMIT: "20" + VMWARE_CREDS_REQUEUE_AFTER_MINUTES: "60" + VOLUME_AVAILABLE_WAIT_INTERVAL_SECONDS: "10" + VOLUME_AVAILABLE_WAIT_RETRY_LIMIT: "15" +EOF + +kubectl apply -f vjailbreak-settings.yaml +``` + +## Settings in Action + +### Optimizing Block Copy Operations + +To increase the number of iterations for copying changed blocks during hot migrations: + +```bash +kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"CHANGED_BLOCKS_COPY_ITERATION_THRESHOLD":"30"}}' +``` + +### Adjusting VM Activation Parameters + +To increase wait time and retry attempts for VM activation: + +```bash +kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"VM_ACTIVE_WAIT_INTERVAL_SECONDS":"30","VM_ACTIVE_WAIT_RETRY_LIMIT":"20"}}' +``` + +### Optimizing Scan Performance + +To increase the number of concurrent vCenter scan pods: + +```bash +kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"VCENTER_SCAN_CONCURRENCY_LIMIT":"150"}}' +``` + +### Configuring Periodic Sync for Admin Cutover + +To adjust the periodic sync settings for admin cutover migrations: + +```bash +kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"PERIODIC_SYNC_INTERVAL":"30m","PERIODIC_SYNC_MAX_RETRIES":"5","PERIODIC_SYNC_RETRY_CAP":"6h"}}' +``` + +This configures the system to sync every 30 minutes, with a maximum of 5 retries, and a total retry window of 6 hours. + +### Enabling Automatic Cleanup After Failures + +To automatically cleanup resources after migration failures: + +```bash +kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"CLEANUP_PORTS_AFTER_MIGRATION_FAILURE":"true","CLEANUP_VOLUMES_AFTER_CONVERT_FAILURE":"true"}}' +``` + +> **Note:** Enabling automatic cleanup helps prevent resource accumulation after failed migrations, but ensure you have proper logging and monitoring in place to track what gets cleaned up. + +### Adjusting Volume Availability Wait Parameters + +To increase wait time and retry attempts for volumes to become available: + +```bash +kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"VOLUME_AVAILABLE_WAIT_INTERVAL_SECONDS":"15","VOLUME_AVAILABLE_WAIT_RETRY_LIMIT":"20"}}' +``` + +### Configuring Credentials Revalidation Intervals + +To adjust how frequently credentials are revalidated: + +```bash +kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"OPENSTACK_CREDS_REQUEUE_AFTER_MINUTES":"30","VMWARE_CREDS_REQUEUE_AFTER_MINUTES":"30"}}' +``` + +### Configuring RDM Disk Validation + +To disable the validation that requires all VMs linked to an RDM disk to be migrated in a single migration plan: + +```bash +kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"VALIDATE_RDM_OWNER_VMS":"false"}}' +``` + +> **Note:** When `VALIDATE_RDM_OWNER_VMS` is set to `true` (default), the system ensures that all VMs sharing an RDM disk are migrated together in the same migration plan. This prevents potential data consistency issues. Only disable this validation if you understand the implications for your RDM disk configuration. + +### Setting Default Migration Method + +To set the default migration method for VMs: + +```bash +kubectl patch configmap -n migration-system vjailbreak-settings --type merge -p '{"data":{"DEFAULT_MIGRATION_METHOD":"hot"}}' +``` + +The system supports two migration methods: + +- **Hot migration**: Migrates VMs while they are running, minimizing downtime but requiring more coordination and potentially multiple sync iterations to capture changed blocks +- **Cold migration** (default): Powers off the VM before migration, ensuring data consistency but causing downtime during the entire migration process + +## Verification + +To verify your settings have been applied correctly: + +```bash +kubectl get configmap -n migration-system vjailbreak-settings -o yaml +``` + +## Applying Changes + +After modifying settings in the ConfigMap, the behavior depends on which settings you changed: + +### Settings That Require Controller Restart + +The following settings are loaded at controller startup and require a restart of the migration controller pod to take effect: + +- `OPENSTACK_CREDS_REQUEUE_AFTER_MINUTES` +- `VMWARE_CREDS_REQUEUE_AFTER_MINUTES` + +To restart the controller after changing these settings: + +```bash +kubectl rollout restart deployment migration-controller-manager -n migration-system +``` + +### Settings That Take Effect Immediately + +All other settings are read dynamically at runtime and do not require a restart: + +- `AUTO_FSTAB_UPDATE` +- `AUTO_PXE_BOOT_ON_CONVERSION` +- `CHANGED_BLOCKS_COPY_ITERATION_THRESHOLD` +- `CLEANUP_PORTS_AFTER_MIGRATION_FAILURE` +- `CLEANUP_VOLUMES_AFTER_CONVERT_FAILURE` +- `DEFAULT_MIGRATION_METHOD` +- `DEPLOYMENT_NAME` +- `PERIODIC_SYNC_INTERVAL` +- `PERIODIC_SYNC_MAX_RETRIES` +- `PERIODIC_SYNC_RETRY_CAP` +- `POPULATE_VMWARE_MACHINE_FLAVORS` +- `VALIDATE_RDM_OWNER_VMS` +- `VCENTER_LOGIN_RETRY_LIMIT` +- `VCENTER_SCAN_CONCURRENCY_LIMIT` +- `VM_ACTIVE_WAIT_INTERVAL_SECONDS` +- `VM_ACTIVE_WAIT_RETRY_LIMIT` +- `VOLUME_AVAILABLE_WAIT_INTERVAL_SECONDS` +- `VOLUME_AVAILABLE_WAIT_RETRY_LIMIT` + +### When Changes Take Effect + +For settings that don't require restart: + +- **On-demand access**: Values are read from the ConfigMap when they are needed for an operation +- **New operations**: Changes affect only new operations that start after the ConfigMap is updated +- **In-progress operations**: Running operations continue using the values they initially read +- **No caching**: The system does not cache these values for extended periods, ensuring relatively quick propagation of changes + +Typically, your changes will be effective within seconds for any new operations initiated after updating the ConfigMap. + +## Best Practices and Considerations + +### Testing Recommendations +- Always test configuration changes in a test environment that closely matches your production setup before applying them to production. +- Validate each setting change independently to understand its impact on system behavior. +- Document any changes made to default settings for future reference and troubleshooting. + +### Operational Considerations +- Setting changes take effect for new operations and do not affect in-progress tasks. +- The impact of settings varies based on your specific environment (hardware, network, storage configuration). +- Performance-related settings should be adjusted based on your specific infrastructure capabilities. + +### Monitoring and Validation +- After changing settings, monitor system behavior to ensure the changes produce the expected results. +- Use vJailbreak logs to verify that settings are being correctly applied. + +> **Important:** Always select configuration values appropriate for your specific environment. Incorrect settings may negatively impact system performance or stability. diff --git a/docs/src/content/docs/guides/How-to/vtpm_migration.md b/docs/src/content/docs/guides/How-to/vtpm_migration.md new file mode 100644 index 0000000..a3f4019 --- /dev/null +++ b/docs/src/content/docs/guides/How-to/vtpm_migration.md @@ -0,0 +1,113 @@ +--- +title: Virtual Trusted Platform Module (vTPM) VM Migration +description: Guide for migrating Windows VMs with vTPM and Virtualization Based Security (VBS) enabled +--- + +## Overview + +While Virtualization Based Security (VBS) and Virtual Trusted Platform Module (vTPM) provide important security protections for the VM, V2V helpers and guestfs require proper read and write access to the VM's disks. Temporarily disabling these features ensures that migration and disk operations can proceed reliably. + +:::caution +Windows 11 with vTPM requires an account for login to the VM. Users may need to reset their PIN after disabling vTPM. Ensure you have a **backup email configured** to reset the PIN or recover the password post migration. +::: + +## Prerequisites + +- **VMware Native Key Provider** (default vCenter-level key provider) must be configured for enabling vTPM on the source VM. +- On PCD, use `tpm_version: 2.0` and `tpm_provider: tpm-crb` as extra metadata specs on the VM flavor to enable vTPM post migration. + +![vCenter Key Provider configuration](../../../../../public/images/vtpm-key-provider.png) + +## Enabling VBS and vTPM During Windows 11 Installation + +When installing Windows 11, the user can enable VBS and vTPM at the OS selection stage by checking **"Enable Windows Virtualization Based Security"**. + +![Select guest OS with VBS enabled](../../../../../public/images/vtpm-select-guest-os.png) + +On the next stage (Customize hardware), you can verify that the **Trusted Platform Module** is present under **Security Devices**. + +![Customize hardware showing TPM present](../../../../../public/images/vtpm-customize-hardware.png) + +## Pre-Migration Steps + +### Step 1: Power Off the Guest OS + +Power off the guest OS on vCenter so that you can edit the VM settings and disable VBS and vTPM. + +### Step 2: Disable Virtualization Based Security + +1. Right-click the VM in vCenter and select **Edit Settings**. +2. Navigate to the **VM Options** tab. +3. Expand **Virtualization Based Security** and **uncheck** the **Enable** checkbox. +4. Click **OK** to save. + +![Disable VBS in VM Options](../../../../../public/images/vtpm-disable-vbs.png) + +### Step 3: Change VM Encryption Policies + +Change the encryption policies of the VM to the **Datastore Default** policy: + +1. Right-click the VM in vCenter. +2. Go to **VM Policies → Edit VM Storage Policies**. +3. Set the VM storage policy to **Datastore Default**. +4. Click **OK** to apply. + +![Edit VM Storage Policies menu](../../../../../public/images/vtpm-vm-policies-menu.png) +![Set Datastore Default policy](../../../../../public/images/vtpm-datastore-default-policy.png) + +### Step 4: Remove vTPM Device + +1. Right-click the VM in vCenter and select **Edit Settings**. +2. Under **Security Devices**, locate the **Virtual TPM** device. +3. Remove the vTPM device and confirm the deletion when prompted. + +:::caution +Removing TPM will render all encrypted data on this VM unrecoverable. Ensure you have proper backups before proceeding. +::: + +![Remove vTPM device with data loss warning](../../../../../public/images/vtpm-remove-tpm-device.png) + +## Start Migration + +With VBS disabled, encryption policies reset, and the vTPM device removed, proceed with the migration using vJailbreak as usual. + +![Migration succeeded in vJailbreak](../../../../../public/images/vtpm-migration-success.png) + +## Post-Migration Steps + +After the migration completes successfully, re-enable vTPM on the VM in PCD by following the steps below. + +### Step 1: Reset PIN Using Backup Email + +Since vTPM was removed before migration, the user will need to reset their PIN using the backup email configured earlier. + +### Step 2: Create a New Flavor with TPM Metadata + +Create a new flavor (or update an existing one) with the same size as the migrated VM, adding the following TPM metadata: + +| Key | Value | +|-----------------|----------| +| `hw:tpm_model` | `tpm-crb`| +| `hw:tpm_version`| `2.0` | + +![Edit Flavor with TPM metadata in PCD](../../../../../public/images/vtpm-flavor-metadata.png) + +### Step 3: Resize Migrated VM Using the TPM Flavor + +1. Navigate to the migrated VM in PCD. +2. Resize the VM using the newly created flavor with TPM metadata. +3. Confirm the resize operation. + +![Migrated VM details in PCD](../../../../../public/images/vtpm-vm-details-pcd.png) + +### Step 4: Verify TPM is Enabled + +After the resize completes, verify that TPM is enabled on the VM: + +- **From the hypervisor**: Run `virsh dumpxml | grep -i tpm` to confirm the `` block is present in the VM's XML definition. + +![virsh dumpxml showing TPM configuration](../../../../../public/images/vtpm-virsh-tpm-verify.png) + +- **From inside the VM**: Open **TPM Management** (`tpm.msc`) and verify that the TPM status shows **"The TPM is ready for use."** + +![TPM Management console showing TPM ready](../../../../../public/images/vtpm-tpm-management-console.png) diff --git a/docs/src/content/docs/guides/How-to/windows-ldm-migration.md b/docs/src/content/docs/guides/How-to/windows-ldm-migration.md new file mode 100644 index 0000000..df674a2 --- /dev/null +++ b/docs/src/content/docs/guides/How-to/windows-ldm-migration.md @@ -0,0 +1,205 @@ +--- +title: "Windows Dynamic Disk (LDM) Migration" +description: "Migrate Windows VMs whose system volume is on a dynamic disk (LDM), including the prerequisites and the LDM Boot Verification cutover." +--- + +Windows VMs whose **system volume sits on a dynamic disk (LDM)** follow a different +migration path. `virt-v2v` cannot convert these guests, so vJailbreak brings the VM +up on an emulated SATA controller first and lets you move it to virtio once you have +confirmed it boots. + +vJailbreak detects this automatically during the migration, but you have to prepare the source VM for such VMs. There is nothing to select in the migration form. +form. + +:::note[Only the system volume matters] +If just the **data disks** are dynamic, none of this applies. The VM migrates +normally, with conversion and every post-migration step running as usual. +::: + +Because conversion is skipped, these do not run for LDM guests: **VMware Tools +removal**, **network persistence** and **user firstboot scripts**. That is why the +tasks below are manual. + +## 1. Before you start + +vJailbreak detects LDM on its own, but if you want to know in advance which VMs +will take this path, run the precheck script on the source VM as Administrator. It +is read-only, prints a plain **YES** or **NO**, and writes a transcript to +`%TEMP%\vjb-ldm-check.log`. + +Download Test-VjbLdmSystemDisk.ps1 + +```powershell +powershell -ExecutionPolicy Bypass -File .\Test-VjbLdmSystemDisk.ps1 +``` + +It also sets an exit code — `1` for LDM, `0` for basic, `2` if inconclusive — so it +can be run across a fleet to build the list of VMs that need the steps below. + +**Take a snapshot of the source VM in vCenter before making any of the changes +below.** Both steps modify the guest, and the driver installation requires a +reboot. The snapshot is your way back if either one leaves the VM in a state you +did not intend. + +Both tasks below are performed on the source VM in vCenter, while it is still +running on ESXi, as Administrator. + +### Install the VirtIO drivers + +1. **Download the ISO.** Every build is published in the + [virtio-win archive](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/). + Windows Server 2016 and later can use the current build. + + :::caution + **Windows Server 2012 and 2012 R2 must use `virtio-win-0.1.185.iso`**, in the + `virtio-win-0.1.185-1` folder. Later builds dropped support for these versions + and leave the guest without a usable storage driver. + ::: + +2. **Upload the ISO** to a datastore the ESXi host can reach. + +3. **Attach it to the VM.** In vCenter, right-click the VM → **Edit Settings** → + **CD/DVD drive 1** → **Datastore ISO File**, browse to the ISO, and tick + **Connected**. + +4. **Run the installer in the guest.** Open File Explorer, open the mounted CD + drive, and run **`virtio-win-guest-tools.exe`**. + +5. **Click through the wizard**, accepting the defaults, and finish it. + +6. **Reboot the VM**, then disconnect the ISO. + +Nothing will look different afterwards — there is no virtio device in vCenter yet, +so the drivers sit staged until one appears at the destination. + +### Set the SAN policy + +Windows marks migrated disks offline because the controller changed; skipping this +leaves the LDM pool broken. + +``` +diskpart +san policy=onlineall +exit +``` + +## 2. Trigger the migration + +Start the migration as usual. vJailbreak skips conversion, creates the VM with +`hw_disk_bus: sata`, and attaches a **1 GB virtio temporary disk**. Windows performs +a real driver installation against that device on first boot, which is what gets the +VirtIO storage driver installed and bound — offline injection cannot do this. The +temporary disk is removed later. + +The status of the migration then changes to **LDM Boot Verification**, and the +migration waits for you to perform the cutover. + +## 3. Confirm the VM booted + +Open the console of the new VM in PCD and log in. What you should check inside the guest is that +Windows bound a VirtIO driver to the temporary disk — if it did, the root disk will work +on virtio too. Below are the commands to check that. + +**Run these before performing the cutover.** The temporary disk only exists while the +migration is held at **LDM Boot Verification**; it is removed whichever option you +select, so the output changes afterwards. + +```powershell +# The 1 GB temporary disk must be present, with VirtIO in its model name. +Get-WmiObject Win32_DiskDrive | Select-Object Model, Size + +# The controller must be healthy. +Get-WmiObject Win32_PnPEntity | Where-Object { $_.Name -like '*VirtIO*' } | + Select-Object Name, Status +``` + +Expect a disk of roughly 1 GB whose model names VirtIO, and a controller reporting +`OK`. Device Manager shows the same thing under **Storage controllers**. Both +commands work on every supported Windows version, including Server 2012. + +:::caution[Do not use `sc query viostor`] +On a VM booted from SATA this reports `STOPPED` even when the driver is installed +and working, so it will make a healthy VM look broken. The service state of a +storage miniport is not a reliable signal here — check for the device, as above. +::: + +If you re-run the same commands after the cutover, expect different output. + +Once above is verified, you will have 3 cutover options: + +| Cutover option | What the checks show afterwards | +| --- | --- | +| **Move to virtio** | The temporary disk is gone and the **root** disk now reports a VirtIO model. This is the successful end state. | +| **Keep on SATA** | The temporary disk is gone and no VirtIO disk remains, because the root disk stayed on SATA. A VirtIO controller may linger in Device Manager as a non-present device. Expected — not a failure. | +| **Rollback Migration** | The VM no longer exists in PCD. | + +## 4. Perform the cutover + +There is no timeout. The migration remains at **LDM Boot Verification** until the +cutover is performed, so it can be scheduled for a maintenance window. + +### Cutover from the UI + +Click the cutover button on the migration, either in the migrations table or on the +migration details page. A confirmation dialog appears with three options: + +| Choice | Result | +| --- | --- | +| **Move to virtio** | The VM is shut down, deleted and recreated with the root disk on virtio, keeping its name, IP and MAC. | +| **Keep on SATA** | The migration completes with the VM disk left on SATA. | +| **Rollback Migration** | The VM is deleted from PCD and the source VM in vCenter is returned to its pre-migration state. | + +**Leave the VM running** — the shutdown is handled for you. Expect a short outage +while it is recreated; the phase shows **Moving to virtio** during the rebuild. + +If the checks in step 3 did not pass, select **Keep on SATA**. The VM remains fully +functional on the SATA controller; only the performance benefit of virtio is lost. +Select **Rollback Migration** only if the VM did not boot at all. + +### Cutover using kubectl patch + +The cutover can also be performed by patching the migration `Pod`. + +```bash +kubectl get migration -n migration-system -o jsonpath='{.spec.podRef}' + +kubectl patch pod -n migration-system \ + -p '{"metadata":{"labels":{"ldmBootStatus":"success"}}}' +``` + +| Label value | Equivalent option | +| --- | --- | +| `success` | Move to virtio | +| `finish` | Keep on SATA | +| `failed` | Rollback Migration | + +## Troubleshooting + +### Disks show "Failed Redundancy" + +Seen on mirrored LDM volumes when the SAN policy was not set beforehand. Windows +marked a disk offline, so the mirror ran on one plex; by the time the disk returned, +the copies had diverged and LDM refuses to merge them. + +Confirm which disk is stale with `detail volume` or Disk Management, then: + +``` +diskpart> san policy=onlineall +Set-Disk -Number 2 -IsOffline $false +diskpart> select volume 0 ; online volume +diskpart> select volume 0 ; break disk=2 nokeep +diskpart> select volume 0 ; add disk=2 +``` + +:::danger +`break ... nokeep` deletes the named disk's plex. Confirm the disk number first — +breaking the **live** disk destroys the copy the VM has been running from. +::: + +No data is lost when the correct disk is named. There is no redundancy while the +mirror resyncs, but the window is bounded. + +### The VM did not boot on SATA + +Perform the cutover with **Rollback Migration**, correct the prerequisites, and +migrate again. diff --git a/docs/src/content/docs/guides/Troubleshooting/debug_vjailbreak_install.md b/docs/src/content/docs/guides/Troubleshooting/debug_vjailbreak_install.md new file mode 100644 index 0000000..39a3e1a --- /dev/null +++ b/docs/src/content/docs/guides/Troubleshooting/debug_vjailbreak_install.md @@ -0,0 +1,39 @@ +--- +title: "Debug vJailbreak Installation" +description: "Learn how to debug installation issues related to vJailbreak, including where the install script is located, how it works, and what to check when things go wrong." +--- + +### 1. Check Installation Logs + +All logs related to the install process are written to: + +`/var/log/pf9-install.log` + +Look here for: +- Image pull errors +- Authentication issues +- YAML apply failures +- Proxy or network errors + +> 🔍 **Tip:** If you're seeing errors related to pulling images, verify that the image registry URL is accessible from within the vJailbreak VM. + +--- + +### 2. Test Registry Access (Image Pull Failures) + +If the logs show image pull issues, run this on the vJailbreak VM: + +```bash +curl -v +``` + +🔁 What If the URL Is Accessible but Installation Still Fails? +Even if the URL is accessible, transient network issues or Kubernetes API hiccups might cause failures. + +Recheck /var/log/pf9-install.log for intermittent or recoverable errors. + +In such cases, you can safely re-run the installer: + +```bash +sudo bash /etc/pf9/install.sh +``` diff --git a/docs/src/content/docs/guides/Troubleshooting/debuglogs.md b/docs/src/content/docs/guides/Troubleshooting/debuglogs.md new file mode 100644 index 0000000..ed6063a --- /dev/null +++ b/docs/src/content/docs/guides/Troubleshooting/debuglogs.md @@ -0,0 +1,55 @@ +--- +title: "Debug Logs" +description: "Learn how vJailbreak collects and stores migration debug logs directly on the host, and how to download a full debug bundle from the UI." +--- + + +This guide outlines how vJailbreak handles debug log collection for VM migrations. Traditionally, enabling debug logs required editing ConfigMaps and restarting pods. With the current setup, debug logs are automatically collected and stored without any manual intervention. In kubectl logs of the pod, normal logs will be displayed as usual. + +## How It Works + +- For every migration executed via vJailbreak, debug logs are written to the host system under `/var/log/pf9`. +- A high-level milestone log is written for the migration at: + + `/var/log/pf9/.log` + + This mirrors the same key milestone messages (e.g. "Snapshot created", "Starting NBD server", "VM active") that also appear in `kubectl logs` for the pod — it is not a full copy of the pod's stdout/stderr. + +- In addition, logs are now **split by category** into a dedicated directory for the migration: + + `/var/log/pf9//..log` + + Each category captures the output of a specific part of the migration, so an issue can be traced straight to the relevant subsystem instead of scanning one milestone log: + + | Category | Contents | + |-----------|-------------------------------------------------------| + | `nbd` | NBD/`nbdcopy` disk-copy commands and their output | + | `virtv2v` | `virt-v2v` conversion commands and their output | + | `general` | Everything else run during the migration | + +- These logs are centrally accessible from the **vjailbreak node**, simplifying the debugging process. + +## Log File Location + +| Node Type | Path | Description | +|--------------------|----------------------------------------------------|-------------------------------------------------------| +| vjailbreak-master | `/var/log/pf9/.log` | High-level milestone log for the migration | +| vjailbreak-master | `/var/log/pf9//..log` | Per-category split logs (`nbd`, `virtv2v`, `general`) | + +## Example + +If a migration is named `vm-migrate-001`, its logs will be available at: + +- `/var/log/pf9/vm-migrate-001.log` — milestone log +- `/var/log/pf9/vm-migrate-001/nbd.2026-08-25-10:15:00.log` — disk-copy log +- `/var/log/pf9/vm-migrate-001/virtv2v.2026-08-25-10:20:00.log` — conversion log + +on the vjailbreak node. + +## Downloading a Debug Bundle from the UI + +Instead of SSHing into the vjailbreak node to collect logs manually, you can download a full debug bundle directly from the migration's **Pod logs** tab. + +![Download button on the Pod logs tab](../../../../../public/images/debug-bundle-download-button.png) + +There is a download button as shown in the image above. This downloads all the logs, debug logs, and everything related to the migration as a tar ball — no extra `kubectl` or SSH access is required. diff --git a/docs/src/content/docs/guides/Troubleshooting/nbdcopy-fails-after-vm-moved-esxi-host.md b/docs/src/content/docs/guides/Troubleshooting/nbdcopy-fails-after-vm-moved-esxi-host.md new file mode 100644 index 0000000..ab7db73 --- /dev/null +++ b/docs/src/content/docs/guides/Troubleshooting/nbdcopy-fails-after-vm-moved-esxi-host.md @@ -0,0 +1,49 @@ +--- +title: nbdcopy fails during disk copy (often DNS resolution) +description: Troubleshooting disk copy failures caused by missing DNS/hosts entries +--- + +## Problem + +A migration fails during the disk copy (live replicate) phase with an error similar to: + +```text +Failed to migrate VM: failed to live replicate disks: failed to copy disk Hard disk 1 (DeviceKey=2000): failed to run nbdcopy: exec: already started. +``` + +Error signature: +```text +failed to run nbdcopy: exec: already started +``` + +## Symptoms + +- Migration fails during the **nbdcopy** phase. +- Debug logs often show **DNS resolution errors** when attempting to connect to an ESXi host. + +## Root Cause + +During the disk copy phase, vJailbreak needs to communicate with ESXi hosts. If name resolution for an ESXi host is not available from the vJailbreak VM, the nbdcopy workflow can fail. + +This is commonly caused by missing DNS records or missing `/etc/hosts` entries for ESXi hosts. + +## Resolution + +1. Review the debug logs to confirm DNS/name-resolution errors. + +See: [Debug Logs](../debuglogs/). + +2. Ensure the vJailbreak VM can resolve ESXi host names. + +If you are not using DNS, add a static entry on the vJailbreak VM: + +```bash +sudo sh -c 'echo " " >> /etc/hosts' +``` + +3. Re-run the migration. + +## Prevention + +- Ensure DNS (or `/etc/hosts`) is configured for **all ESXi hosts** in the cluster, not just vCenter. + diff --git a/docs/src/content/docs/guides/Troubleshooting/troubleshooting.md b/docs/src/content/docs/guides/Troubleshooting/troubleshooting.md new file mode 100644 index 0000000..51c88e9 --- /dev/null +++ b/docs/src/content/docs/guides/Troubleshooting/troubleshooting.md @@ -0,0 +1,290 @@ +--- +title: Troubleshooting vJailbreak +description: Tips on effectively troubleshooting vJailbreak deployment and migration +--- + +:::note +All of the following Kubernetes commands will need to be run from the vJailbreak VM, or remotely using the vJailbreak VM's kubeconfig, located at `/etc/ranger/k3s/k3s.yaml` on the vJailbreak VM. +::: + +## Common issues + +- [VDDK unavailable: VMware's public download pages are down](#vddk-unavailable-vmwares-public-download-pages-are-down) +- [Windows Dynamic Disk (LDM) migration](../../how-to/windows-ldm-migration/) +- [nbdcopy fails during disk copy (often DNS resolution)](nbdcopy-fails-after-vm-moved-esxi-host/) +- [virt-v2v fails: rename /sysroot/etc/resolv.conf Operation not permitted](#virt-v2v-fails-rename-sysrootetcresolvconf-operation-not-permitted) +- [virt-v2v-in-place fails on RHEL 7: missing GRUB compatibility symlink](#virt-v2v-in-place-fails-on-rhel-7-missing-grub-compatibility-symlink) +- [Proxy VM disk attach fails when several migrations start together](#proxy-vm-disk-attach-fails-when-several-migrations-start-together) +- [vJailbreak Accelerated Copy fails: could not identify block device](#vjailbreak-accelerated-copy-fails-could-not-identify-block-device) + +vJailbreak is deployed on Kubernetes running on Ubuntu 22.04.5, and distributed as a QCOW2 image. The Kubernetes namespace `migration-system` contains the vJailbreak UI and migration controller pods. Each VM migration will spawn a migration object. The status field contains a high level view of the progress of the migration of the VM. For more details about the migration, check the logs of the pod specified in the Migration object. + +### Getting logs +List all pods in the migration namespace +```bash +kubectl -n migration-system get pod +``` + +Find a specific VM migration pod +```bash +kubectl -n migration-system get pod | grep +``` + +Get details & events for a v2v-helper pod. This is helpful if a migration is stuck in a pending state, or to track the progress of a migration without the UI. +```bash +kubectl -n migration-system describe pod +``` + +Get logs for a specific migration pod. This shows more detail than `describe pod`. +```bash +kubectl logs -n migration-system +``` + +Get logs for the `migration-controller-manager` +```bash +kubectl logs -n migration-system deploy/migration-controller-manager +``` + +Turn on Debug Mode +```bash +kubectl patch configmap -n migration-system migration-config- --type merge -p '{"data":{"DEBUG":"true"}}' +``` +### A migration is stuck in pending +If the migration was set to Retry on Failure, then delete the v2v-helper pod for that VM and collect the logs of the pod that comes up. + +```bash +kubectl delete pod -n migration-system v2v-helper- +``` +If the v2v-helper pod doesn't come back up, and you can't delete the migration in the UI, then delete the associated `migrationplan`. +- First, get the `migrationplan` object name UUID for the associated VMs: +```bash +kubectl get migrationplans -n migration-system -o yaml +``` +- Then delete the `migrationplan` object, which should remove it from the UI. +```bash +kubectl delete migrationplan -n migration-system +``` +### A migration failed and I want to run it again +Use the **Retry** action on the failed migration in the vJailbreak UI. It reopens the migration form pre-filled with the original configuration, so you can correct the setting that caused the failure before starting again. To restart several failed migrations without changing anything, select them in the **Migrations** table and use **Retry Selected**. + +See [Retry a Failed Migration](../how-to/retry_failed_migration/) for the full workflow and its limitations. + +### Get all vJailbreak custom resource definitions (CRDs) + +```bash +kubectl get migrationplans,migrations,migrationtemplates,networkmappings,openstackcreds,storagemappings,vmwarecreds,secrets -n migration-system -o yaml +``` + +--- + +## virt-v2v fails: rename /sysroot/etc/resolv.conf Operation not permitted + +- **Symptom** + + `virt-v2v` or `virt-v2v-in-place` fails with an error similar to: + + ```text + renaming /sysroot/etc/resolv.conf to /sysroot/etc/6vvk9gzd + guestfsd: error: rename: /sysroot/etc/resolv.conf to /sysroot/etc/6vvk9gzd: Operation not permitted + commandrvf: stdout=n stderr=n flags=0x0 + commandrvf: umount /sysroot/sys + virt-v2v-in-place: error: libguestfs error: sh_out: rename: /sysroot/etc/resolv.conf to /sysroot/etc/6vvk9gzd: Operation not permitted + ``` + +- **Cause** + + On some Linux VMs, `/etc/resolv.conf` is marked **immutable**. When `virt-v2v` tries to rename or replace this file inside the guest filesystem during conversion, the immutable attribute prevents the operation and conversion fails. + + You can confirm the immutable bit inside the source VM with: + + ```bash + lsattr /etc/resolv.conf + ----i----------------- /etc/resolv.conf + ``` + + The `i` flag indicates the file is immutable. + +- **Resolution** + + 1. Remove the immutable attribute inside the source VM before migration: + + ```bash + chattr -i /etc/resolv.conf + ``` + + 2. Verify the attribute is gone: + + ```bash + lsattr /etc/resolv.conf + ---------------------- /etc/resolv.conf + ``` + + 3. Re-run the migration. + +- **Notes** + + - This is a known and documented `virt-v2v` issue. [See upstream documentation](https://libguestfs.org/virt-v2v.1.html#linux%3A-rename%3A-sysroot-etc-resolv.conf-failure). + - If configuration management or security hardening marks `/etc/resolv.conf` immutable, ensure this is unset before conversion, or adjust your automation so VMs intended for conversion do not have `/etc/resolv.conf` marked immutable. + +--- + +## Disk attach fails during migration: No more available PCI slots + +- **Symptom** + + During a migration, attaching a target volume to the vJailbreak VM (or an agent VM) fails. The `nova-compute` log on the OpenStack side shows an error similar to: + + ```text + TRACE nova.virt.libvirt.driver [instance: ] libvirt.libvirtError: internal error: No more available PCI slots + ``` + +- **Cause** + + During conversion, vJailbreak attaches the target Cinder volumes to the vJailbreak VM (or its agent VMs) to copy and convert the disk data. If the vJailbreak image was uploaded without a disk bus setting, OpenStack attaches these volumes using the default **virtio-blk** bus, where **every attached volume is a separate PCI device** and consumes its own PCI slot. + + The virtual PCI bus has a limited number of slots, several of which are already used by essential devices (network interfaces, video, memory balloon, and so on). Migrating VMs with many disks — or running many migrations in parallel on one agent — exhausts the available PCI slots, and the volume attach fails with the error above. + +- **Resolution** + + Configure the vJailbreak image to use the **virtio-scsi** disk bus. With virtio-scsi, all attached volumes share a single SCSI controller that consumes only one PCI slot and supports up to 256 devices. + + 1. Set the following properties on the vJailbreak image **before** creating the vJailbreak VM: + + ```bash + openstack image set \ + --property hw_disk_bus=scsi \ + --property hw_scsi_model=virtio-scsi \ + + ``` + + 2. Deploy the vJailbreak VM from the updated image, then re-run the migration. + +- **Notes** + + - The disk bus is fixed when the VM is created. If your vJailbreak VM is already deployed, setting the properties on the image is not enough — you must recreate the vJailbreak VM from the updated image. + - Agent VMs created during [scale up](../../how-to/scaling/) use the same image, so set these properties before scaling up agents. + - See also: [Known Limitations](../../../reference/known-limitations/#pci-slot-exhaustion-when-attaching-disks-with-virtio-blk). + +--- + +## virt-v2v-in-place fails on RHEL 7: missing GRUB compatibility symlink + +- **Symptom** + + A RHEL 7.x migration fails during `virt-v2v-in-place`, after disk copy and volume attach/detach have already succeeded. The migration log only shows: + + ```text + failed to run virt-v2v-in-place: exit status 1 + ``` + + The debug log under `/var/log/pf9/` (see [Debug Logs](debuglogs/)) shows the actual error: + + ```text + virt-v2v-in-place: error: libguestfs error: command: + error opening /boot/grub/grub.cfg for read: + No such file or directory + ``` + +- **Cause** + + RHEL 7's `grubby`, used internally by `virt-v2v-in-place`, expects `/boot/grub/grub.cfg` to be a symlink to `/boot/grub2/grub.cfg`. On affected guests GRUB2 itself is configured correctly, but this compatibility symlink is missing, so `grubby`'s file open fails. This is unrelated to [SUSE Legacy GRUB 0.97](../../../reference/known-limitations/#suse-linux-sles-sled-with-legacy-grub-097), which requires a GRUB2 upgrade instead. + +- **Resolution** + + Before migrating a BIOS RHEL 7 guest, confirm: + + - `/boot/grub2/grub.cfg` exists and is non-empty (regenerate with `grub2-mkconfig -o /boot/grub2/grub.cfg` if not). + - `/boot/grub/grub.cfg` exists as a symlink to `../grub2/grub.cfg` (`mkdir -p /boot/grub && ln -sfn ../grub2/grub.cfg /boot/grub/grub.cfg` if missing). + - `/etc/grub2.cfg` and `/etc/grub.conf` resolve to the same file — recreate the same way if broken. + + Then re-run the migration. + +- **Notes** + + - Check ahead of a migration wave: `test -L /boot/grub/grub.cfg && echo OK || echo MISSING`. + - Observed on RHEL 7.9 (Maipo); other RHEL 7.x releases with the same layout may be affected. + - See also: [Known Limitations](../../../reference/known-limitations/#rhel-7-guests-missing-grub-compatibility-symlink). + +--- + +## VDDK unavailable: VMware's public download pages are down + +- **Symptom** + + VMware's public VDDK download pages are currently unavailable. You cannot download VDDK from + VMware's official site. + +- **Impact** + + Only the **Standard copy method** requires VDDK. **vJailbreak Accelerated Copy** and + **Storage-Accelerated Copy** do not need VDDK and are fully unaffected. + +- **Resolution** + + Use **vJailbreak Accelerated Copy** or **Storage-Accelerated Copy** instead of Standard copy. + Both methods work without VDDK installed on the vJailbreak appliance. + + :::caution[Cold migration only] + vJailbreak Accelerated Copy supports **cold migration only**: the source VM must be powered + off before copy begins. It does not support live (hot) migration. If live migration is required, + wait for VDDK to become available and use Standard copy. + ::: + + See [vJailbreak Accelerated Copy](../../../concepts/vjailbreak-accelerated-copy/) and + [Storage-Accelerated Copy](../../../concepts/storage-accelerated-copy/) for setup instructions. + +--- + +## Proxy VM disk attach fails when several migrations start together + +- **Symptom** + + A batch of [vJailbreak Accelerated Copy](../../../concepts/vjailbreak-accelerated-copy/) migrations is started at once. Some of them fail early with a vCenter error while attaching the source snapshot disks to the Proxy VM, while the rest continue into the copy phase without any problem. + +- **Cause** + + Each migration attaches its source disks to the Proxy VM as a vCenter VM reconfigure task. When several migrations issue these tasks against the same Proxy VM simultaneously, vCenter does not always serialize them gracefully and rejects some of the attach requests. + + This is a **transient race condition** — the Proxy VM, its credentials, and its configuration are all fine. Only the migrations that lost the race are affected. + +- **Resolution** + + 1. Let the surviving migrations progress past the attach step and into the copy phase. + 2. [Retry](../../how-to/retry_failed_migration/) the failed migrations. They normally succeed on the second attempt. + +- **Notes** + + - To reduce the chance of hitting this, stagger migration start times rather than starting a large batch at once, or register additional Proxy VMs and distribute migrations across them. + - Use the [data copy start time](../../../concepts/migration-options/#data-copy-start-time) option to spread a wave of migrations over a window. + - See also: [Known Limitations](../../../reference/known-limitations/#concurrent-disk-attach-can-fail). + +--- + +## vJailbreak Accelerated Copy fails: could not identify block device + +- **Symptom** + + A [vJailbreak Accelerated Copy](../../../concepts/vjailbreak-accelerated-copy/) migration fails after the snapshot disk has been attached to the Proxy VM: + + ```text + could not identify block device for disk + ``` + +- **Cause** + + vJailbreak locates each attached disk inside the Proxy VM by matching its disk UUID to a block device. Two conditions must be met for this to work: + + 1. `disk.EnableUUID` is set to `TRUE` on the Proxy VM, so the UUID is visible to the guest. + 2. The Proxy VM's first SCSI controller (**SCSI controller 0**) is of type **VMware Paravirtual (PVSCSI)**. Only PVSCSI is supported — LSI Logic SAS, LSI Logic Parallel, and BusLogic Parallel controllers do not work. + +- **Resolution** + + 1. Confirm `disk.EnableUUID = TRUE` on the Proxy VM: vSphere Client → **Edit Settings** → **VM Options** → **Advanced** → **Edit Configuration**. + 2. Confirm **SCSI controller 0** is **VMware Paravirtual**. If it is not, power off the Proxy VM, then in **Edit Settings** → **Virtual Hardware** set **SCSI controller 0** → **Change Type** → **VMware Paravirtual**, and power it back on. + 3. Re-verify the Proxy VM in the vJailbreak UI and re-run the migration. + +- **Notes** + + - SSH into the Proxy VM and run `lsblk` to confirm the attached disks are visible to the guest. + - Check vCenter events on the Proxy VM for disk attach errors if the disk never appears. + - See also: [Known Limitations](../../../reference/known-limitations/#proxy-vm-must-use-a-pvscsi-controller) and [Configure the SCSI Controller Type on the Proxy VM](../../../concepts/vjailbreak-accelerated-copy/#configure-the-scsi-controller-type-on-the-proxy-vm). diff --git a/docs/src/content/docs/guides/Troubleshooting/vmware_residual_artifacts.md b/docs/src/content/docs/guides/Troubleshooting/vmware_residual_artifacts.md new file mode 100644 index 0000000..2abbd1d --- /dev/null +++ b/docs/src/content/docs/guides/Troubleshooting/vmware_residual_artifacts.md @@ -0,0 +1,105 @@ +--- +title: VMware Residual Artifacts +description: Residual VMware artifacts +--- + +In v0.4.4, the following artifacts remain on the Windows VMs after selecting "Remove VMware Tools" option: + + +### 1. VMware Driver Files + +| Driver File Path | 2012 | 2016 | 2019 | 2022 | 2025 | Win11 | +|---|---|---|---|---|---|---| +| C:\Windows\System32\drivers\vmci.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vm3dmp.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vmaudio.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vmhgfs.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vmmemctl.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vmmouse.sys | Not Found | Not Found | Not Found | Not Found | Not Found | **Present** | +| C:\Windows\System32\drivers\vmrawdsk.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vmtools.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vmusbmouse.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vmvss.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vsock.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vmx_svga.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vmxnet3.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vm3dmp-stats.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vm3dmp_loader.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vm3dmp-debug.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vm3dservice.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vmgid.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vmgencounter.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vms3cap.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Windows\System32\drivers\vmstorfl.sys | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | + +### 2. VMware Registry Keys + +| Registry Key Path | 2012 | 2016 | 2019 | 2022 | 2025 | Win11 | +|---|---|---|---|---|---|---| +| HKLM:\SOFTWARE\VMware, Inc. | **Present** | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SOFTWARE\VMware | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SOFTWARE\WOW6432Node\VMware, Inc. | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SOFTWARE\WOW6432Node\VMware | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\vmci | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\vm3dmp | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\vm3dmp_loader | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\vm3dmp-debug | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\vm3dmp-stats | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\vm3dservice | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\vmaudio | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\vmhgfs | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\VMMemCtl | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\vmmouse | Not Found | Not Found | Not Found | Not Found | Not Found | **Present** | +| HKLM:\SYSTEM\CurrentControlSet\Services\vmrawdsk | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\VMRawDisk | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\VMTools | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\vmusbmouse | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\vmvss | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\vmvsock | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\VMwareCAF | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\VMwareCAFCommAmqpListener | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\VMwareCAFManagementAgentHost | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| HKLM:\SYSTEM\CurrentControlSet\Services\vnetWFP | **Present** | Not Found | Not Found | Not Found | Not Found | Not Found | + +### 3. VMware Folders + +| Folder Path | 2012 | 2016 | 2019 | 2022 | 2025 | Win11 | +|---|---|---|---|---|---|---| +| C:\Program Files\VMware | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Program Files (x86)\VMware | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Program Files\Common Files\VMware | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Program Files (x86)\Common Files\VMware | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\ProgramData\VMware | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Users\Administrator\AppData\Local\VMware | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\Users\Administrator\AppData\Roaming\VMware | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | +| C:\ProgramData\Microsoft\Windows\Start Menu\Programs\VMware | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | + +### 4. Startup Entries + +| Startup Entry | 2012 | 2016 | 2019 | 2022 | 2025 | Win11 | +|---|---|---|---|---|---|---| +| vmtoolsd (HKLM:\Software\Microsoft\Windows\CurrentVersion\Run) | Not Found | Not Found | Not Found | Not Found | Not Found | Not Found | + +### 5. VMware Devices (Device Manager) + +Devices with **Error** status indicate a residual device entry whose driver was removed with VMware Tools. + +| Device Name | 2012 | 2016 | 2019 | 2022 | 2025 | Win11 | +|---|---|---|---|---|---|---| +| VMware VMCI Host Device | Error | Error | Error | Not Found | Not Found | Not Found | +| VMware Pointing Device | Error | Error | Error | Not Found | Not Found | Error | +| **Total devices found** | **2** | **2** | **2** | **0** | **0** | **1** | + +### 6. Impact of Remaining Artifacts + +| Artifact | Versions | Impact | +|---|---|---| +| `vmmouse.sys` + `HKLM:\SYSTEM\CurrentControlSet\Services\vmmouse` | Win11 | Residual VMware mouse driver. After migration of VMware hypervisor, there's no VMware hardware to drive, so it's inert. The Pointing Device shows Error in Device Manager but Windows falls back to standard HID drivers — mouse input works normally. | +| `HKLM:\SOFTWARE\VMware, Inc.` | 2012 | Metadata-only registry key left by the VMware installer. No services load from it, no runtime effect. May appear in software inventory/audit tools as VMware still "installed" but it's not. | +| `HKLM:\SYSTEM\CurrentControlSet\Services\vnetWFP` | 2012 | VMware virtual network Windows Filtering Platform driver. The service entry remains but since the driver binary is gone, Windows will fail to start it silently. No network degradation observed. | +| VMware Pointing Device / VMCI Host Device (Error) | 2012, 2016, 2019, Win11 | Phantom device entries in Device Manager with no loaded driver (Code 28). Cosmetic only — no runtime effect, no performance impact, no BSOD risk. Windows ignores driver-less device entries during normal operation. | + +**Summary:** +- None of these remnants are harmful to VM operation or stability post-migration. +- The `vnetWFP` service key on Windows 2012 is the most noteworthy from a compliance audit standpoint, but has no observed runtime impact. +- The Error devices in Device Manager are cosmetic — they do not affect functionality. diff --git a/docs/src/content/docs/guides/Troubleshooting/windows-dynamic-disk-ldm-migration-issue.md b/docs/src/content/docs/guides/Troubleshooting/windows-dynamic-disk-ldm-migration-issue.md new file mode 100644 index 0000000..14bf806 --- /dev/null +++ b/docs/src/content/docs/guides/Troubleshooting/windows-dynamic-disk-ldm-migration-issue.md @@ -0,0 +1,45 @@ +--- +title: Windows Dynamic Disk (LDM) migration issue +description: Windows VMs with a dynamic disk (LDM) system volume are supported through a dedicated migration path. +sidebar: + hidden: true +--- + +:::note[This page has moved] +Windows VMs with an LDM system volume are supported. Everything about this +migration path — prerequisites, what vJailbreak does, the cutover, and +troubleshooting — is documented in one place: +[Windows Dynamic Disk (LDM) Migration](../../how-to/windows-ldm-migration/). +::: + +## Why LDM needs a different path + +**LDM (Logical Disk Manager)** is Windows' volume manager for "dynamic disks". It +is conceptually similar to Linux LVM, but it stores its volume metadata in a +private database at the end of each disk rather than in a standard partition +table. + +`virt-v2v` converts a guest by inspecting it with libguestfs, reading the +`SYSTEM` and `SOFTWARE` registry hives with Hivex, and writing VirtIO drivers and +registry changes back into the offline filesystem. When the Windows system volume +is an LDM volume, libguestfs cannot reliably assemble it, so inspection fails +before conversion can start. + +Rather than requiring the disk to be converted to basic beforehand, vJailbreak +skips conversion for these guests and brings the VM up on an emulated SATA +controller, which Windows can boot without VirtIO drivers. A temporary virtio +temporary disk lets Windows install the `viostor` driver itself, and the migration +then waits at the **LDM Boot Verification** phase for you to move the root disk to +virtio. + +:::caution +Only the **system volume** matters. If just the data disks are dynamic, the VM +migrates normally and no special handling is needed. +::: + +## Converting to basic disks is no longer required + +Earlier versions of this guide recommended running `diskpart` → `convert basic` on +the source VM before migrating. That is no longer necessary, and `convert basic` +requires an empty disk in any case. Follow the +[LDM migration guide](../../how-to/windows-ldm-migration/) instead. diff --git a/docs/src/content/docs/guides/Troubleshooting/windows-offline-disks.md b/docs/src/content/docs/guides/Troubleshooting/windows-offline-disks.md new file mode 100644 index 0000000..fbacf88 --- /dev/null +++ b/docs/src/content/docs/guides/Troubleshooting/windows-offline-disks.md @@ -0,0 +1,169 @@ +--- +title: Windows Offline Disks After Migration +description: Troubleshooting guide for Windows VMs with offline disks after migration from VMware to PCD +--- + +# Windows Offline Disks After Migration + +## Problem Description + +After migrating a Windows VM from VMware vCenter to PCD using vJailbreak, additional disks (beyond the primary C: drive) may not be visible inside the Windows operating system, even though they are successfully attached to the VM in PCD. + +### Symptoms + +- The VM migrates successfully and shows as active in PCD +- PCD shows all volumes/disks are attached (e.g., 3 disks attached) +- Inside the Windows VM, only the primary C: drive is visible +- Additional drives (e.g., E:, F:, G:) that existed before migration are missing +- The disks exist but are in an "Offline" state in Windows Disk Management + +### Root Cause + +This issue occurs due to Windows SAN Policy settings. When Windows detects disks on a SAN (Storage Area Network), it applies a policy that determines whether new disks are automatically brought online or kept offline. + +After migration from VMware to PCD, the storage subsystem changes, and Windows may apply the **"Offline Shared"** SAN policy to the migrated disks. This policy keeps disks offline by default to prevent data corruption in shared storage scenarios. + +The default SAN policies in Windows are: +- **Offline Shared**: Keeps all shared disks offline (common after migration) +- **Offline All**: Keeps all new disks offline +- **Online All**: Automatically brings all new disks online + +## Manual Workaround + +Before migrating the VM, you can manually fix this issue by changing the SAN policy and bringing disks online. + +### Step 1: Check Current SAN Policy + +Open Command Prompt as Administrator and run: + +```cmd +C:\> diskpart +DISKPART> SAN +``` + +You will likely see: +``` +SAN Policy : Offline Shared +``` + +### Step 2: Change SAN Policy to Online All + +```cmd +DISKPART> SAN POLICY=OnlineAll +``` + +### Step 3: Verify the Change + +```cmd +DISKPART> SAN +``` + +You should now see: +``` +SAN Policy : Online All +``` + +### Step 4: Bring Offline Disks Online + +While still in diskpart: + +```cmd +DISKPART> list disk +``` + +Identify offline disks (marked with an asterisk *), then for each offline disk: + +```cmd +DISKPART> select disk +DISKPART> online disk +``` + +### Step 5: Exit and Verify + +```cmd +DISKPART> exit +``` + +Check File Explorer - your drives (E:, F:, G:, etc.) should now be visible. + +## Automated Solution + +vJailbreak provides automated scripts to detect and fix this issue during first boot after migration. + +### Available Scripts + +Two BAT scripts are available in the `scripts/firstboot/windows/` directory. These scripts generate PowerShell scripts that run on first boot: + +1. **`check-disks.bat`** - Generates a diagnostic PowerShell script that only checks disk status +2. **`disk-online-fix.bat`** - Generates an automated fix PowerShell script that brings offline disks online + +**Usage**: Copy the contents of either BAT file and paste it into the **Post Migration Script** field in the migration form. The script will execute automatically on first boot after migration. + +### Script 1: Check Disks (Diagnostic Only) + +The `check-disks.bat` script generates `check-disks.ps1` which performs a read-only analysis: + +- Scans all physical disks +- Reports operational status (Online/Offline) +- Lists partitions and drive letter assignments +- Identifies disks without drive letters +- Checks for read-only disks and health issues +- Generates a detailed report at `C:\DiskStatus_Report.txt` + + +### Script 2: Disk Online Fix (Automated Repair) + +The `disk-online-fix.bat` script generates and executes `check-disks-fix.ps1` which automatically fixes offline disk issues: + +- Performs all diagnostic checks from Script 1 +- **Automatically brings ALL offline disks online** +- Logs all actions to `C:\DiskStatus_Report.txt` + +**Note**: The generated PowerShell script (`check-disks-fix.ps1`) is a separate file from the diagnostic-only `check-disks.ps1`. It includes all diagnostic functionality plus automated repair capabilities. + + +## ⚠️ Important Warnings + +### Blanket Online Policy + +The automated fix script uses a **blanket approach** to bring ALL offline disks online without discrimination. This is necessary because: + +- Pre-migration disk states (online/offline) are unknown +- The script cannot determine if a disk was intentionally kept offline before migration +- This is designed for automated firstboot scenarios after VM conversion + +### Risks and Considerations + +1. **Intentionally Offline Disks**: If the source VM had disks that were intentionally kept offline (for backup, security, or operational reasons), the script will bring them online. This may not align with your original configuration. + +2. **Shared Storage**: In environments with shared storage, bringing disks online indiscriminately could potentially cause issues if the same disk is accessed by multiple systems. + +3. **Testing Required**: Always test this script in a non-production environment first to ensure it aligns with your migration policy. + +4. **No Drive Letter Assignment**: The script does NOT automatically assign drive letters to partitions. If a partition lacks a drive letter, you must assign it manually using Disk Management or PowerShell cmdlets. + +### Recommendations + +- Review the diagnostic output from `check-disks.bat` before running the automated fix +- Document which disks should be online in your source environment +- Test the migration process with a non-critical VM first +- Review the log file at `C:\DiskStatus_Report.txt` after running the fix script +- Manually verify that all expected drives are accessible after the fix + +## Prevention + +To prevent this issue in future migrations, you can: + +1. **Pre-configure SAN Policy**: Before migration, set the SAN policy on the source VM to `OnlineAll` +2. **Post-migration Automation**: Copy the contents of `disk-online-fix.bat` into the Post Migration Script field in the migration form +3. **Document Disk States**: Maintain documentation of which disks should be online/offline for each VM + +:::caution[LDM system volumes] +The automated solution above relies on firstboot scripts, which `virt-v2v` installs +during conversion. Windows VMs whose system volume is on a dynamic disk (LDM) skip +conversion, so the Post Migration Script does not run for them — the SAN policy +**must** be set on the source VM beforehand. Leaving a disk offline on these guests +breaks the LDM volume set. See +[Windows Dynamic Disk (LDM) Migration](../../how-to/windows-ldm-migration/). +::: + diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx new file mode 100644 index 0000000..5c4f4e2 --- /dev/null +++ b/docs/src/content/docs/index.mdx @@ -0,0 +1,191 @@ +--- +title: vJailbreak +description: A free and open-source tool that simplifies the migration of virtual machines from VMware to any OpenStack-compliant cloud. +template: splash +head: + - tag: script + content: | + window.location.href = 'https://platform9.com/vjailbreak/'; +hero: + tagline: Effortless Open-Source VMware to OpenStack Migration + image: + html: | +
+
+ +
+
+ actions: + - text: Getting Started + link: introduction/getting_started/ + icon: right-arrow + - text: Clone the Repo + link: https://github.com/platform9/vjailbreak + icon: github + variant: minimal + align: center +--- + +import { + Card, + CardGrid, + Icon, + Steps, + Tabs, + TabItem, +} from "@astrojs/starlight/components" +import AboutVJB from "../../components/about.astro" +import Aurora from "../../components/Aurora.astro" + + + +### Download the latest Version + + +
    +
  1. + **Install ORAS** + + + + ``` + brew install oras + ``` + + + + ``` + snap install oras --classic + ``` + + + + ``` + VERSION="1.2.2" + curl -LO "https://github.com/oras-project/oras/releases/download/v${VERSION}/oras_${VERSION}_darwin_arm64.tar.gz" + mkdir -p oras-install/ + tar -zxf oras_${VERSION}_*.tar.gz -C oras-install/ + sudo mv oras-install/oras /usr/local/bin/ + rm -rf oras_${VERSION}_*.tar.gz oras-install/ + ``` + + + + ``` + VERSION="1.2.2" + curl -LO "https://github.com/oras-project/oras/releases/download/v${VERSION}/oras_${VERSION}_linux_amd64.tar.gz" + mkdir -p oras-install/ + tar -zxf oras_${VERSION}_*.tar.gz -C oras-install/ + sudo mv oras-install/oras /usr/local/bin/ + rm -rf oras_${VERSION}_*.tar.gz oras-install/ + ``` + + +
  2. + +
  3. + **Download Image** + + ``` + oras pull quay.io/platform9/vjailbreak:v0.4.10 + ``` +
  4. + +
  5. + **Get PCD** + + + + ``` + https://platform9.com/private-cloud-director/ + ``` + + + + ``` + https://platform9.com/docs/private-cloud-director/private-cloud-director/getting-started-with-community-edition + ``` + + + + ``` + https://www.openstack.org/ + ``` + + +
  6. + +
+
+ +
+

+ Enterprise VM Migration: VMware to OpenStack, Simplified +

+

+ vJailbreak is an open-source tool featuring a user-friendly interface + designed to simplify and accelerate the migration of virtual machines (VMs) + from VMware vSphere environments to any OpenStack-compliant cloud. It + eliminates the complexities of cross-platform VM migration, enabling you to + modernize your infrastructure with minimal disruption and a streamlined, + visual workflow. +

+
+ +

Key Benefits

+ + + + Intuitive UI with no command-line requirements for seamless VM migration + + + + Automated tasks with real-time progress tracking reduce migration time + + + + Maintain source environment operations with minimal downtime + + + + Open-source solution eliminating expensive licensing costs + + + + Compatible with any standard OpenStack-compliant cloud platform + + + + Guided workflow with automated health checks and validation + + + +

Get Started

+ +

+ Ready to experience simplified, UI-driven VM migration? +

+ + + + Deploy vJailbreak and connect it to your vSphere and OpenStack environments. + Learn more about installation, configuration, and usage in the [vJailbreak + Documentation](introduction/getting_started/). + + + Help improve vJailbreak by contributing code, documentation, or bug reports. + View our [contribution + guidelines](https://github.com/platform9/vjailbreak/blob/main/CONTRIBUTING.md) + to get started. + + + + diff --git a/docs/src/content/docs/introduction/architecture-components.md b/docs/src/content/docs/introduction/architecture-components.md new file mode 100644 index 0000000..f6e56eb --- /dev/null +++ b/docs/src/content/docs/introduction/architecture-components.md @@ -0,0 +1,28 @@ +--- +title: Components +description: Overview of vJailbreak components +--- + + +# Architecture +Below is high level architecture of how vJailbreak works. vJailbreak runs +in a virtual machine in the target OpenStack environment. vJailbreak connects with VMware environment via vSphere APIs, using the VDDK library for the Standard copy method only. vJailbreak Accelerated Copy and Storage-Accelerated Copy transfer disk data without requiring VDDK. It also uses the OpenStack SDK to interact with the OpenStack environment and perform the necessary provisioning operations including creation of volumes, VMs. + +![vJailbreak Architecture](/vjailbreak/images/deployment-architecture.png) + +# Components +Below is an overview of each component and its role in the migration process. + +### v2v-helper +The `v2v-helper` is the main application responsible for executing the migration process. It is designed to run as a pod within the vJailbreak virtual machine (VM) in the target OpenStack environment. It supports three storage copy methods: Standard (VDDK-based), vJailbreak Accelerated Copy (Hot-Add, VDDK-free), and Storage-Accelerated Copy (XCOPY, VDDK-free). Only Standard copy requires VDDK. + +### UI +The `UI` component provides a user-friendly interface for vJailbreak. It allows users to manage and monitor the migration process through an intuitive graphical interface. + +### migration-controller +The `migration-controller` is a Kubernetes controller that schedules and manages the migration tasks. It ensures that migrations are executed efficiently and in accordance with the defined policies. + +### v2v-cli +The `v2v-cli` is a command-line interface tool that can initiate the migration process. While it is available, it is not required in the current version of vJailbreak, as the primary interface is the UI. + +By understanding these components, users can better appreciate the architecture and functionality of vJailbreak, enabling them to effectively manage and execute VM migrations. \ No newline at end of file diff --git a/docs/src/content/docs/introduction/faq.md b/docs/src/content/docs/introduction/faq.md new file mode 100644 index 0000000..7d24088 --- /dev/null +++ b/docs/src/content/docs/introduction/faq.md @@ -0,0 +1,129 @@ +--- +title: FAQ +description: frequently asked questions +--- + +### What should I do if I cannot download VDDK? + +VMware's public VDDK download pages are currently unavailable. VDDK is only required for the +**Standard** storage copy method. + +**vJailbreak Accelerated Copy** and **Storage-Accelerated Copy** do not require VDDK and can be +used immediately: + +- [vJailbreak Accelerated Copy](../../concepts/vjailbreak-accelerated-copy/): works with any + datastore; **cold migration only** (source VM must be powered off before copy begins, live/hot + migration is not supported) +- [Storage-Accelerated Copy](../../concepts/storage-accelerated-copy/): requires a supported + storage array (Pure Storage or NetApp); cold migration only + +### Are IPs and MAC addresses persisted? +Yes, if your OpenStack network has a valid subnet range that allows the IP to be allocated, vJailbreak will create a port with the same MAC address and IP address as the source VM. + +### Are network interface names persisted? +Yes, vJailbreak can preserve network interface names during migration. + +To enable this behavior, select **Persist source network interfaces** in the migration form under **Migration Options**. + +Read more in [Migration Options](../../concepts/migration-options/#persist-source-network-interfaces). + +### What OS versions are supported? +We internally use virt-v2v, so all operating systems supported for conversion by virt-v2v are supported by vJailbreak. You can find a detailed list of them [here](https://libguestfs.org/virt-v2v-support.1.html#guests). + +### Do I need to perform any manual steps to remove VMware Tools? +No, vJailbreak will remove them for you, with the help of virt-v2v. The process that virt-v2v uses along with alternative approaches can be found [here](https://libguestfs.org/virt-v2v.1.html#converting-a-windows-guest). + +### Do I need to perform any manual steps to install drivers for Linux and Windows VMs? +No, vJailbreak will install it for you. For Windows, we allow you to specify a URL for a specific version of virtio drivers. This is useful for older Windows versions, eg. Windows Server 2012, which specifically need [v0.1.189](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.189-1/virtio-win-0.1.189.iso) in order to work. + +### Why does the conversion step take time? +The delay is typically not because a single script is slow. The overall conversion process includes OS-level changes that take time, such as installing VirtIO drivers, removing old hypervisor drivers, and (for Windows guests) performing registry changes. + +The helper scripts that apply static changes (for example, writing mount persistence entries to `/etc/fstab`) are simple and usually complete quickly. + +Conversion time depends heavily on your infrastructure performance (especially CPU) and VM-specific factors, including the guest OS and root disk size. + +### Why does nbdcopy fail during disk copy? +If this issue is seen, most of the time it is a DNS/name-resolution problem. Debug logs typically show DNS resolution errors when vJailbreak tries to connect to an ESXi host. + +Error signature: +```text +failed to run nbdcopy: exec: already started +``` + +See: [Debug Logs](../../guides/troubleshooting/debuglogs/). + +See the troubleshooting guide: [nbdcopy fails during disk copy (often DNS resolution)](../../guides/troubleshooting/nbdcopy-fails-after-vm-moved-esxi-host/). + +### What do when virt-v2v fails with `rename: /sysroot/etc/resolv.conf ... Operation not permitted`? + +The conversion fails because `/etc/resolv.conf` is marked **immutable** inside the source VM. `virt-v2v` cannot rename or replace immutable files during guest filesystem conversion. + +**Quick fix**: Inside the source VM, remove the immutable attribute before migrating: + +```bash +chattr -i /etc/resolv.conf +``` + +For the full symptom description, root cause analysis, and verification steps, see: [virt-v2v fails: rename /sysroot/etc/resolv.conf Operation not permitted](../../guides/troubleshooting/troubleshooting/#virt-v2v-fails-rename-sysrootetcresolvconf-operation-not-permitted) + +### How does Vjailbreak handle flavors of the vm in the target openstack environment? + +vJailbreak provides users the flexibility to assign desired OpenStack flavors to virtual machines during the migration setup. If the user specifies a flavor in the migration form, vJailbreak will honor that choice during provisioning on the target OpenStack environment. When migrating via CLI/kubectl, the same explicit choice can be made by setting `spec.targetFlavorId` on the VM's `VMwareMachine` custom resource — see [Explicitly select the target OpenStack flavor](../../guides/CLI-API/migrating_using_cli_and_kubectl/#optional-explicitly-select-the-target-openstack-flavor). + +If no flavor is explicitly chosen, vJailbreak automatically selects the most appropriate flavor based on the VM's resource requirements (We always try to find the exact match if not the next best match). In cases where no suitable flavor is found, the UI will display a warning. If the user proceeds despite the warning, the migration will fail with a clear error message indicating that a compatible flavor could not be found. + +#### Hotplug Flavor Support + +vJailbreak supports migrating VMs to OpenStack flavors that have **hotplug** CPU and RAM enabled. Hotplug allows live resize of vCPUs and memory without powering off the VM after migration. + +To use hotplug after migration: + +1. **On Platform9 Private Cloud Director (PCD)**: a hotplug base flavor named `hotplug` is available by default. While triggering the migration, simply select the `hotplug` flavor for the VMs that should support live resize. + +2. **On other OpenStack environments**: ask your administrator to create or identify a flavor with hotplug-enabled extra specs, and select it in the migration form. Example: + + ```bash + openstack flavor set \ + --property hw:cpu_policy=mixed \ + --property hw:cpu_max_vcpus= + ``` + +3. After migration completes, resize the VM to add or remove vCPUs and RAM without a reboot. + +When a hotplug base flavor (0 vCPU, 0 RAM — such as PCD's default `hotplug` flavor) is assigned, vJailbreak sets the VM's hotplug metadata at creation time: the current values (`HOTPLUG_CPU`, `HOTPLUG_MEMORY`) match the source VM, and the maximum values (`HOTPLUG_CPU_MAX`, `HOTPLUG_MEMORY_MAX`) are set to **twice** the source VM's vCPU and memory. This gives every migrated VM room to live-resize up to 2x its original size. Hotplug metadata cannot be changed after the VM is created. + +:::note +Standard flavors without hotplug extra specs require a VM power-off for resize. See [Known Limitations: Hotplug Flavor Requirements](../../reference/known-limitations/#hotplug-flavor-requirements) for details on flavor prerequisites and the metadata reference. +::: + +### Can vJailbreak migrate VMs running Docker Engine? +Yes, vJailbreak can migrate VMs running Docker Engine without any issues. vJailbreak performs VM-level migration and is agnostic to the workloads running inside the VM. Docker Engine is simply software running on the guest operating system, and the migration process handles it like any other application. + +### Can vJailbreak migrate VMs that are part of a Kubernetes cluster? +Yes, you can migrate VMs that are part of a Kubernetes cluster. However, it's important to understand that vJailbreak operates purely as a VM migration tool and has no awareness of Kubernetes components or distributed applications running on the VM. + +Users are responsible for taking necessary steps to ensure no disruption to applications and the distributed architecture of Kubernetes. This may include draining nodes, managing pod scheduling, and coordinating the migration with cluster operations. vJailbreak is not responsible for managing any workload-specific concerns. + +### Can vJailbreak migrate Kubernetes Persistent Volume Claims (PVCs)? +No, vJailbreak does not migrate PVCs. PVCs are Kubernetes constructs managed by CSI drivers and storage backends. vJailbreak has no visibility into these workload-level abstractions. + +vJailbreak migrates virtual machines along with the disks that are **currently attached** to those VMs at the time of migration. Any storage managed by Kubernetes (such as PVCs) must be handled separately using Kubernetes-native tools or storage migration solutions. + +### Can vJailbreak migrate VMs running Docker Swarm clusters? +Yes, vJailbreak can migrate VMs that are part of a Docker Swarm cluster. However, the same principles apply as with any distributed workload: vJailbreak performs VM-level migration and does not manage workload-specific concerns. + +Users must take appropriate precautions for Docker Swarm, such as draining nodes, managing service placement, and ensuring cluster quorum is maintained during migration. vJailbreak does not inspect or manage what is running inside the VM. + + +### How do I migrate a Windows VM with Group Policy (GPO) applied? + +GPO settings can block driver injection and registry changes that vJailbreak performs during conversion. The migration may succeed but produce a VM that fails to boot or has missing drivers. + +See the [GPO Migration Guide](../../guides/how-to/gpo_migration/) for steps to temporarily disable or scope down GPO before migration. + +### How do I migrate a VM with vTPM (Virtual Trusted Platform Module) enabled? + +VMs with vTPM and Virtualization Based Security (VBS) enabled require special handling. You must temporarily disable vTPM on the source VM before migration, then re-enable it on the destination VM post-migration. + +See the [vTPM Migration Guide](../../guides/how-to/vtpm_migration/) for step-by-step instructions. diff --git a/docs/src/content/docs/introduction/getting_started.mdx b/docs/src/content/docs/introduction/getting_started.mdx new file mode 100644 index 0000000..11f32af --- /dev/null +++ b/docs/src/content/docs/introduction/getting_started.mdx @@ -0,0 +1,323 @@ +--- +title: Getting Started +description: Usage +--- + +vJailbreak works by running itself as a VM on the target OpenStack cloud. It connects remotely +to the VMware vSphere environment to perform the migration. vJailbreak supports multiple storage +copy methods: the **Standard** copy method uses the VMware VDDK library, while **vJailbreak +Accelerated Copy** and **Storage-Accelerated Copy** do not require VDDK. These are +the recommended methods when VDDK is unavailable. + +It also uses the OpenStack SDK to interact with the OpenStack environment and perform the necessary +provisioning operations including creation of volumes, VMs. + +### Network and access requirements +Ensure that your vJailbreak VM can communicate with your OpenStack and VMware environments. This includes any setup required for VPNs, etc. + +:::note[vJailbreak Accelerated Copy] +When using the **vJailbreak Accelerated Copy** data copy method, the onboarded Proxy VM must accept the following inbound TCP traffic from the vJailbreak VM: +- **10809–11808** — used by `qemu-nbd` to expose attached disks (one port per disk copied in parallel) +- **22** — SSH, used by vJailbreak to control the Proxy VM +::: + +Further details can be found in [Prerequisites](../prerequisites/). + +### Download vJailbreak Image + +You can download the vJailbreak image using one of the following methods: + +#### Option 1: Using ORAS + +Download and install [ORAS](https://oras.land/docs/installation), a toolkit to download the qcow2 image of vJailbreak. Then, download the latest version of the vJailbreak image with the following command: +```shell +oras pull quay.io/platform9/vjailbreak:v0.4.10 +``` + +For older versions, download the vJailbreak image with the following command: +```shell +oras pull quay.io/platform9/vjailbreak: +``` + +These will download the vJailbreak qcow2 folder containing the image locally in the current directory named `vjailbreak_qcow2/vjailbreak-image.qcow2`. + +#### Option 2: Direct Download from S3 + +For the latest version, download directly using wget: +```shell +wget https://vjailbreak.s3.us-west-2.amazonaws.com/releases/latest/vjailbreak.qcow2 +``` + +For older versions, download directly using wget: +```shell +wget https://vjailbreak.s3.us-west-2.amazonaws.com/releases//vjailbreak.qcow2 +``` + +These will download the vJailbreak qcow2 image locally in the same directory named `vjailbreak.qcow2`. + +Note: This direct download method is supported starting from v0.3.2 and later versions. + +### Upload image to OpenStack +These example instructions are for any version of [Private Cloud Director](https://platform9.com/private-cloud-director/) - Platform9 hosted, self-hosted, or [Community Edition](https://platform9.com/docs/private-cloud-director/private-cloud-director/getting-started-with-community-edition) - but can be adapted for any OpenStack-compliant cloud. +- Follow the instructions in _Private Cloud Director > Images > Import with CLI_ to upload the image from the command line. +- Upload the vJailbreak qcow2 image to your image library. +```shell +openstack image create --os-interface admin --insecure --container-format bare --disk-format qcow2 --file vjailbreak-image.qcow2 +``` +- Set the disk bus to virtio-scsi on the uploaded image. During migrations, vJailbreak attaches the target volumes to itself for conversion; with the default virtio-blk bus each attached volume consumes a PCI slot, and migrating VMs with many disks can fail with a `No more available PCI slots` error. +With virtio-scsi, all volumes share a single controller. See [Known Limitations](../../reference/known-limitations/#pci-slot-exhaustion-when-attaching-disks-with-virtio-blk). +```shell +openstack image set --property hw_disk_bus=scsi --property hw_scsi_model=virtio-scsi vjailbreak-image.qcow2 +``` + +### Create vJailbreak VM +- Deploy a new VM from the uploaded image, choosing the _m1.xlarge.vol_ flavor (use larger flavor for larger VM migration). +- Choose a network that can reach your VMware vCenter environment. +- Assign a network security group that allows inbound and outbound traffic. + +### Network Initialization + +vJailbreak requires a valid IP address and network route to initialize properly. The installation behavior differs based on your network configuration: + +**Standard DHCP Networks** + +In most PCD deployments, the VM receives an IP address via DHCP immediately at boot. The vJailbreak installation proceeds automatically and the UI becomes accessible within a few minutes at `http:///`. + +**L2-Only Networks** + +In environments where IP addresses are assigned manually after VM deployment (L2-only networks without DHCP), the vJailbreak installation follows a specific sequence. This section provides a detailed walkthrough of what to expect and how to configure the VM. + +#### Step 1: VM Boot and Network Wait State + +When the vJailbreak VM starts in an L2-only network, the installation script detects that no IP address is available and enters a waiting state. You will see the following behavior in the console: + +1. The VM boots and initializes basic services +2. The script sets a default password for the `ubuntu` user +3. K3s master setup begins but pauses waiting for network availability +4. The console displays repeated messages indicating it is waiting for network configuration + +**Console output during wait state:** +``` +[2026-03-17 10:07:59] IS_MASTER: true +[2026-03-17 10:07:59] MASTER_IP: +[2026-03-17 10:07:59] K3S_TOKEN: +[2026-03-17 10:07:59] INSTALL_K3S_EXEC: server --secrets-encryption +[2026-03-17 10:07:59] Setting default password for ubuntu user... +[2026-03-17 10:07:59] Default password set for ubuntu user. User will need to change it on first login +[2026-03-17 10:07:59] Setting up K3s Master... +[2026-03-17 10:07:59] Waiting for network availability... +[2026-03-17 10:07:59] Waiting for network: missing default route and global IPv4 address... +[2026-03-17 10:08:59] Waiting for network: missing default route and global IPv4 address... +[2026-03-17 10:09:59] Waiting for network: missing default route and global IPv4 address... +``` + +The script checks every minute for: +- A non-loopback IPv4 address assigned to a network interface +- A default route configured in the routing table + +**Note:** The VM is fully accessible via console during this waiting period. You can log in and configure networking while the script waits. + +#### Step 2: Assign an IP Address + +You can assign an IP address using one of the following methods: + +**Option A: Using DHCP Client** + +If your network has a DHCP server available (but wasn't configured at boot), you can request an IP: + +```bash +# Check current network interface status +ip a + +# Request IP via DHCP on the primary interface (commonly ens3 or enp0s3) +dhclient ens3 +``` + +After running `dhclient`, verify the IP assignment: +```bash +ip a +``` + +You should see output similar to: +``` +2: ens3: mtu 1500 qdisc fq_codel state UP group default qlen 1000 + link/ether fa:16:3e:50:e9:85 brd ff:ff:ff:ff:ff:ff + altname enp0s3 + inet 10.96.9.142/20 brd 10.96.15.255 scope global dynamic ens3 + valid_lft 8631sec preferred_lft 8631sec +``` + +**Option B: Static IP Configuration** + +For environments without DHCP, configure a static IP address: + +```bash +# Assign static IP (replace with your network details) +sudo ip addr add 192.168.1.100/24 dev ens3 + +# Configure default gateway +sudo ip route add default via 192.168.1.1 + +# Verify configuration +ip a +ip route +``` + +**Important:** Ensure the IP address and gateway are appropriate for your network. Contact your network administrator if you're unsure about the correct values. + +#### Step 3: Automatic Installation Proceeds + +Once the network is configured, the installation script automatically detects the change and proceeds with the setup. You can monitor progress by tailing the installation log: + +```bash +tail -f /var/log/pf9-install.log +``` + +You will see the script transition from waiting to active installation: +``` +[2026-03-17 11:08:00] Waiting for network: missing default route and global IPv4 address... +[2026-03-17 11:09:01] Network detected. Default route and global IPv4 address available. +[2026-03-17 11:09:09] K3s is ready. +[2026-03-17 11:09:10] Loading all the images in /etc/pf9/images... +[2026-03-17 11:10:38] Applying kube-prometheus manifests... +[2026-03-17 11:10:50] K3s master setup completed +[2026-03-17 11:10:51] Rsync daemon started successfully. +[2026-03-17 11:10:51] Config map created successfully. +[2026-03-17 11:10:51] Installing cert-manager +[2026-03-17 11:10:53] Waiting for cert-manager deployments to become available +[2026-03-17 11:11:04] removing the cron job +``` + +The installation performs the following steps automatically: +1. **K3s Initialization** - Kubernetes cluster setup completes +2. **Image Loading** - Container images are loaded from `/etc/pf9/images` +3. **Prometheus Setup** - Monitoring stack is deployed +4. **Rsync Daemon** - File synchronization service starts +5. **Cert-Manager** - TLS certificate management is installed +6. **Cleanup** - Temporary cron jobs are removed + +#### Step 4: Access vJailbreak + +Once installation completes (typically 2-5 minutes after network configuration), you can access vJailbreak: + +- **UI Access:** `http:///` +- **SSH Access:** `ssh ubuntu@` (default password: `password`) + +#### Troubleshooting L2-Only Network Setup + +| Issue | Solution | +|-------|----------| +| Script not detecting IP | Ensure both IP address AND default route are configured | +| Installation stuck after IP assignment | Check `/var/log/pf9-install.log` for errors | +| Cannot reach UI after installation | Verify security groups allow inbound traffic on port 80 | +| Network interface not visible | Check VM network attachment in OpenStack dashboard | + +**Verifying Network Readiness:** +```bash +# Check IP assignment +ip addr show | grep "inet " + +# Check default route +ip route | grep default + +# Both should return valid entries for installation to proceed +``` + +### Configure VM with cloud-init +Cloud-init can be used during VM creation to automate initial configuration including setting passwords and configuring /etc/hosts entries for DNS resolution. +- When creating the VM, use a cloud-init configuration script to: + - Set a password for the ubuntu user + - Add static DNS entries to /etc/hosts + + ```yaml + #cloud-config + password: your-secure-password + chpasswd: { expire: False } + ssh_pwauth: True + + write_files: + - path: /etc/hosts.append + append: true + content: | + # VMware and OpenStack endpoints + 192.168.1.100 vcenter.example.com + 192.168.2.100 openstack.example.com + + runcmd: + - cat /etc/hosts.append >> /etc/hosts + ``` +- You can provide this cloud-init configuration when creating the VM through the OpenStack dashboard or CLI. + +**Note:** If you do not set a password for the `ubuntu` user using cloud-init, the default password for the `ubuntu` user will be set to "password." After the first login, you will be prompted to change the password and if the password is set using cloud-init you will not be prompted to change the password at first login. + + + +### Copy VDDK Libraries (Standard Copy Only) + +VDDK is required only if you plan to use the **Standard** storage copy method. If you are using +**vJailbreak Accelerated Copy** or **Storage-Accelerated Copy**, skip this step; neither method +requires VDDK. + +:::caution[VDDK currently unavailable] +VMware's public VDDK download pages are currently unavailable. If you do not already have a VDDK +package, use **vJailbreak Accelerated Copy** or **Storage-Accelerated Copy** instead. Both work +without VDDK. Note that vJailbreak Accelerated Copy supports **cold migration only** (source VM +must be powered off before copy). +::: + +If you have a VDDK 8.0.x package, copy it into `/home/ubuntu` of the vJailbreak VM and untar it +to a folder named `vmware-vix-disklib-distrib`: + +```shell +cd /home/ubuntu +tar -xvf VMware-vix-disklib-8.0.3-*.x86_64.tar.gz +# This will create the vmware-vix-disklib-distrib directory +``` + +### Configure DNS Resolution +Proper DNS resolution for your VMware and OpenStack URLs is required for vJailbreak to function correctly. You can either configure this during VM creation with cloud-init (as shown above) or modify the configuration after VM deployment. + +**Important:** DNS resolution for all ESXi hosts must be properly configured in your environment. This is specifically required during the VM copy phase of migration. Without proper DNS resolution for ESXi hosts, the migration process may fail. + + +- **Static Entries**: If you didn't configure `/etc/hosts` entries during VM creation with cloud-init, you can add them manually later. + ```shell + # Example manual addition to /etc/hosts + sudo sh -c 'echo "192.168.1.100 vcenter.example.com" >> /etc/hosts' + sudo sh -c 'echo "192.168.2.100 openstack.example.com" >> /etc/hosts' + + # ESXi hosts entries (required for VM copy phase) + sudo sh -c 'echo "192.168.1.101 esxi01.example.com esxi01" >> /etc/hosts' + sudo sh -c 'echo "192.168.1.102 esxi02.example.com esxi02" >> /etc/hosts' + ``` + Once the `/etc/hosts` file is modified execute below commands to apply the changes. + ``` + kubectl -n migration-system rollout restart deployment migration-controller-manager + ``` + +- **DNS Configuration**: If modifying `/etc/resolv.conf` to use DNS servers instead of static entries, restart the same `migration-controller-manager` deployment as shown above to apply the changes. + +### Initial Access Steps + + - **SSH Access** + - Username: ubuntu + - Password: password + - Note: After you SSH, you will be prompted to change the password. + - Example: + ```bash + ssh ubuntu@ + ``` + + - **UI Access** + - URL: `http:///` + - Username: admin + - Password: password + +### Launch vJailbreak +- Connect to the vJailbreak UI using the IP address assigned during VM creation. +- Add VMware and OpenStack credentials. See more here [Credential Management](../../concepts/credential-management/). +- Select the VMs you wish to migrate and complete the rest of the migration form. +- Migrate your VMs. + +### Scaling vJailbreak +Read more about [scaling vJailbreak](../../guides/how-to/scaling/). diff --git a/docs/src/content/docs/introduction/prerequisites.md b/docs/src/content/docs/introduction/prerequisites.md new file mode 100644 index 0000000..567a784 --- /dev/null +++ b/docs/src/content/docs/introduction/prerequisites.md @@ -0,0 +1,197 @@ +--- +title: Prerequisites +description: prerequisites for vJailbreak +--- + +For frequently asked questions, see [FAQ](../faq/). + +### VDDK requirements + +VDDK is required only if you plan to use the **Standard** storage copy method. +**vJailbreak Accelerated Copy** and **Storage-Accelerated Copy** do not require VDDK. + +:::note +VMware's public VDDK download pages are currently unavailable. If you do not already have a VDDK +package, use vJailbreak Accelerated Copy or Storage-Accelerated Copy instead. Both work without +VDDK. Note that vJailbreak Accelerated Copy supports **cold migration only**. +::: + +### What access do I need for my vCenter user to be able to perform this migration? + +The required privileges depend on which features you use. The base set below is required for all migrations. Additional privileges are listed separately for vJailbreak Accelerated Copy and OVA-based Proxy VM deployment. + +#### Base Privileges (all migrations) + +| Privilege | Description | +| --- | --- | +| `Virtual machine.Interaction` privileges: | | +| `Virtual machine.Interaction.Power Off` | Allows powering off a powered-on virtual machine. This operation powers down the guest operating system. | +| `Virtual machine.Interaction.Power On` | Allows powering on a powered-off virtual machine and resuming a suspended virtual machine. | +| `Virtual machine.Config.ChangeTracking`| Allows enabling or disabling change tracking on a virtual machine. | +| `Virtual machine.Guest operating system management by VIX API` | Allows managing a virtual machine by the VMware VIX API. | +| `Virtual machine.Provisioning` | Note: All `Virtual machine.Provisioning` privileges are required. | +| `Virtual machine.Provisioning.Allow disk access` | Allows opening a disk on a virtual machine for random read and write access. Used mostly for remote disk mounting. | +| `Virtual machine.Provisioning.Allow file access` | Allows operations on files associated with a virtual machine, including VMX, disks, logs, and NVRAM. | +| `Virtual machine.Provisioning.Allow read-only disk access` | Allows opening a disk on a virtual machine for random read access. Used mostly for remote disk mounting. | +| `Virtual machine.Provisioning.Allow virtual machine download` | Allows read operations on files associated with a virtual machine, including VMX, disks, logs, and NVRAM. | +| `Virtual machine.Provisioning.Allow virtual machine files upload` | Allows write operations on files associated with a virtual machine, including VMX, disks, logs, and NVRAM. | +| `Virtual machine.Provisioning.Clone template` | Allows cloning of a template. | +| `Virtual machine.Provisioning.Clone virtual machine` | Allows cloning of an existing virtual machine and allocation of resources. | +| `Virtual machine.Provisioning.Create template from virtual machine` | Allows creation of a new template from a virtual machine. | +| `Virtual machine.Provisioning.Customize guest` | Allows customization of a virtual machine’s guest operating system without moving the virtual machine. | +| `Virtual machine.Provisioning.Deploy template` | Allows deployment of a virtual machine from a template. | +| `Virtual machine.Provisioning.Mark as template` | Allows marking an existing powered-off virtual machine as a template. | +| `Virtual machine.Provisioning.Mark as virtual machine` | Allows marking an existing template as a virtual machine. | +| `Virtual machine.Provisioning.Modify customization specification` | Allows creation, modification, or deletion of customization specifications. | +| `Virtual machine.Provisioning.Promote disks` | Allows promote operations on a virtual machine’s disks. | +| `Virtual machine.Provisioning.Read customization specifications` | Allows reading a customization specification. | +| `Virtual machine.Snapshot management` privileges: | | +| `Virtual machine.Snapshot management.Create snapshot` | Allows creation of a snapshot from the virtual machine’s current state. | +| `Virtual machine.Snapshot management.Remove Snapshot` | Allows removal of a snapshot from the snapshot history. | +| `Datastore` privileges: | | +| `Datastore.Browse datastore` | Allows exploring the contents of a datastore. | +| `Datastore.Low level file operations` | Allows performing low-level file operations - read, write, delete, and rename - in a datastore. | +| `Sessions` privileges: | | +| `Sessions.Validate session` | Allows verification of the validity of a session. | +| `Cryptographic` privileges: | | +| `Cryptographic.Decrypt` | Allows decryption of an encrypted virtual machine. | +| `Cryptographic.Direct access` | Allows access to encrypted resources. | + +### Additional privileges required for encrypted VMs + +| Privilege | Purpose | +|---|---| +| `Cryptographer.Access` | Base access to cryptographic operations on the VM | +| `Cryptographer.Decrypt` | Decrypt the VM's disks for read access during migration | + + +## Troubleshooting + +### `VixDiskLib_Open` token-retrieval failure + +**Symptom**: Migration fails during disk open with: + +``` +Error 1 (Unknown error): Unexpected error when trying to retrieve token for disk +Unable to locate appropriate transport mode +``` + +**Cause**: Missing `Cryptographer.Access` and/or `Cryptographer.Decrypt` privilege on the migration service account. This is the most common encrypted-VM permissions failure and does not surface as a permissions error in the message text. + +**Fix**: Verify the account role includes both `Cryptographer.Access` and `Cryptographer.Decrypt`. Re-check with `govc permissions.ls` against the target VM. + +#### Additional Privileges: vJailbreak Accelerated Copy Migrations + +Required when using the **vJailbreak Accelerated Copy** storage copy method (VMware hot-add). vJailbreak attaches snapshot disks from the source VM to a Proxy VM and detaches them after the NBD copy completes. The controller also automatically enables `disk.EnableUUID` on the Proxy VM if it is not already set. + +| Privilege | Description | +| --- | --- | +| `Virtual machine.Config.AddExistingDisk` | Allows attaching an existing VMDK (snapshot disk) to another virtual machine — used to attach source disks to the Proxy VM. | +| `Virtual machine.Config.RemoveDisk` | Allows removing a disk from a virtual machine — used to detach source disks from the Proxy VM after copy. | +| `Virtual machine.Config.AdvancedConfig` | Allows modifying VM extra config parameters — used to set `disk.EnableUUID = TRUE` on the Proxy VM. | + +#### Additional Privileges: OVA-Based Proxy VM Deployment + +Required only when using the **Deploy a new vJailbreak Proxy VM** option in the UI. These privileges are not needed if you register an existing VM as the Proxy VM. + +| Privilege | Description | +| --- | --- | +| `vApp.Import` | Allows importing an OVF/OVA package into vCenter — used to deploy the pre-built Proxy VM appliance. | +| `Datastore.AllocateSpace` | Allows allocating disk space on a datastore — used to create the Proxy VM disk files during OVA import. | +| `Network.Assign` | Allows assigning a network to a virtual machine or vApp — used to connect the Proxy VM to the selected portgroup. | +| `Resource.AssignVAppToPool` | Allows assigning a vApp to a resource pool — used to place the deployed Proxy VM in the target compute resource. | +| `Virtual machine.Inventory.Create` | Allows creating a virtual machine in the vCenter inventory — used to register the Proxy VM after OVA import. | +| `Virtual machine.Config.AdvancedConfig` | Allows modifying VM extra config parameters — used to set `disk.EnableUUID = TRUE` on the newly deployed Proxy VM. | + +### Understanding VMware NFC Performance Limitations + +vJailbreak uses nbdkit to transfer disk data from VMware ESXi hosts via the NFC (Network File Copy) protocol over port 902. It's important to understand the inherent performance characteristics and limitations of VMware's NFC protocol: + +#### NFC Protocol Characteristics + +- **Per-VMDK throughput limit**: NFC is limited to approximately **1 Gbps per VMDK** due to VMware's internal implementation +- **Single-threaded**: NFC operations are single-threaded, limiting performance to what a single thread can achieve +- **Encrypted by default**: NFC traffic is SSL-encrypted, which adds overhead (disabling SSL can improve speed by up to 20% but reduces security) +- **Synchronous operations**: NFC must complete READ/WRITE/CHECK operations sequentially before proceeding +- **Latency-aware throttling**: NFC will automatically throttle when network latency increases + +#### Impact on vJailbreak Migrations + +- **Per-disk transfer speed**: Each VMDK transfers at approximately 1 Gbps (125 MB/s), regardless of available network bandwidth +- **Network saturation**: Multiple parallel VM migrations can saturate network links (e.g., a 10 Gbps link can theoretically support ~10 concurrent VM migrations) +- **Migration time estimation**: Expect transfer times of approximately 8-9 minutes per 100 GB per VMDK + +#### Recommendations + +- Plan migration schedules accounting for the ~1 Gbps per-VMDK limitation +- For VMs with large single disks, migration time will be constrained by NFC throughput rather than network capacity +- Use parallel migrations across multiple VMs to better utilize available network bandwidth +- Monitor network utilization to optimize the number of concurrent migrations +- Consider scheduling large VM migrations during maintenance windows + + +**References:** +- [Veeam Forum: 1Gbit/s per VMDK Limit](https://forums.veeam.com/vmware-vsphere-f24/1gbit-s-per-vmdk-limit-t66468.html) +- [Broadcom KB: NFC Performance](https://knowledge.broadcom.com/external/article/307001/nfc-performance-is-slow.html) + +### What ports do I need to open for vJailbreak to work? +Please refer the following table for the required ports: + +| Port | Protocol | Source | Destination | Purpose | +| --- | --- | --- | --- | --- | +| 443 | TCP | PCD nodes | VMware vCenter API endpoint | VMware provider inventory

Disk transfer authentication | +| 443 | TCP | PCD nodes | VMware ESXi hosts | Disk transfer authentication | +| 902 | TCP | PCD nodes | VMware ESXi hosts | Disk transfer data copy via NFC protocol (see NFC limitations above) | +| 22 | TCP | vJailbreak VM | Proxy VM | vJailbreak Accelerated Copy only: SSH control of the Proxy VM | +| 10809–11808 | TCP | vJailbreak VM | Proxy VM | vJailbreak Accelerated Copy only: `qemu-nbd` disk transfer (one port per disk copied in parallel) | + + +### What network connectivity do I need for vJailbreak? + +During normal migration operations, vJailbreak only requires connectivity to vCenter, ESXi hosts, and the OpenStack API. The additional endpoints listed below are needed only when **installing or upgrading vJailbreak** itself. + +#### Required for Migration (always) + +The vJailbreak VM and any helper nodes must be able to reach: + +- **vCenter and ESXi hosts** — for VM inventory, disk transfer authentication, and NFC data copy (ports 443 and 902) +- **OpenStack API endpoints** — for creating volumes, networks, and VM resources at the destination +- **ICMP (ping) access to guest VM IPs** — for post-migration connectivity verification (health checks) +- **Health-check endpoints on migrated guest VMs** — over user-defined HTTP/HTTPS ports, if health checks are enabled + +#### Required for Installation and Upgrades Only + +The following endpoints are accessed during first-time installation or when upgrading vJailbreak. They are **not** required during normal migration operations: + +- **Virtio ISO download source** (for Windows driver injection): + - [https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso](https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso) +- **Cloud-init certificate endpoints** +- **External tooling sources**: + - [https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/main/bundle.yaml](https://raw.githubusercontent.com/prometheus-operator/prometheus-operator/main/bundle.yaml) + - [https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml](https://github.com/cert-manager/cert-manager/releases/download/v1.12.0/cert-manager.yaml) +- **K3s installation sources**: + - [https://get.k3s.io](https://get.k3s.io) + - [https://github.com/k3s-io/k3s](https://github.com/k3s-io/k3s) + - [https://update.k3s.io](https://update.k3s.io) + - [https://github.com/rancher/k3s-root](https://github.com/rancher/k3s-root) +- **Helm chart repository for NGINX ingress**: + - [https://kubernetes.github.io/ingress-nginx](https://kubernetes.github.io/ingress-nginx) +- **Container registries** — for K3s, vJailbreak components (controller, UI), Prometheus, Grafana, CoreDNS, NGINX ingress, exporters: + - [https://docker.io](https://docker.io) + - [https://ghcr.io](https://ghcr.io) + - [https://quay.io](https://quay.io) + - [https://registry.k8s.io](https://registry.k8s.io) + + + + +### Required Ingress Rules for Kubernetes Node with Kubelet, Metrics Server, and Prometheus + +| **Component** | **Port** | **Protocol** | **Source** | **Purpose** | +|--------------------|----------|-------------|------------|-------------| +| **Kubelet API** | 10250 | TCP | Control Plane / Prometheus | Health checks, logs, metrics | +| **Kubelet Read-Only (Optional)** | 10255 | TCP | Internal Only | Deprecated but might be used in some cases | +| **Metrics Server** | 4443 | TCP | Internal Cluster | K8s resource metrics (`kubectl top`) | +| **Prometheus** | 9090 | TCP | Internal Cluster / Monitoring Server | Prometheus UI and API | +| **Node Exporter** (if used) | 9100 | TCP | Prometheus | Node-level metrics | +| **Cadvisor (Optional)** | 4194 | TCP | Internal Cluster / Prometheus | Container metrics collection | \ No newline at end of file diff --git a/docs/src/content/docs/introduction/what_is_vjailbreak.md b/docs/src/content/docs/introduction/what_is_vjailbreak.md new file mode 100644 index 0000000..7654311 --- /dev/null +++ b/docs/src/content/docs/introduction/what_is_vjailbreak.md @@ -0,0 +1,36 @@ +--- +title: What is vJailbreak? +description: Introduction to vJailbreak +--- + +[vJailbreak](https://github.com/platform9/vjailbreak) is an open-source tool featuring a user-friendly interface designed to simplify and accelerate the migration of virtual machines (VMs) __from VMware vSphere__ environments to any [Platform9 Private Cloud Director](https://platform9.com/private-cloud-director/) OR any __OpenStack-compliant__ cloud. It eliminates the complexities of cross-platform VM migration, enabling you to modernize your infrastructure with minimal disruption and a streamlined, visual workflow. + +### How vJailbreak works + +vJailbreak's intuitive interface leverages the OpenStack & VMware SDKs to interact directly with both your VMware vSphere environment and your target OpenStack cloud. The UI guides you through these key steps: + +1. **Connection Setup:** Easily configure connections to your source VMware vSphere environment and your target OpenStack cloud. +2. **VM Selection:** Select the VMs you wish to migrate from your vSphere environment. +3. **Migration Planning:** Configure migration settings, such as target storage and network configurations, through interactive forms. +4. **Migration Execution:** Initiate and monitor the migration process with real-time progress updates. +5. **Post-Migration Validation:** Verify the successful migration and launch of your VMs in OpenStack. + +### Key features + +* **Intuitive User Interface:** Manage the entire migration process through a clear, easy-to-use graphical interface – no command-line expertise required. +* **Seamless vCenter Integration:** Easily connect to your VMware vCenter to manage and migrate VMs. +* **Effortless VM Selection:** Select the virtual machines you wish to migrate with just a few clicks. +* **Automated Disk Conversion:** VM disks are automatically converted from `vmdk` to `qcow2` format. +* **Driver and Device Installation:** Necessary virtual devices and drivers are installed to ensure smooth operation post-migration. +* **Post-Migration Health Checks:** Comprehensive health checks are performed to verify the success of the migration and the operational status of the VMs in the new environment. +### Key benefits +* **Reduced Migration Time:** Automate migration tasks and visualize progress, significantly reducing the time and effort compared to manual methods. +* **Minimized Downtime:** vJailbreak's efficient migration process helps minimize downtime for your critical workloads. +* **Cost-Effective Solution:** As an open-source tool, vJailbreak eliminates licensing costs. +* **Broad Compatibility:** Migrate VMs to any OpenStack cloud that adheres to standard OpenStack APIs. +* **Non-Disruptive Migration:** Perform migrations without impacting the operation of your source VMware environment. +* **Visual Progress Tracking:** Monitor the status of your migrations in real-time through the user interface. + +:::tip[Did you know?] +By leveraging vJailbreak, organizations can modernize their infrastructure with minimal disruption, ensuring a smooth transition to an OpenStack cloud. +::: \ No newline at end of file diff --git a/docs/src/content/docs/reference/compatibility.md b/docs/src/content/docs/reference/compatibility.md new file mode 100644 index 0000000..5dfe171 --- /dev/null +++ b/docs/src/content/docs/reference/compatibility.md @@ -0,0 +1,64 @@ +--- +title: vJailbreak Compatibility +description: A list of supported VMware and operating system versions. +--- + +Following is a list of systems vJailbreak has been validated with. Please reach out to us to report any issues or to add support for additional versions. + +## VMware + +* VMware vCenter Server 6.7 +* VMware vCenter Server 7.0 +* VMware vCenter Server 8.0 + +## Operating System + +The list of operating system is large and while we have attempted +to test some of them, there are still gaps specifically with the older +versions. This list is expected to grow over time and we will continue to +add support for additional versions. Verified implies it has been tested and converted vs expected means it has not been tested but is expected to work. + + +| Operating System | Platform | Verified | Expected | +| :--- | :--- | :--- | :--- | +| AlmaLinux | linux/amd64 | No | Yes | +| Amazon Linux 2 | linux/amd64 | No | Yes | +| CentOS 4/5 | linux/i386 | No | Yes | +| CentOS 4 | linux/amd64 | No | Yes | +| CentOS 5 | linux/amd64 | Yes | Yes | +| CentOS 6 | linux/amd64 | Yes | Yes | +| CentOS 7 | linux/amd64 | Yes | Yes | +| CentOS 8 | linux/amd64 | Yes | Yes | +| CentOS 9 | linux/amd64 | Yes | Yes | +| CentOS Stream10 | linux/amd64 | No | Yes | +| Debian GNU/Linux 8 (64-bit) | linux/amd64 | No | Yes | +| Debian 12 | linux/amd64 | Yes | Yes | +| FreeBSD 14 | bsd/amd64 | Yes | Yes | +| Microsoft Windows 11 | windows/amd64 | Yes | Yes | +| Microsoft Windows 11 Enterprise | windows/amd64 | Yes | Yes | +| Microsoft Windows Server 2012 | windows/amd64 | Yes | Yes | +| Microsoft Windows Server 2016 | windows/amd64 | Yes | Yes | +| Microsoft Windows Server 2019 | windows/amd64 | Yes | Yes | +| Microsoft Windows Server 2022 | windows/amd64 | Yes | Yes | +| Microsoft Windows Server 2025 | windows/amd64 | Yes | Yes | +| Oracle Linux 7 | linux/amd64 | No | Yes | +| Oracle Linux 8 | linux/amd64 | Yes | Yes | +| Red Hat Enterprise Linux 10 | linux/amd64 | Yes | Yes | +| Red Hat Enterprise Linux 8 | linux/amd64 | Yes | Yes | +| Red Hat Enterprise Linux 9 | linux/amd64 | Yes | Yes | +| Red Hat Enterprise Linux 7 | linux/amd64 | No | Yes | +| Red Hat Enterprise Linux 5 | linux/amd64 | No | Yes | +| Red Hat Enterprise Linux 4 | linux/amd64 | No | Yes | +| Rocky 8 | linux/amd64 | Yes | Yes | +| Rocky 9 | linux/amd64 | Yes | Yes | +| Rocky 10 | linux/amd64 | Yes | Yes | +| SUSE Linux Enterprise 15 | linux/amd64 | Yes | Yes | +| Ubuntu Linux 14 | linux/amd64 | Yes | Yes | +| Ubuntu Linux 15 | linux/amd64 | Yes | Yes | +| Ubuntu Linux 16 | linux/amd64 | Yes | Yes | +| Ubuntu Linux 17 | linux/amd64 | Yes | Yes | +| Ubuntu Linux 22.04 | linux/amd64 | Yes | Yes | +| Ubuntu Linux 24.04 | linux/amd64 | Yes | Yes | +| VMware Photon OS | linux/amd64 | No | No | + + diff --git a/docs/src/content/docs/reference/known-limitations.md b/docs/src/content/docs/reference/known-limitations.md new file mode 100644 index 0000000..693465a --- /dev/null +++ b/docs/src/content/docs/reference/known-limitations.md @@ -0,0 +1,293 @@ +--- +title: Known Limitations +description: Known limitations and unsupported configurations in vJailbreak +--- + +This page documents known limitations, unsupported configurations, and important caveats in vJailbreak. Review this page before planning a migration to avoid unexpected failures. + +## Windows Dynamic Disk (LDM) + +Windows VMs whose **system volume** sits on a **dynamic disk** (Logical Disk Manager / LDM) are supported, but they follow a dedicated migration path. `virt-v2v` cannot convert these guests, so vJailbreak skips conversion, brings the VM up on an emulated SATA controller, and waits at the **LDM Boot Verification** phase for you to move it to virtio. + +vJailbreak detects this automatically — there is nothing to select in the migration form. + +| Configuration | Result | +|---|---| +| Root: Basic, Data: LDM | Migrates normally — import LDM data disks in Windows post-migration | +| Root: LDM, Data: Basic | Supported via the SATA-first path — manual cutover required | +| Root: LDM, Data: LDM | Supported via the SATA-first path — manual cutover required | + +The following limitations apply when the system volume is on LDM: + +| Limitation | Detail | +|---|---| +| Conversion-time features do not run | **VMware Tools removal**, **network persistence** and **user firstboot scripts** are all performed by `virt-v2v` during conversion. Conversion is skipped, so these must be handled manually inside the guest. | +| VirtIO drivers must be pre-installed | Drivers cannot be injected offline into an LDM volume. Install the VirtIO guest tools on the **source** VM before migrating. | +| The SAN policy must be set beforehand | Without `san policy=onlineall`, Windows brings the migrated disks up offline and the LDM volume set is left broken. | +| The migration requires manual intervention | The migration pauses at **LDM Boot Verification** until you confirm the VM booted. There is no timeout, so the migration will not complete unattended. | + +See the full guide: [Windows Dynamic Disk (LDM) Migration](../../guides/how-to/windows-ldm-migration/). + +## Active Directory-Joined VMs + +### Domain Controllers + +Migrating Active Directory Domain Controller VMs is **strongly not recommended**. The core risk is specific to how vJailbreak works: `virt-v2v` performs a disk-level conversion and creates a new VM on a different hypervisor. **VM-GenerationID** — the hypervisor metadata that Windows Server 2012+ uses to detect unsafe restores — is not stored on disk and is not preserved through this process. The migrated DC starts with a new (or absent) VMGenID, which Windows AD treats as an unsafe restore/clone. + +What happens depends on the Windows version: + +- **Windows Server 2012 and later**: The lost VMGenID triggers Windows' built-in safeguards. The DC automatically resets its invocation ID and forces a non-authoritative resync against replication partners. The domain may recover if other DCs are reachable, but this is unreliable in production and is not a supported migration path. +- **Windows Server 2008 R2 and earlier** (no VMGenID support): A genuine **USN rollback** can occur. The domain silently stops accepting replication from the migrated DC, and the AD environment can diverge without obvious errors. This is difficult to detect and hard to recover from. + +In both cases the **source DC must be permanently removed from the domain** before or immediately after the migrated copy is brought online. Running both simultaneously on the same domain will corrupt AD. + +**Recommended approach** (from [Microsoft guidance](https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/detect-and-recover-from-usn-rollback)): + +1. **Provision a new DC** in the target OpenStack environment using standard AD promotion. +2. **Let AD replication populate it** from an existing domain controller. +3. **Decommission the source DC** via `dcpromo` or Server Manager once replication is verified complete. + +**If you must migrate a DC** (lab/test environments, single-DC setups with no alternative), take these precautions: + +- Cleanly shut down the source DC before migration — do not snapshot a running DC. +- Migrate only one DC at a time. +- After the migrated DC boots, verify replication health immediately: + ```cmd + repadmin /replsummary + dcdiag /test:replications + ``` +- Confirm time synchronization (Kerberos requires clocks within 5 minutes of each other). +- Verify DNS is resolving correctly for all domain members. +- Decommission the source DC immediately — never run the original and migrated DC on the same domain simultaneously. + +### Member Servers and Workstations + +Migrating domain-joined **member VMs** (non-DC servers and workstations) is generally safe. The machine account password is stored in the VM's own LSA secrets and is copied with the disk, so domain membership typically survives the migration intact. + +A few edge cases can cause domain authentication to fail post-migration: + +- **Kerberos clock skew**: If the migrated VM's clock is more than 5 minutes off from the domain controller, Kerberos authentication will fail. Sync the VM's clock immediately after boot. +- **DNS resolution failures**: The VM must be able to resolve the domain controller's name and locate AD SRV records. Verify DNS settings after migration. +- **Pre-existing stale computer account**: If the source VM had been offline for an extended period (typically 90+ days) before migration, the domain controller may have already invalidated its computer account. This is a pre-existing condition unrelated to the migration itself. + +If users see `The trust relationship between this workstation and the primary domain failed` after migration, run the following to reset the account: + +```powershell +# Option 1 — reset computer account password without rejoining +netdom resetpwd /server: /userd: /passwordd:* + +# Option 2 — rejoin the domain +Remove-Computer -WorkgroupName WORKGROUP -Force +Add-Computer -DomainName -Credential -Restart +``` + +## Persist Network: Windows Server 2012 and Below + +The **Persist source network interfaces** option does not work for Windows Server 2012 and earlier (including Windows Server 2008 R2 and Windows Server 2008). + +Network interface name persistence depends on PowerShell capabilities, the Windows registry structure for network adapters, and a compatible version of `pnputil`. These prerequisites are not met on Windows Server 2012 and earlier. + +**Workaround**: Manually reconfigure network interface names and static IP settings inside the VM after migration. + +## Assign IP and Persist Network Cannot Be Used Together + +The **Assign IP** and **Persist Network** (Persist source network interfaces) options are mutually exclusive. Enabling both simultaneously produces undefined behavior and the migration may not apply either setting correctly. + +**Rule**: Use one or the other — not both. + +- Use **Assign IP** when you need to set a specific IP address on the destination VM. +- Use **Persist Network** when you need to preserve the source VM's interface names and static routes. + +## Multi-IP Assignment Not Supported + +Only one IP address per network interface is supported in the **Assign IPs** field. The UI enforces this — the field accepts a single IP per interface. If multiple IPs are specified via CLI, the migration will fail. + +**Workaround**: Assign additional IPs manually inside the VM after migration, or use OpenStack port configuration to attach additional floating IPs post-migration. + +## VMware Tools Removal: Residual Artifacts + +The VMware Tools removal process performed by `virt-v2v` during migration may leave behind residual files and registry entries on the destination VM. + +These artifacts are typically harmless but may appear in application logs or security scans. + +For a full list of known residual artifacts and cleanup steps, see: [VMware Residual Artifacts](../../guides/troubleshooting/vmware_residual_artifacts/). + +## Multi-Boot VMs Not Supported + +vJailbreak does not support VMs with **multiple bootable operating systems** (multi-boot configurations). `virt-v2v` inspects only a single OS installation per VM and cannot convert multi-boot disk layouts. + +**Workaround**: Migrate each OS as a separate VM, or convert the disk to a single-boot configuration before migration. + +## SUSE Linux (SLES / SLED) with Legacy GRUB 0.97 + +Older SUSE-family VMs — **SLES**, **SLED**, and other **SUSE** distributions — that still boot with **legacy GRUB (0.97)** require special handling. These are typically BIOS VMs on a multi-disk layout, where the first boot stage sits in one disk's MBR while its second stage and `/boot` live on a separate disk. After migration to KVM, the virtual disks are re-numbered and no longer match the original VMware ordering, so GRUB cannot find its second stage and the VM fails to boot with `GRUB Error 21`. + +In such scenarios, we recommend upgrading to GRUB2. + +**Why we upgrade GRUB**: GRUB 0.97 is too old and fragile — it hard-codes disk numbers and block offsets that break the moment the hypervisor re-orders disks. `virt-v2v` also can't reconfigure GRUB 0.97 for KVM; it only manages GRUB2. + +NOTE: On these older SUSE releases GRUB2 ships only as an EFI build (no legacy-BIOS version), so upgrading GRUB forces a switch to UEFI. + +## RHEL 7 Guests Missing GRUB Compatibility Symlink + +Some RHEL 7.x guests are missing the `/boot/grub/grub.cfg` compatibility symlink that `grubby` (used internally by `virt-v2v-in-place`) expects to point at `/boot/grub2/grub.cfg`. GRUB2 itself is configured correctly — only this symlink is missing — and conversion fails with: + +```text +libguestfs error: command: +error opening /boot/grub/grub.cfg for read: +No such file or directory +``` + +**Workaround**: Verify and, if needed, recreate the symlink before migrating. See [virt-v2v-in-place fails on RHEL 7](../../guides/troubleshooting/troubleshooting/#virt-v2v-in-place-fails-on-rhel-7-missing-grub-compatibility-symlink) for details. + +## Hotplug Flavor Requirements + +OpenStack **hotplug** (live CPU/RAM resize without VM reboot) is supported post-migration, but only if the VM is migrated with a hotplug-capable flavor. + +To use hotplug after migration: + +1. **Platform9 Private Cloud Director (PCD)**: PCD provides a hotplug base flavor named `hotplug` by default. While triggering the migration in vJailbreak, select the `hotplug` flavor for the VMs that need live resize. + +2. **Other OpenStack environments**: ask your OpenStack admin to create a flavor with hotplug-enabled extra specs, for example: + + ```bash + openstack flavor set \ + --property hw:cpu_policy=mixed \ + --property hw:cpu_max_vcpus= \ + --property hw:mem_page_size=any + ``` + + Then assign this flavor in the vJailbreak migration form before starting the migration. +3. After migration, resize the VM in OpenStack using the hotplug capability. + +### Hotplug Metadata and Resize Headroom + +When a hotplug base flavor (0 vCPU, 0 RAM — such as PCD's default `hotplug` flavor) is assigned, vJailbreak creates the target VM with the following server metadata: + +| Metadata key | Value | +|---|---| +| `HOTPLUG_CPU` | Source VM's current vCPU count | +| `HOTPLUG_MEMORY` | Source VM's current memory (MB) | +| `HOTPLUG_CPU_MAX` | **2x** the source VM's vCPU count | +| `HOTPLUG_MEMORY_MAX` | **2x** the source VM's memory (MB) | + +The max keys define the ceiling for post-migration live resize. They are set to twice the source VM's size so the migrated VM has hotplug headroom out of the box — for example, a VM migrated with 2 vCPUs and 4096 MB RAM can be live-resized up to 4 vCPUs and 8192 MB RAM. + +:::caution +Hotplug metadata can only be set at VM creation time and cannot be modified afterward. To resize beyond the 2x ceiling, the VM must be recreated. +::: + +:::note +Standard flavors without hotplug extra specs will not support live resize. The VM must be powered off for a cold resize in that case. +::: + +## PCI Slot Exhaustion When Attaching Disks with virtio-blk + +During conversion, vJailbreak attaches the target volumes to the vJailbreak VM (or its agent VMs). If the vJailbreak image is uploaded without a disk bus setting, OpenStack uses the default **virtio-blk** bus, where every attached volume consumes its own PCI slot. Migrating VMs with many disks, or running many parallel migrations on one agent, Maximum 26 devices can be attached after which PCI slots will exhaust and volume attach fails with: + +```text +libvirt.libvirtError: internal error: No more available PCI slots +``` + +**Workaround**: Set the disk bus to **virtio-scsi** on the vJailbreak image before creating the vJailbreak VM. All attached volumes then share a single SCSI controller (one PCI slot, up to 256 devices): + +```bash +openstack image set \ + --property hw_disk_bus=scsi \ + --property hw_scsi_model=virtio-scsi \ + +``` + +:::note +The disk bus is fixed when the VM is created. If the vJailbreak VM is already deployed, recreate it from the updated image. Agent VMs created during scale up use the same image, so set these properties before scaling up. +::: + +See the full troubleshooting entry: [Disk attach fails during migration: No more available PCI slots](../../guides/troubleshooting/troubleshooting/#disk-attach-fails-during-migration-no-more-available-pci-slots). + +## Low Disk Space in the Source VM + +Before starting conversion, `virt-v2v` checks that each filesystem inside the **source VM** has sufficient free space. If any filesystem is too full, the conversion fails before it begins. + +Minimum free space required inside the source VM ([source: virt-v2v docs](https://libguestfs.org/virt-v2v.1.html)): + +| Filesystem | Minimum free space | +|---|---| +| Linux root (`/`) | 100 MB | +| Linux `/boot` | 50 MB (needed to rebuild initramfs) | +| Windows `C:` drive | 100 MB (virtio drivers and guest agents are copied in) | +| Any other mountable filesystem | 10 MB | + +Each filesystem must also have at least **100 free inodes**. + +**Workaround**: Before migrating, free up space inside the source VM on any full partitions. Check with `df -h` (Linux) or Disk Management (Windows). + +## Hot Migration Requires Virtual Hardware Version 7 or Newer + +vJailbreak **Hot migration** (**Copy live VMs, then power off**) relies on VMware **Changed Block Tracking (CBT)** to copy only changed disk blocks during the live sync phase. CBT is available only on VMs running **virtual hardware version 7 or newer** (VMware KB 1020128). + +VMs on older hardware versions (for example, version 4) do not expose the CBT property at all, so Hot migration cannot track changed blocks for them. + +**Symptom**: A Hot migration of a legacy-hardware VM fails at the CBT step. The reported error looks similar to: + +``` +CBT is not enabled on disk +``` + +**What to do** — choose one: + +1. **Use cold migration** (**Power off VMs, then copy**) for these VMs. Cold migration copies each disk once in full while the VM is powered off and does not use CBT, so it works on any hardware version. *(Recommended — requires no changes to the source VM.)* +2. **Upgrade the VM's virtual hardware version** to 7 or newer in vCenter, then use Hot migration if you need minimal downtime. Upgrading the hardware version requires a VM power-off and cannot be reversed — review VMware's documentation before proceeding. + +| VM virtual hardware version | Hot migration | Cold migration | +|---|---|---| +| 7 or newer | Supported | Supported | +| Below 7 (e.g., version 4) | Not supported — use cold migration | Supported | + +:::note +To check a VM's hardware version in vCenter, select the VM and look at **VM Hardware → Compatibility** (shown as "ESXi X.X and later (VM version N)"). +::: + +## vJailbreak Accelerated Copy + +The limitations below are specific to [vJailbreak Accelerated Copy](../../concepts/vjailbreak-accelerated-copy/). See its [full limitations list](../../concepts/vjailbreak-accelerated-copy/#limitations) for the remaining constraints. + +### Concurrent Disk Attach Can Fail + +When several migrations reach the disk-attach step at the same time on the same Proxy VM, vCenter does not always handle the simultaneous reconfigure tasks gracefully and rejects some attach requests, failing those migrations. This is a **transient race condition** — the migrations that attached first are unaffected and continue into the copy phase. + +**Workaround**: [Retry](../../guides/how-to/retry_failed_migration/) the failed migrations once the others have moved into the copy phase. To reduce the chance of the race, stagger migration start times or distribute migrations across additional Proxy VMs. + +See [Proxy VM disk attach fails when several migrations start together](../../guides/troubleshooting/troubleshooting/#proxy-vm-disk-attach-fails-when-several-migrations-start-together). + +### Proxy VM Must Use a PVSCSI Controller + +vJailbreak matches each attached snapshot disk to a block device inside the Proxy VM by disk UUID, which works only on the **VMware Paravirtual (PVSCSI)** controller. The Proxy VM's **SCSI controller 0** must be PVSCSI — LSI Logic SAS, LSI Logic Parallel, and BusLogic Parallel are not supported. Migrations using any other type fail with `could not identify block device for disk `. + +**Workaround**: Power off the Proxy VM and set **Edit Settings** → **SCSI controller 0** → **Change Type** → **VMware Paravirtual** before registering it in vJailbreak. + +See [Configure the SCSI Controller Type on the Proxy VM](../../concepts/vjailbreak-accelerated-copy/#configure-the-scsi-controller-type-on-the-proxy-vm) and [could not identify block device](../../guides/troubleshooting/troubleshooting/#vjailbreak-accelerated-copy-fails-could-not-identify-block-device). + +## Application Reboot During Migration + +Cold migration (**Power off VMs, then copy**) powers off the source VM before copying its disk. The destination VM boots fresh after migration completes. **Applications must tolerate a reboot** — any in-memory state, open transactions, or non-persistent connections will be lost. + +Hot migration (**Copy live VMs, then power off**) minimizes downtime but still requires a brief power-off during the final cutover phase to synchronize the last changed blocks. Applications should be tested for graceful handling of this cutover reboot. + +:::caution +Before migrating, verify that your application starts cleanly after a cold reboot. Applications that require manual intervention to restart (e.g., databases with crash-inconsistent state) should be cleanly shut down inside the VM before initiating cold migration. +::: + +## Retrying a Failed Migration + +The **Retry** action reopens a failed migration in the migration form so its configuration can be corrected. The following limitations apply. See [Retry a Failed Migration](../../guides/how-to/retry_failed_migration/) for the full workflow. + +| Limitation | Detail | +|---|---| +| VMs with RDM disks cannot be retried | Shared RDM disk state prevents an automatic retry. The Retry button is disabled for these migrations and the migration must be restarted manually. | +| A retry always produces a single-VM plan | Retrying one VM from a plan that covered several VMs moves that VM into a plan of its own. The remaining VMs stay in the original plan and are unaffected. | +| Bulk retry cannot change configuration | **Retry Selected** restarts each migration with its existing configuration. To change settings, retry the migration individually. | +| Credentials and source cluster are locked | A retry cannot change the VMware or OpenStack credentials or the source cluster. Create a new migration instead. | + +:::note +Changing the target PCD cluster during a retry clears the network and storage mappings, because mappings are specific to a cluster. Select new mappings before submitting. +::: diff --git a/docs/src/content/docs/reference/reference.md b/docs/src/content/docs/reference/reference.md new file mode 100644 index 0000000..04080d4 --- /dev/null +++ b/docs/src/content/docs/reference/reference.md @@ -0,0 +1,252 @@ +--- +title: vJailbreak CRD references +description: A helpful explaination of the Kubernetes custom resource definitions (CRD) that vJailbreak uses. +--- +The following custom resource definitions (CRD) are deployed in the same namespace as the Migration Controller pod. By default, the namespace is `migration-system`. + +## Credentials +### OpenStack +- OpenstackCreds use the variables from the openstack.rc file. All fields are required except `OS_INSECURE` +```yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: OpenstackCreds +metadata: + name: osc1 + namespace: migration-system +spec: + secretRef: + name: osc1-openstack-secret +--- +apiVersion: v1 +data: + OS_AUTH_URL: + OS_DOMAIN_NAME: + OS_INSECURE: + OS_PASSWORD: + OS_REGION_NAME: + OS_TENANT_NAME: + OS_USERNAME: +kind: Secret +metadata: + name: osc1-openstack-secret + namespace: migration-system +type: Opaque +``` +### VMware +- All fields in VMwareCreds are required. +```yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: VMwareCreds +metadata: + name: vmc1 + namespace: migration-system +spec: + secretRef: + name: vmc1-vmware-secret +--- +apiVersion: v1 +data: + VCENTER_HOST: + VCENTER_INSECURE: + VCENTER_PASSWORD: + VCENTER_USERNAME: +kind: Secret +metadata: + name: vmc1-vmware-secret + namespace: migration-system +type: Opaque +``` +## Network mapping +```yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: NetworkMapping +metadata: + name: nwmap1 + namespace: migration-system +spec: + networks: + - source: VM Network + target: vlan3002 + - source: VM Network 2 + target: vlan3003 +``` +## Datastore mapping +```yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: StorageMapping +metadata: + name: stmap1 + namespace: migration-system +spec: + storages: + - source: vcenter-datastore-1 + target: lvm + - source: vcenter-datastore-2 + target: ceph +``` +## VMwareMachine +- `VMwareMachine` represents a discovered VMware VM and is created automatically by vJailbreak when a `VMwareCreds` resource is reconciled; it is not created by hand. `spec.vms` is a read-only snapshot of the VM's properties in vCenter (name, CPU, memory, datastores, networks, etc.) used to populate other resources such as `StorageMapping` and `NetworkMapping`. +```yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: VMwareMachine +metadata: + name: vm-1 + namespace: migration-system +spec: + vms: + name: vm-1 + cpu: 4 + memory: 8192 + osFamily: linuxGuest + datastores: + - datastore-1 + networks: + - network-1 + targetFlavorId: "" # optional +``` +- `targetFlavorId`: Optional. The OpenStack flavor ID to use for this VM's target VM. Set this to explicitly pin the migration to a specific flavor instead of relying on vJailbreak's automatic best-match selection (matched only on vCPU/RAM, so it cannot distinguish between same-sized flavors with different tags or extra specs). Must be set before the corresponding `MigrationPlan` is created, since it is read once when the per-VM migration ConfigMap is generated. If left empty, vJailbreak selects the closest matching flavor based on `spec.vms.cpu` and `spec.vms.memory`. + +## MigrationTemplate +```yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: MigrationTemplate +metadata: + name: migrationtemplate-windows + namespace: migration-system +spec: + networkMapping: name_of_networkMapping + storageMapping: name_of_storageMapping + osFamily: windowsGuest/linuxGuest + source: + datacenter: name_of_datacenter + vmwareRef: name_of_VMwareCreds + destination: + openstackRef: name_of_OpenstackCreds + +``` +- `osFamily` is optional. If not provided, the `osFamily` is retrieved from vCenter. If it can't be automatically determined, migration will not proceed. +## MigrationBlueprint +A `MigrationBlueprint` is a saved, reusable migration configuration — what the UI calls a **Migration Template**. See [Migration Templates](../../guides/how-to/migration_templates/) for the workflow. + +It is not the same object as the `MigrationTemplate` above. A `MigrationTemplate` is created per migration and drives an actual migration; a `MigrationBlueprint` is only read by the UI to pre-fill the migration form. The migration controller never reads it, and it holds no status. + +```yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: MigrationBlueprint +metadata: + name: production-rhel-east + namespace: migration-system +spec: + displayName: Production RHEL · East + description: Cold copy into the east cluster + vmwareRef: name_of_VMwareCreds + vmwareClusterName: name_of_source_cluster + pcdRef: name_of_OpenstackCreds + targetPCDClusterName: name_of_target_PCD_cluster + networkMappings: + - source: VM Network + target: external-network + storageMappings: + - source: vmware-datastore + target: ceph + storageCopyMethod: normal + osFamily: windowsGuest/linuxGuest + migrationStrategy: + type: hot/cold + adminInitiatedCutOver: true/false + advancedOptions: + networkPersistence: true/false + securityGroups: + - default + serverGroup: name_of_server_group +``` + +- `displayName`: Required. The name shown in the Templates tab. The object's `metadata.name` is a sanitized form of it. +- Every other field is optional, so a partially configured form can still be saved as a template. +- `vmwareClusterName`, `targetPCDClusterName`: Cluster **names**, not IDs. The UI resolves them back to the form's cluster selections when the template is applied. +- `networkMappings`, `storageMappings`, `arrayCredsMappings`: Inline copies of the mapping pairs, not references to `NetworkMapping` or `StorageMapping` objects. This keeps a template intact when those per-migration objects are deleted. +- `storageCopyMethod`: One of `normal`, `vJailbreakAcceleratedCopy`, or `StorageAcceleratedCopy`. Defaults to `normal`. `proxyVMRef` applies to `vJailbreakAcceleratedCopy`; `arrayCredsMappings` applies to `StorageAcceleratedCopy`. +- The VM selection is deliberately absent — VMs are chosen each time the template is used. + +## MigrationPlan +```yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: MigrationPlan +metadata: + name: vm-migration-app1 + namespace: migration-system +spec: + migrationTemplate: migrationtemplate-windows + retry: true/false + advancedOptions: + granularVolumeTypes: + - newvoltype1 + granularNetworks: + - newnetworkname1 + - newnetworkname2 + granularPorts: + - + - + migrationStrategy: + type: hot/cold + dataCopyStart: 2024-08-27T17:30:25.230Z + vmCutoverStart: 2024-08-27T17:30:25.230Z + vmCutoverEnd: 2024-08-28T17:30:25.230Z + adminInitiatedCutOver: true/false + performHealthChecks: true/false + healthCheckPort: string + virtualMachines: + - - winserver2k12 + - winserver2k16 + - - winserver2k19 + - winserver2k22 +``` +- `retry`: Optional. Retries one failed migration in a migration plan once. Set to false after a migration has been retried. +- `advancedOptions`: This is an optional field for granular control over migration options. MigrationTemplate with mappings must still be present. These options override the ones in the template, if set. If you use these options, you must only have 1 VM present in the virtualMachines list. + - `granularVolumeTypes`: In case you wish to provide different volume types to disks of a VM when they are all on the same datastore, you can specify the volume type of each disk of your VM in order. You must define one volume type for one disk present on the VM + - `granularNetworks`: In case you wish to override the default network mapping for a VM, you can provide a list of OpenStack network names to use in for each NIC on the VM, in order. + - `granularPorts`: In case you wish to pre-create ports for a VM with certain configs and directly provide them to the target VM, you can define a list of port IDS to be used for each network on the VM. It will override options set in `granularNetworks`. +- `migrationStrategy`: This is an optional field + - `type`: + - `cold`: Cold indicates to power off VMs in migrationplan at the start of the migration. Quicker than hot. + - `hot`: Powers VM off just before cutover starts. Data copy occurs with the source VM powered on. May take longer. + - `dataCopyStart`: Optional. ISO 8601 timestamp indicating when to start data copy + - `vmCutoverStart`: Optional. ISO 8601 timestamp indicating when to start VM cutover + - `vmCutoverEnd`: Optional. ISO 8601 timestamp indicating the latest time by when VM cutover can start. If this time has been passed before the cutover can start, migration will fail. + - `adminInitiatedCutOver`: Set to true if you wish to manually trigger the cutover process. Default: `false` + - `performHealthChecks`: Set to false if you want to disable Ping and HTTP GET health check. Failing these checks does not clean up the targeted VM. Default: `false` + - `healthCheckPort`: Port to run the HTTP GET health check against. Default "443" +- `virtualmachines`: Specify names of VMs to migrate. In this example the batch of VMs `winserver2k12` and `winserver2k16` migrate in parallel. `winserver2k19` and `winserver2k22` will wait for the first 2 to complete successfully, and then start in parallel. You can use this notation to specify whether VMs should migrate sequentially or in parallel within a plan. + +## VjailbreakNode +vJailbreak can be scaled to perform multiple migrations in parallel by deploying additional `agents`, enabling greater efficiency and workload distribution. The VjailbreakNode Custom Resource Definition (CRD) streamlines the creation and management of these agents, ensuring seamless integration into the migration workflow. Each `VjailbreakNode` represents a VM that functions as an independent migration `agent`. These agents are dynamically added to the original `VjailbreakNode`, forming a cohesive cluster that enhances scalability, reliability, and overall migration performance. +```yaml +apiVersion: vjailbreak.k8s.pf9.io/v1alpha1 +kind: VjailbreakNode +metadata: + name: example-vjailbreak-node + namespace: migration-system +spec: + imageId: "your-openstack-image-id" # This ID is for the first vjailbreak VMimage. It auto-populates in the UI—do not delete it. + nodeRole: "worker" + openstackCreds: + name: "name" # Reference to your OpenstackCreds + namespace: "migration-system" + openstackFlavorId: "your-openstack-flavor-id" + ``` +This `VjailbreakNode` CRD defines a Kubernetes resource that provisions a VM in OpenStack to act as a migration agent. Below is a breakdown of each field: +- `metadata:` Metadata contains identifying details about the `VjailbreakNode`. + - `name: example-vjailbreak-node`: Specifies the name of this `VjailbreakNode` resource in Kubernetes. + - `namespace: migration-system`: Indicates the namespace where this resource is deployed within the Kubernetes cluster. + +The `spec` section defines the desired state of the `VjailbreakNode`. + +- `imageId: "your-openstack-image-id"`: This is the ID of the OpenStack image used to create the VM. + - **It must match the image ID used to create the initial vJailbreak VM**, ensuring compatibility across all migration agents. +- `nodeRole: "worker"`: Defines the role of the node. + - It should be set to `"worker"` as this node functions as a migration agent within the vJailbreak cluster. +- `openstackCreds:`: OpenstackCreds use the variables from the openstack.rc file. + - `name: "name"` → Refers to a `Secret` or `CustomResource` storing OpenStack authentication details. + - `namespace: "migration-system"` → The namespace where OpenStack credentials are stored. +- `openstackFlavorId: "your-openstack-flavor-id"`: Specifies the OpenStack flavor ID, which determines the VM's compute resources (CPU, RAM, disk size, etc.). + - The chosen flavor should align with the resource requirements for migration workloads. \ No newline at end of file diff --git a/docs/src/content/docs/release_docs/v0.4.10.md b/docs/src/content/docs/release_docs/v0.4.10.md new file mode 100644 index 0000000..f1a1b94 --- /dev/null +++ b/docs/src/content/docs/release_docs/v0.4.10.md @@ -0,0 +1,17 @@ +--- +title: v0.4.10 +description: Release Notes for v0.4.10 +--- + +## What's Changed +* Enabling other copy methods without vddk- #2350 by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2353 +* Adding directory or create by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2356 +* [2358] Add VDDK_REQUIRED configmap flag to gate mandatory VDDK upload redirect by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2359 +* [2363] Default migration form to vJailbreak Accelerated Copy (HotAdd) by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2362 +* Pre-release CRD generation for v0.4.10 by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2370 + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.4.9...v0.4.10 + +## Highlights +This is a quick release in response to VDDK unavailability. More details - https://platform9.com/blog/vddk-no-longer-available/ diff --git a/docs/src/content/docs/release_docs/v0.4.6.md b/docs/src/content/docs/release_docs/v0.4.6.md new file mode 100644 index 0000000..41d57b8 --- /dev/null +++ b/docs/src/content/docs/release_docs/v0.4.6.md @@ -0,0 +1,66 @@ +--- +title: v0.4.6 +description: Release Notes for v0.4.6 +--- + +## What's Changed +* [1822] Removed redundant field (AssignedIP) by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1899 +* [1907] Fix: fetch single VMwareMachine instead of entire list in migration detail query by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1903 +* [1241] Disable port security on migrated ports when no security group is selected while migration by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1892 +* #1822: Display All VM IP Addresses in Migration Details Page (Assigned & Preserved IPs) by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1911 +* [1897] Feat: Add controller logs button to topbar in UI by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1896 +* docs(CLAUDE.md): add unit test requirements for Claude-written code by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1917 +* [1830] Fix: Include RBAC-shared security groups in destination tenant security group list by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1910 +* [1925] ci: Shift nightly build trigger to 01:00 IST by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1924 +* [1920] Added warnings for selected VMs with missing IPs by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1918 +* [1913] Fix: Skip VMwareMachine reconciliation for migrated VMs by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1912 +* #973: Verify vCenter FQDN DNS resolution by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1928 +* [1931] feat: Add debug bundle collection with resources and logs for easier troubleshooting by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1929 +* Bug: Filter Flavor Selection on AZ and flavours without AZ by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1916 +* [1840] Added the ability to migrate the VMs with no interface by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1927 +* Updating the compute api version to support multi-attach volumes by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1932 +* [1921] feat: Show revalidation progress on Credentials page by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1919 +* [1890] agent dns config by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1937 +* [1888] refactor: Remove direct pod permissions by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1935 +* [1938] Support VMs with space in their name by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1936 +* [1933] Don't restart ui when listing users by @spai-p9 in https://github.com/platform9/vjailbreak/pull/1939 +* [1796] Optimising the copy changed blocks mechanism for faster sync by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1934 +* #587: UI refactor migration by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1960 +* #1940: Block v2v pod creation or disable start migration if vddk is not uploaded by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1969 +* #1873: Block security group selection during scaling L2 Agent by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1962 +* [1803] fix: Added retry for transient API errors by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1958 +* [1961] fix: Handle transient vCenter errors during VM power-off by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1973 +* [1610] Fix VMware Tools cleanup for Linux/SUSE VMs by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1968 +* [1482] Adding hot add proxy by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1964 +* [1978] Sles LVM Minitrd fix by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/1976 +* #1971: UI hot add proxy migration complete UI requirements by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1981 +* UI E2E testcases with mock data using playwright by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/1972 +* Fix cluster conversion issues by @geet-pf9 in https://github.com/platform9/vjailbreak/pull/1984 +* [1990] Fix boot device index parse failure due to mixed guestfish stderr/stdout by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1993 +* Hypervisor resmgr role Cluster Conversion by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1997 +* #1999: Security group warning should be visible for Use security group of primary vJB vm by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2000 +* [2004] Fix SLED OS detection and broaden SSH key format support by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2003 +* [2002] fix- Cant upload key Invalid key error for RSA key by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2006 +* [2009] Unified logs icons and aligned migration name across logs drawer and details by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2008 +* Update CRD for v0.4.6 by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2017 + + +## Highlights +**Hot Add Proxy**: Hot-Add Proxy is an advanced data copy method that attaches source VM disks directly to a dedicated Proxy VM and streams the data over NBD (Network Block Device) to the destination. Instead of copying data over the NFC protocol from ESXi. This feature is currently in beta, will be stabilized in the coming releases. + +**Migration Reliability**: + - VMs with no network interfaces can now be migrated successfully. + - Multi-attach volume types are now supported for migration. + +**Enhanced UX**: +- All IP addresses (assigned and preserved) are now shown on the Migration Details page in the UI. +- Controller logs (migration controller manager) are visible in the UI irrespective of any migrations triggered. +- A debug bundle collection button makes log and resource gathering easier for troubleshooting. All CRs related to that migration are now gathered for easier debugging. + + +## Known Limitations +- Migrations of VMs with grub version lower than 2 might face issues while booting post migration. + + + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.4.5...v0.4.6 diff --git a/docs/src/content/docs/release_docs/v0.4.7.md b/docs/src/content/docs/release_docs/v0.4.7.md new file mode 100644 index 0000000..2cdc936 --- /dev/null +++ b/docs/src/content/docs/release_docs/v0.4.7.md @@ -0,0 +1,66 @@ +--- +title: v0.4.7 +description: Release Notes for v0.4.7 +--- + +## What's Changed +* Fixed the missing --binding-profile '{"l2-port": true}' on ports created for VMs migrated onto L2-only networks in PCD by @spai-p9 in https://github.com/platform9/vjailbreak/pull/2015 +* [2007] Fix fstab rewrite incorrectly activating commented bind-mount entries by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2011 +* [1058] Configure NTP servers and system timezone for the vJailbreak VM directly from the UI by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/1742 +* [1915] Added persist source network interfaces as a global setting by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2025 +* Detect if we are using virtio-scsi controller for windows and pass --blockdriver for conversion by @spai-p9 in https://github.com/platform9/vjailbreak/pull/2027 +* [1812] Fix volume cleanup when CreateTargetInstance times out by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2026 +* [1975] Hot-Add Proxy VM UX improvements by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2023 +* [1996] upgrade core components with guest OS migration fixes by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2031 +* [1970] Add server group selection for agent scale-up to control placement affinity by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2032 +* [1979] VM selection refresh button now revalidates VMware credentials by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2034 +* [1872] Verify volume bootable state after SetBootable timeout by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2035 +* prebake proxy VM OVA into appliance image by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2038 +* Fix: Check hardware version and migration type only then check for cbt by @spai-p9 in https://github.com/platform9/vjailbreak/pull/2037 +* [1980] Enhance user experience with new UI by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2040 +* UI side changes for Hot add proxy improvements by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2030 +* Rename Firstboot-Scheduler.ps1 to 0-Firstboot-Scheduler.ps1 by @noaboa97 in https://github.com/platform9/vjailbreak/pull/1994 +* fix(v2v-helper): repair broken build on main by @valentin-pf9 in https://github.com/platform9/vjailbreak/pull/1945 +* [1750] UI for edit and retry by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2024 +* [2044] Consolidate edit-and-retry into single Retry action by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2021 +* #2047 : Need UI enhancement in hot add proxy changes by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2048 +* [2050] Optimize retry flow and add bulk retry by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2049 +* [2052] Fix Windows firstboot loop by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2056 +* [1915] Added persist source network interfaces as a global setting by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2058 +* [2054] fix(retry): prefilled IP overrides not shown in Assign IP dialog on retry form by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2059 +* [2062] fix - ssh keypair name missmatch by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2060 +* #2051: Converting disk shows copy disk inside the migration details page by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2063 +* #2067: Block/Grey out the already registered proxy vm, this could mismatch the secret by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2070 +* [2079] Fix ProxyVM showing Ready status while SSH validation is failing by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2080 +* #2068 #2078 #2069: VM is in conversion state but it shows Hot add transfer in UI and stuck in validating state by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2074 +* [2077] Fix: Reset to Defaults must skip NTP server & timezone while migration running by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2084 +* #2088: [UI] Windows VM should be blocked for selection as a proxy VM by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2089 +* [2093] feat: add debug-bundle API to vpwned by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2090 +* #2082: Debug logs, yamls are not present in logs fetched using log download button by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2092 +* Fix Host Entries tab: duplicate description text and low-visibility button by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2099 + +## New Contributors +* @noaboa97 made their first contribution in https://github.com/platform9/vjailbreak/pull/1994 +* @valentin-pf9 made their first contribution in https://github.com/platform9/vjailbreak/pull/1945 + +## Highlights +**Migration details page**: Added new page which shows in depth state monitoring for migration lifecycle. + +**Vjailbreak Proxy**: +* End-to-end UI for registering a Proxy VM (SSH key upload, validation status), selecting Hot-Add as the copy method in the migration form, and monitoring hot-add-specific migration phases +* Verification now auto-installs missing dependencies on the Proxy VM during validation, eliminating the need for manual pre-configuration before registering + +**Advanced Configuration Management** + * Configure **NTP servers** and **system timezone** for vJailbreak VMs directly from the UI. + * Introduced **server group selection** for agent scale-up, enabling better placement and affinity control. + +**Enhanced Retry Experience** + * Introduced a new Edit & Retry capability, allowing users to modify migration settings and retry failed migrations without recreating the migration. + * Simplified recovery with a unified Retry action and support for Bulk Retry, reducing manual effort. + + +## Known Limitations + +**Proxy VM OVA requires ESXi 8.0 U2 or higher**: The bundled OVA template uses virtual hardware version vmx-21, which is incompatible with older ESXi hosts. Attempting to deploy it on ESXi 7.x will fail with an "unsupported hardware family" error. For ESXi 7.x environments, the Proxy VM must be created and configured manually instead of using the OVA deploy option. + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.4.6...v0.4.7 diff --git a/docs/src/content/docs/release_docs/v0.4.8.md b/docs/src/content/docs/release_docs/v0.4.8.md new file mode 100644 index 0000000..ffe993d --- /dev/null +++ b/docs/src/content/docs/release_docs/v0.4.8.md @@ -0,0 +1,85 @@ +--- +title: v0.4.8 +description: Release Notes for v0.4.8 +--- + +## What's Changed +* #2067: Block/Grey out the already registered proxy vm, this could mismatch the secret by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2105 +* [2083] Infer hotplug intent from assigned flavor by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2109 +* fix: security workflow fork PR comments + graphify git noise by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/2116 +* test(v2v-helper): repair stale mkinitrd LVM wrapper unit tests by @gtherond in https://github.com/platform9/vjailbreak/pull/2075 +* [2106] Fix wrong stage shown for Hot-Add proxy migrations by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2117 +* [2085] Block Persist source network interfaces when mapped network subnet doesn't match VM IPs by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2110 +* [2095] Serve debug bundle as .tar.gz archive instead of single .txt file by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2112 +* [2113] Improved subnet mismatch warning and Persist IP disabled warning by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2126 +* refactor: remove deprecated files and components, streamline package dependencies by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2114 +* Don't call openstackcreds per cluster to get tenant name by @spai-p9 in https://github.com/platform9/vjailbreak/pull/2057 +* #2087 : Add confirmation dialog for node reprovisioning by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2131 +* #2094: [UI] Update Controller Log Section Styling to Match Migration Details Logs by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2136 +* [2128] Show assigned flavor and disk size for VMs selected for migration by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2137 +* [1930] Fix CentOS 7 conversion failure from e2fsck bitmap errors by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2134 +* Delete associated agent nodes when OpenstackCreds is deleted by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/2119 +* #2125: Move Proxy VM Credentials Navigation from Credentials to Migration Menu by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2138 +* Refactor: ReservePortsForVM by @spai-p9 in https://github.com/platform9/vjailbreak/pull/2140 +* [2012] Fix SUSE GRUB Legacy device references post-migration by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2133 +* AI analysis of migration failures by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/1998 +* [1801] Twice HOTPLUG_CPU_MAX/HOTPLUG_MEMORY_MAX metadata so migrated VMs can be resized on PCD by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2127 +* [2167] fix(v2v-helper): Install vendored libnbd with libnbd-devel by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2166 +* When Ip is empty and fallback to dhcp true ask pcd to assign ip via dhcp by @spai-p9 in https://github.com/platform9/vjailbreak/pull/2153 +* [2161] fix(ui): Global Settings Save button silently dead by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2162 +* [428] Added ability to preserve tags, attributes, and custom metadata attached to the virtual machine by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2146 +* [2115] Fix IP address not populating on port creation due to MAC address case mismatch by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2160 +* [428] UI: Added ability to preserve tags, attributes, and custom metadata attached to the virtual machine by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2145 +* [2096] Default removeVMwareTools to true by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/2159 +* Make clean up both agents and vjb nodes to run as background and improve reliability by @spai-p9 in https://github.com/platform9/vjailbreak/pull/2155 +* Refactoring boot partition detection by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2168 +* Separate out nbd and virtv2v debug logs by @spai-p9 in https://github.com/platform9/vjailbreak/pull/2165 +https://github.com/platform9/vjailbreak/pull/2172 +* [2173] Remove beta tag from Storage Accelerated Copy by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2177 +* [2152] Add MigrationBlueprint CRD to support migration templates by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2158 +* [2175] UI patch call for proxy vm retry by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2181 +* [2176] Fix tags toggle needing two clicks to enable by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2183 +* [2184] Fix Migration Options toggles needing two clicks to enable by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2185 +* #2120: Add Support for Migration Templates and Saved Configurations by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2180 +* Configure guest network incase of ubuntu for fallback to dhcp case by @spai-p9 in https://github.com/platform9/vjailbreak/pull/2182 +* [2178] fix(migration): disable persist network interfaces when Preserve IP is off and mapped to diff network by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2189 +* Feature: Add error propogation to UI from debug logs for failures by @spai-p9 in https://github.com/platform9/vjailbreak/pull/2170 +* [2213] Fix migration stuck in ConvertingDisk after transient VM creation timeout by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/2192 +* [2143] unique port names for multi-NIC VMs using network+subnet+vmname by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/2147 +* [2191] Grid/list toggle wraps left instead of staying inline on Templates tooolbar by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2199 +* [2193] Prevent checkbox selection column from being hidden via Manage Columns by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2198 +* [2194] Fix: Converting Disk status should not show count of disks by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2200 +* [2202] Fix: AI Analysis key configuration redirect to correct settings tab by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2204 +* feat: add data-only migration mode (no VM creation) (#492) by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/2171 +* Support encrypted volume types in pcd. by @spai-p9 in https://github.com/platform9/vjailbreak/pull/2208 +* [2201] Show migration failure messages on the UI by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2205 +* [2203] Fix: Disable migration progress bar by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2210 +* #2209: Step check marks doesnt disable if user disable the option by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2215 +* [2214] Fix: Controller/pod log search returns zero results for lines that exist by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2221 +* [2217] Fix: Remove stale network/storage mappings when VMs are deselected by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2222 +* feat(dev): add Claude Code agent definitions for vjailbreak workflows by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/2223 +* [2195] Fix incorrect step timings on the migration detail page by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2225 +* [2213] Fix: Migration phase permanently frozen at Failed from transient kubelet events by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2229 +* [2234] Fix: remove VM selection from template creation and refactor pruning of mappings by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2233 +* [CI] modify pre-release actions to not skip builds by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/2239 + +## New Contributors +* @gtherond made their first contribution in https://github.com/platform9/vjailbreak/pull/2075 + +## Highlights + +* Introduced log analysis for failed migrations using Anthropic AI to help identify potential root causes and simplify troubleshooting. +* Added ability to preserve source vCenter VM tags, attributes, and custom metadata during migration. +* Promoted Storage Assisted Migration to General Availability (GA). +* Added support for Migration Templates and Saved Configurations to streamline repeated migration workflows. +* Added support for Data-Only migration mode, enabling data transfer without creating the destination VM. +* Added support for migrating KMS-encrypted source VMs. + +## Known Limitations + +* The **Data-Only** migration option is not preserved when a migration is retried using the **Retry** button. See [[Issue #2245](https://github.com/platform9/vjailbreak/issues/2245)] for details. +* Migrations of VMs running **RHEL 7.9** may intermittently encounter a disk index-related issue. Retrying the migration typically resolves the problem. +* When using the **Hotplug** flavor for migration, the destination VM's **Maximum CPU** and **Maximum Memory** values are set to twice the original VM's configured CPU and memory in vCenter. +* Migration of **SUSE Linux Enterprise Server (SLES)** and **SUSE Linux Enterprise Desktop (SLED)** systems using **Legacy GRUB 0.97** is not supported. + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.4.7...v0.4.8 diff --git a/docs/src/content/docs/release_docs/v0.4.9.md b/docs/src/content/docs/release_docs/v0.4.9.md new file mode 100644 index 0000000..d1cbecf --- /dev/null +++ b/docs/src/content/docs/release_docs/v0.4.9.md @@ -0,0 +1,40 @@ +--- +title: v0.4.9 +description: Release Notes for v0.4.9 +--- + +## What's Changed +* improve vjb debug skill and add security review skill by @OmkarDeshpande7 in https://github.com/platform9/vjailbreak/pull/2230 +* [2247] Fix: 403 Forbidden fetching migration ConfigMap on migration details page by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2253 +* [2179] fix(migration): recompute subnet-mismatch warning when IP changes after network mapping by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2190 +* #2211: UI: Filter not working by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2267 +* [2270] fix: report failed vmkfstools clones as failures by @spai-p9 in https://github.com/platform9/vjailbreak/pull/2272 +* [2245] Fix: Persist data-only, source tags and custom metadata when retrying a failed migration by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2264 +* [2246] Show data-only mode in migration details page policies by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2275 +* [2262] Warn before upgrade cleanup and gate Upgrade on cleanup by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2276 +* [2257] Fix migration failure when the guest root filesystem spans multiple disks by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2232 +* [2283] [Claude] Constitution v1.3.0 — migration form field parity + explicit UI K8s access by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2281 +* [2228] Cannot create cred with same name if previous attempt failed by @AbhijeetThakur in https://github.com/platform9/vjailbreak/pull/2249 +* [2279] fix(upgrade): server-side apply manifests so bound PVCs don't fail the CRD phase by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2284 +* [2278] Adding ability to migrate LDM VMs by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2277 +* [2286] Added vjailbreak-ai to the upgrade flow by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2285 +* [2289] LDM UI popup renaming by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2291 +* [2296] fix win2k12 troubleshooting mode by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2297 +* [2287] Adding extra phase for transition from sata to virtio by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2293 +* [2290] Adding disk detach path on keep sata option by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2295 +* [2288] marking success on keep sata path by @meghansh-pf9 in https://github.com/platform9/vjailbreak/pull/2294 +* [2279] fix(upgrade): keep the AI PVC out of 00crds.yaml by @sarika-pf9 in https://github.com/platform9/vjailbreak/pull/2299 + +## Highlights + +* Added support for Windows VMs with LDM system disk +* UI Retry related stability fixes +* Added support for Linux VMs with BTRFS + +## Known Limitations + +* Race condition with vjailbreak accelerated proxy - #2300 +* Multi-IP assignment limitation - #2163 +* Incorrect migration copy progress on UI - #2139 + +**Full Changelog**: https://github.com/platform9/vjailbreak/compare/v0.4.8...v0.4.9 diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css new file mode 100644 index 0000000..698c297 --- /dev/null +++ b/docs/src/styles/custom.css @@ -0,0 +1,454 @@ +/* Override default card grid layout */ +.card-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1.5rem; + padding: 0; +} + +/* Make all cards the same height */ +.card { + height: 100%; + margin: 0 !important; + transform: none !important; +} + +/* Remove stagger animation */ +.card-grid[data-stagger] .card { + animation: none !important; +} + +/* Import Geist font */ +@font-face { + font-family: 'Geist'; + src: url('https://assets.website-files.com/638d2ca7e839e538fc311594/638d2ca7e839e54527311772_Geist-Regular.woff2') format('woff2'), + url('https://assets.website-files.com/638d2ca7e839e538fc311594/638d2ca7e839e54527311772_Geist-Regular.woff') format('woff'); + font-weight: 400; + font-style: normal; +} + +@font-face { + font-family: 'Geist'; + src: url('https://assets.website-files.com/638d2ca7e839e538fc311594/638d2ca7e839e54527311773_Geist-Medium.woff2') format('woff2'), + url('https://assets.website-files.com/638d2ca7e839e538fc311594/638d2ca7e839e54527311773_Geist-Medium.woff') format('woff'); + font-weight: 500; + font-style: normal; +} + +/* Override Starlight's default fonts */ +:root { + --sl-font: 'Geist', -apple-system, BlinkMacSystemFont, sans-serif; +} + +@media (min-width: 1600px) { + :root { + --sl-content-width: 80rem; + } +} + +/* Specific element styling */ +h1, h2, h3, h4, h5, h6 { + font-family: var(--sl-font); + font-weight: 500; +} + +.hero-image { + width: 100%; /* This will make the image take the full width of its container */ + height: auto; /* Maintain aspect ratio */ + max-width: 1400px; /* Optional: to limit the image width to a max */ + margin: 0 auto; /* Centers the image */ +} + +.hero-image.large { + max-width: 1600px; /* Increase the maximum size for larger screens */ +} + +.hero h1 { + font-weight: 500; + letter-spacing: -0.02em; +} + +p, li { + font-family: var(--sl-font); + font-weight: 400; +} + +/* Platform9 theme colors */ +:root { + --p9-blue: #0091ff; /* Primary blue */ + --p9-dark-blue: #0d1b2a; /* Darker blue for backgrounds */ + --p9-light-blue: #61a0ff; /* Light blue for accents */ +} + + +@keyframes shimmer { + 0% { background-position: -200% center; } + 100% { background-position: 200% center; } +} + +/* Enhanced video container */ +.video-container { + position: relative; + z-index: 2; /* Higher than overlay */ + pointer-events: auto; /* Ensure clicks pass through */ +} + +.video-wrapper { + position: relative; + border-radius: 12px; + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15); + border: 1px solid rgba(97, 160, 255, 0.1); + overflow: hidden; + transform: translateY(0); + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.video-wrapper iframe { + position: relative; + z-index: 3; /* Highest z-index */ + width: 100%; + height: calc(100% - (10px)); /* Adjust height for padding if needed */ + border-radius :12px; /* Add border-radius to iframe for consistency with wrapper*/ + border : none; /* Remove default border on iframe*/ +} + +/* Enhanced action buttons */ +[data-has-hero] .action { + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +[data-has-hero] .action:hover { + transform: translateY(-2px); +} + +[data-has-hero] .action.primary { + background-image : linear-gradient(135deg, var(--p9-blue), var(--p9-light-blue)); + border-radius :8px; /* Add rounded corners to buttons*/ +} + +[data-has-hero] .action.minimal { + border-color : var(--p9-blue); +} + +/* CSS for sections in index page */ +/* Section styling */ +.section-title { + color : var(--sl-color-white); + font-size :2rem ; + margin :3rem auto ; /* Center the title with auto margins*/ + background-image : linear-gradient( + to right, + var(--sl-color-text) , + var(--p9-blue) , + var(--sl-color-text) + ); + -webkit-background-clip : text ; + background-clip : text ; + -webkit-text-fill-color : transparent ; +} + +/* Card styling */ +.card-grid[data-theme='light'] .card { + background-image : linear-gradient( + to bottom right, + rgba(255,255,255,.9), + rgba(240,245,250,.8) + ); + border :1px solid rgba(0,145,255,.1); +} + +.card-grid[data-theme='dark'] .card { + background-image : linear-gradient( + to bottom right, + rgba(13,27,42,.9), + rgba(27,38,59,.8) + ); + border :1px solid rgba(0,145,255,.2); +} + +.card { + transition :transform .2s ease ,box-shadow .2s ease ; + box-shadow :0px4px16px rgba(0 ,145 ,255 , .1); +} + +.card:hover { + transform :translateY(-4px); + box-shadow :0px8px24px rgba(0 ,145 ,255 , .15); +} + + +/* CSS for download button */ +.download-section { + margin :2rem auto ; + padding :1.5rem ; + background-image : linear-gradient( + to bottom right, + var(--sl-color-bg), + var(--sl-color-bg-sidebar) + ); + border-radius :12px ; + border :1px solid rgba(0 ,145 ,255 , .1); +} + +.download-content { + display :flex ; + align-items:center ; + gap :2rem ; + max-width :var(--sl-content-width); + margin :0 auto ; +} + +.download-section h3 { + font-size :1.2rem ; + margin :0 ; + color :var(--sl-color-text); +} + +.command-box { + position :relative ; + flex-grow :1 ; + margin :0 ; + padding :.75rem ; + background-color :var(--sl-color-bg-sidebar); +border-radius :8px ; +} + +.command-box pre { +margin :0 ; +padding-right :4rem ; /* Make space for copy button */ +} + +.copy-button { + position: absolute; + right: 0.75rem; + top: 50%; + transform: translateY(-50%); + background: var(--sl-color-text-accent); + color: white; + border: none; + border-radius: 4px; + padding: 0.3rem; + cursor: pointer; + font-size: 1.2rem; /* Icon size */ + transition: all 0.2s ease; + display: flex; + justify-content: center; + align-items: center; /* Aligns icon and text vertically */ + gap: 0.4rem; /* Adds space between the icon and the text */ +} + +.copy-button svg { + width: 1.2rem; /* Adjust icon size */ + height: 1.2rem; +} + +.copy-button:hover { + background: var(--p9-light-blue); + transform: translateY(-50%) scale(1.05); +} + + +.oras-link{ +font-size:.8rem ; +color: var(--sl-color-text-accent); +text-decoration:none ; +display:flex ; +align-items:center ; +gap:.3rem ; +white-space: nowrap ; +} + +.oras-link:hover{ +text-decoration: underline ; +color: var(--p9-light-blue); +} +/* Modal Overlay */ +.modal-overlay { + display: none; + justify-content: center; + align-items: center; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + opacity: 0; + transition: opacity 0.3s ease; +} + +/* Active State for Modal */ +.modal-overlay.active { + display: flex; + opacity: 1; +} + +/* Modal Container */ +.modal-container { + /*background-color: #fff;*/ + border-radius: 12px; + padding: 2rem; + max-width: 500px; + width: calc(100% - 2rem); + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: white; + padding: 20px; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); + border-radius: 8px; + z-index: 1000; /* Ensure it's on top of other elements */ +} + +/* Modal Header */ +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.modal-header h3 { + margin: 0; +} + +.modal-close { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; +} + +/* Input and Submit Button */ +input { + width: calc(100% - 2rem); + padding: .75rem; +} + +button[type='submit'] { + margin-top :1rem ; +color:white ; +background-image : linear-gradient(to bottom , var(--p9-blue),var(--p9-light-blue)); +} + +/* .dropdown { + position: relative; + display: inline-block; +} + +.dropdown-button { + background-color: #0077B6; + color: white; + padding: 10px 15px; + border: none; + cursor: pointer; +} + +.dropdown-menu { + display: none; + position: absolute; + background-color: #f9f9f9; + min-width: 160px; + box-shadow: 0px 8px 16px rgba(0, 0, 0, 0.2); + z-index: 1; +} + +.dropdown:hover .dropdown-menu { + display: block; +} + +.dropdown-item { + padding: 8px 12px; + text-decoration: none; + display: block; +} + +.dropdown-item:hover { + background-color: #ddd; +} */ + +.slack-invite { + position: relative; /* Needed for absolute positioning of close button */ +} + +.custom-icon-button { + background: none; + border: none; + cursor: pointer; +} + +#custom-form { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: white; + padding: 20px; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); + border-radius: 8px; + z-index: 1000; /* Ensure it's on top of other elements */ +} + +.close-button { + position: absolute; + top: 10px; + right: 10px; + font-size: 20px; + cursor: pointer; +} + +.rate-limit-warning { + display: none; + color: #dc3545; + padding: 0.5rem; + margin-top: 0.5rem; + border: 1px solid #dc3545; + border-radius: 4px; +} + + +.version-buttons { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-bottom: 1.5rem; +} + +.version-btn { + padding: 0.5rem 1rem; + font-size: inherit; + line-height: 1.2; + font-weight: 500; + text-align: center; + + border: 1px solid var(--sl-color-gray-5); + background-color: transparent; + color: var(--sl-color-text); + border-radius: 6px; + cursor: pointer; + + transform: translateY(0); + transition: all 0.2s ease-in-out; +} + +.version-btn:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + border-color: var(--sl-color-accent); +} + +.version-btn.active { + background-color: var(--sl-color-accent); + color: var(--sl-color-text-invert); + border-color: var(--sl-color-accent); + font-weight: 700; + box-shadow: 0 2px 4px rgba(0, 118, 255, 0.3); + transform: translateY(-1px); +} + +[data-theme='dark'] .version-btn.active { + background-color: #b3c7ff; + color: #17264f; + border-color: #b3c7ff; + box-shadow: 0 2px 6px rgba(179, 199, 255, 0.2); +} \ No newline at end of file diff --git a/docs/tsconfig.json b/docs/tsconfig.json new file mode 100644 index 0000000..8bf91d3 --- /dev/null +++ b/docs/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "astro/tsconfigs/strict", + "include": [".astro/types.d.ts", "**/*"], + "exclude": ["dist"] +} diff --git a/swagger-ui-template/favicon-16x16.png b/swagger-ui-template/favicon-16x16.png new file mode 100644 index 0000000..8b194e6 Binary files /dev/null and b/swagger-ui-template/favicon-16x16.png differ diff --git a/swagger-ui-template/favicon-32x32.png b/swagger-ui-template/favicon-32x32.png new file mode 100644 index 0000000..249737f Binary files /dev/null and b/swagger-ui-template/favicon-32x32.png differ diff --git a/swagger-ui-template/index.css b/swagger-ui-template/index.css new file mode 100644 index 0000000..f2376fd --- /dev/null +++ b/swagger-ui-template/index.css @@ -0,0 +1,16 @@ +html { + box-sizing: border-box; + overflow: -moz-scrollbars-vertical; + overflow-y: scroll; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +body { + margin: 0; + background: #fafafa; +} diff --git a/swagger-ui-template/index.html b/swagger-ui-template/index.html new file mode 100644 index 0000000..84ae62d --- /dev/null +++ b/swagger-ui-template/index.html @@ -0,0 +1,19 @@ + + + + + + Swagger UI + + + + + + + +
+ + + + + diff --git a/swagger-ui-template/oauth2-redirect.html b/swagger-ui-template/oauth2-redirect.html new file mode 100644 index 0000000..5640917 --- /dev/null +++ b/swagger-ui-template/oauth2-redirect.html @@ -0,0 +1,79 @@ + + + + Swagger UI: OAuth2 Redirect + + + + + diff --git a/swagger-ui-template/swagger-initializer.js b/swagger-ui-template/swagger-initializer.js new file mode 100644 index 0000000..3acf1a1 --- /dev/null +++ b/swagger-ui-template/swagger-initializer.js @@ -0,0 +1,10 @@ +window.onload = function() { + window.ui = SwaggerUIBundle({ + url: "openapi.yaml", + dom_id: '#swagger-ui', + deepLinking: true, + presets: [ SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset ], + plugins: [ SwaggerUIBundle.plugins.DownloadUrl ], + layout: "StandaloneLayout" + }); +}; diff --git a/swagger-ui-template/swagger-ui-bundle.js b/swagger-ui-template/swagger-ui-bundle.js new file mode 100644 index 0000000..b703daa --- /dev/null +++ b/swagger-ui-template/swagger-ui-bundle.js @@ -0,0 +1,2 @@ +/*! For license information please see swagger-ui-bundle.js.LICENSE.txt */ +!function webpackUniversalModuleDefinition(s,o){"object"==typeof exports&&"object"==typeof module?module.exports=o():"function"==typeof define&&define.amd?define([],o):"object"==typeof exports?exports.SwaggerUIBundle=o():s.SwaggerUIBundle=o()}(this,(()=>(()=>{var s={251:(s,o)=>{o.read=function(s,o,i,a,u){var _,w,x=8*u-a-1,C=(1<>1,L=-7,B=i?u-1:0,$=i?-1:1,V=s[o+B];for(B+=$,_=V&(1<<-L)-1,V>>=-L,L+=x;L>0;_=256*_+s[o+B],B+=$,L-=8);for(w=_&(1<<-L)-1,_>>=-L,L+=a;L>0;w=256*w+s[o+B],B+=$,L-=8);if(0===_)_=1-j;else{if(_===C)return w?NaN:1/0*(V?-1:1);w+=Math.pow(2,a),_-=j}return(V?-1:1)*w*Math.pow(2,_-a)},o.write=function(s,o,i,a,u,_){var w,x,C,j=8*_-u-1,L=(1<>1,$=23===u?Math.pow(2,-24)-Math.pow(2,-77):0,V=a?0:_-1,U=a?1:-1,z=o<0||0===o&&1/o<0?1:0;for(o=Math.abs(o),isNaN(o)||o===1/0?(x=isNaN(o)?1:0,w=L):(w=Math.floor(Math.log(o)/Math.LN2),o*(C=Math.pow(2,-w))<1&&(w--,C*=2),(o+=w+B>=1?$/C:$*Math.pow(2,1-B))*C>=2&&(w++,C/=2),w+B>=L?(x=0,w=L):w+B>=1?(x=(o*C-1)*Math.pow(2,u),w+=B):(x=o*Math.pow(2,B-1)*Math.pow(2,u),w=0));u>=8;s[i+V]=255&x,V+=U,x/=256,u-=8);for(w=w<0;s[i+V]=255&w,V+=U,w/=256,j-=8);s[i+V-U]|=128*z}},462:(s,o,i)=>{"use strict";var a=i(40975);s.exports=a},659:(s,o,i)=>{var a=i(51873),u=Object.prototype,_=u.hasOwnProperty,w=u.toString,x=a?a.toStringTag:void 0;s.exports=function getRawTag(s){var o=_.call(s,x),i=s[x];try{s[x]=void 0;var a=!0}catch(s){}var u=w.call(s);return a&&(o?s[x]=i:delete s[x]),u}},694:(s,o,i)=>{"use strict";i(91599);var a=i(37257);i(12560),s.exports=a},953:(s,o,i)=>{"use strict";s.exports=i(53375)},1733:s=>{var o=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;s.exports=function asciiWords(s){return s.match(o)||[]}},1882:(s,o,i)=>{var a=i(72552),u=i(23805);s.exports=function isFunction(s){if(!u(s))return!1;var o=a(s);return"[object Function]"==o||"[object GeneratorFunction]"==o||"[object AsyncFunction]"==o||"[object Proxy]"==o}},1907:(s,o,i)=>{"use strict";var a=i(41505),u=Function.prototype,_=u.call,w=a&&u.bind.bind(_,_);s.exports=a?w:function(s){return function(){return _.apply(s,arguments)}}},2205:function(s,o,i){var a;a=void 0!==i.g?i.g:this,s.exports=function(s){if(s.CSS&&s.CSS.escape)return s.CSS.escape;var cssEscape=function(s){if(0==arguments.length)throw new TypeError("`CSS.escape` requires an argument.");for(var o,i=String(s),a=i.length,u=-1,_="",w=i.charCodeAt(0);++u=1&&o<=31||127==o||0==u&&o>=48&&o<=57||1==u&&o>=48&&o<=57&&45==w?"\\"+o.toString(16)+" ":0==u&&1==a&&45==o||!(o>=128||45==o||95==o||o>=48&&o<=57||o>=65&&o<=90||o>=97&&o<=122)?"\\"+i.charAt(u):i.charAt(u):_+="�";return _};return s.CSS||(s.CSS={}),s.CSS.escape=cssEscape,cssEscape}(a)},2209:(s,o,i)=>{"use strict";var a,u=i(9404),_=function productionTypeChecker(){invariant(!1,"ImmutablePropTypes type checking code is stripped in production.")};_.isRequired=_;var w=function getProductionTypeChecker(){return _};function getPropType(s){var o=typeof s;return Array.isArray(s)?"array":s instanceof RegExp?"object":s instanceof u.Iterable?"Immutable."+s.toSource().split(" ")[0]:o}function createChainableTypeChecker(s){function checkType(o,i,a,u,_,w){for(var x=arguments.length,C=Array(x>6?x-6:0),j=6;j>",null!=i[a]?s.apply(void 0,[i,a,u,_,w].concat(C)):o?new Error("Required "+_+" `"+w+"` was not specified in `"+u+"`."):void 0}var o=checkType.bind(null,!1);return o.isRequired=checkType.bind(null,!0),o}function createIterableSubclassTypeChecker(s,o){return function createImmutableTypeChecker(s,o){return createChainableTypeChecker((function validate(i,a,u,_,w){var x=i[a];if(!o(x)){var C=getPropType(x);return new Error("Invalid "+_+" `"+w+"` of type `"+C+"` supplied to `"+u+"`, expected `"+s+"`.")}return null}))}("Iterable."+s,(function(s){return u.Iterable.isIterable(s)&&o(s)}))}(a={listOf:w,mapOf:w,orderedMapOf:w,setOf:w,orderedSetOf:w,stackOf:w,iterableOf:w,recordOf:w,shape:w,contains:w,mapContains:w,orderedMapContains:w,list:_,map:_,orderedMap:_,set:_,orderedSet:_,stack:_,seq:_,record:_,iterable:_}).iterable.indexed=createIterableSubclassTypeChecker("Indexed",u.Iterable.isIndexed),a.iterable.keyed=createIterableSubclassTypeChecker("Keyed",u.Iterable.isKeyed),s.exports=a},2404:(s,o,i)=>{var a=i(60270);s.exports=function isEqual(s,o){return a(s,o)}},2523:s=>{s.exports=function baseFindIndex(s,o,i,a){for(var u=s.length,_=i+(a?1:-1);a?_--:++_{"use strict";var a=i(45951),u=Object.defineProperty;s.exports=function(s,o){try{u(a,s,{value:o,configurable:!0,writable:!0})}catch(i){a[s]=o}return o}},2694:(s,o,i)=>{"use strict";var a=i(6925);function emptyFunction(){}function emptyFunctionWithReset(){}emptyFunctionWithReset.resetWarningCache=emptyFunction,s.exports=function(){function shim(s,o,i,u,_,w){if(w!==a){var x=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw x.name="Invariant Violation",x}}function getShim(){return shim}shim.isRequired=shim;var s={array:shim,bigint:shim,bool:shim,func:shim,number:shim,object:shim,string:shim,symbol:shim,any:shim,arrayOf:getShim,element:shim,elementType:shim,instanceOf:getShim,node:shim,objectOf:getShim,oneOf:getShim,oneOfType:getShim,shape:getShim,exact:getShim,checkPropTypes:emptyFunctionWithReset,resetWarningCache:emptyFunction};return s.PropTypes=s,s}},2874:s=>{s.exports={}},2875:(s,o,i)=>{"use strict";var a=i(23045),u=i(80376);s.exports=Object.keys||function keys(s){return a(s,u)}},2955:(s,o,i)=>{"use strict";var a,u=i(65606);function _defineProperty(s,o,i){return(o=function _toPropertyKey(s){var o=function _toPrimitive(s,o){if("object"!=typeof s||null===s)return s;var i=s[Symbol.toPrimitive];if(void 0!==i){var a=i.call(s,o||"default");if("object"!=typeof a)return a;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===o?String:Number)(s)}(s,"string");return"symbol"==typeof o?o:String(o)}(o))in s?Object.defineProperty(s,o,{value:i,enumerable:!0,configurable:!0,writable:!0}):s[o]=i,s}var _=i(86238),w=Symbol("lastResolve"),x=Symbol("lastReject"),C=Symbol("error"),j=Symbol("ended"),L=Symbol("lastPromise"),B=Symbol("handlePromise"),$=Symbol("stream");function createIterResult(s,o){return{value:s,done:o}}function readAndResolve(s){var o=s[w];if(null!==o){var i=s[$].read();null!==i&&(s[L]=null,s[w]=null,s[x]=null,o(createIterResult(i,!1)))}}function onReadable(s){u.nextTick(readAndResolve,s)}var V=Object.getPrototypeOf((function(){})),U=Object.setPrototypeOf((_defineProperty(a={get stream(){return this[$]},next:function next(){var s=this,o=this[C];if(null!==o)return Promise.reject(o);if(this[j])return Promise.resolve(createIterResult(void 0,!0));if(this[$].destroyed)return new Promise((function(o,i){u.nextTick((function(){s[C]?i(s[C]):o(createIterResult(void 0,!0))}))}));var i,a=this[L];if(a)i=new Promise(function wrapForNext(s,o){return function(i,a){s.then((function(){o[j]?i(createIterResult(void 0,!0)):o[B](i,a)}),a)}}(a,this));else{var _=this[$].read();if(null!==_)return Promise.resolve(createIterResult(_,!1));i=new Promise(this[B])}return this[L]=i,i}},Symbol.asyncIterator,(function(){return this})),_defineProperty(a,"return",(function _return(){var s=this;return new Promise((function(o,i){s[$].destroy(null,(function(s){s?i(s):o(createIterResult(void 0,!0))}))}))})),a),V);s.exports=function createReadableStreamAsyncIterator(s){var o,i=Object.create(U,(_defineProperty(o={},$,{value:s,writable:!0}),_defineProperty(o,w,{value:null,writable:!0}),_defineProperty(o,x,{value:null,writable:!0}),_defineProperty(o,C,{value:null,writable:!0}),_defineProperty(o,j,{value:s._readableState.endEmitted,writable:!0}),_defineProperty(o,B,{value:function value(s,o){var a=i[$].read();a?(i[L]=null,i[w]=null,i[x]=null,s(createIterResult(a,!1))):(i[w]=s,i[x]=o)},writable:!0}),o));return i[L]=null,_(s,(function(s){if(s&&"ERR_STREAM_PREMATURE_CLOSE"!==s.code){var o=i[x];return null!==o&&(i[L]=null,i[w]=null,i[x]=null,o(s)),void(i[C]=s)}var a=i[w];null!==a&&(i[L]=null,i[w]=null,i[x]=null,a(createIterResult(void 0,!0))),i[j]=!0})),s.on("readable",onReadable.bind(null,i)),i}},3110:(s,o,i)=>{const a=i(5187),u=i(85015),_=i(98023),w=i(53812),x=i(23805),C=i(85105),j=i(86804);class Namespace{constructor(s){this.elementMap={},this.elementDetection=[],this.Element=j.Element,this.KeyValuePair=j.KeyValuePair,s&&s.noDefault||this.useDefault(),this._attributeElementKeys=[],this._attributeElementArrayKeys=[]}use(s){return s.namespace&&s.namespace({base:this}),s.load&&s.load({base:this}),this}useDefault(){return this.register("null",j.NullElement).register("string",j.StringElement).register("number",j.NumberElement).register("boolean",j.BooleanElement).register("array",j.ArrayElement).register("object",j.ObjectElement).register("member",j.MemberElement).register("ref",j.RefElement).register("link",j.LinkElement),this.detect(a,j.NullElement,!1).detect(u,j.StringElement,!1).detect(_,j.NumberElement,!1).detect(w,j.BooleanElement,!1).detect(Array.isArray,j.ArrayElement,!1).detect(x,j.ObjectElement,!1),this}register(s,o){return this._elements=void 0,this.elementMap[s]=o,this}unregister(s){return this._elements=void 0,delete this.elementMap[s],this}detect(s,o,i){return void 0===i||i?this.elementDetection.unshift([s,o]):this.elementDetection.push([s,o]),this}toElement(s){if(s instanceof this.Element)return s;let o;for(let i=0;i{const o=s[0].toUpperCase()+s.substr(1);this._elements[o]=this.elementMap[s]}))),this._elements}get serialiser(){return new C(this)}}C.prototype.Namespace=Namespace,s.exports=Namespace},3121:(s,o,i)=>{"use strict";var a=i(65482),u=Math.min;s.exports=function(s){var o=a(s);return o>0?u(o,9007199254740991):0}},3209:(s,o,i)=>{var a=i(91596),u=i(53320),_=i(36306),w="__lodash_placeholder__",x=128,C=Math.min;s.exports=function mergeData(s,o){var i=s[1],j=o[1],L=i|j,B=L<131,$=j==x&&8==i||j==x&&256==i&&s[7].length<=o[8]||384==j&&o[7].length<=o[8]&&8==i;if(!B&&!$)return s;1&j&&(s[2]=o[2],L|=1&i?0:4);var V=o[3];if(V){var U=s[3];s[3]=U?a(U,V,o[4]):V,s[4]=U?_(s[3],w):o[4]}return(V=o[5])&&(U=s[5],s[5]=U?u(U,V,o[6]):V,s[6]=U?_(s[5],w):o[6]),(V=o[7])&&(s[7]=V),j&x&&(s[8]=null==s[8]?o[8]:C(s[8],o[8])),null==s[9]&&(s[9]=o[9]),s[0]=o[0],s[1]=L,s}},3650:(s,o,i)=>{var a=i(74335)(Object.keys,Object);s.exports=a},3656:(s,o,i)=>{s=i.nmd(s);var a=i(9325),u=i(89935),_=o&&!o.nodeType&&o,w=_&&s&&!s.nodeType&&s,x=w&&w.exports===_?a.Buffer:void 0,C=(x?x.isBuffer:void 0)||u;s.exports=C},4509:(s,o,i)=>{var a=i(12651);s.exports=function mapCacheHas(s){return a(this,s).has(s)}},4640:s=>{"use strict";var o=String;s.exports=function(s){try{return o(s)}catch(s){return"Object"}}},4664:(s,o,i)=>{var a=i(79770),u=i(63345),_=Object.prototype.propertyIsEnumerable,w=Object.getOwnPropertySymbols,x=w?function(s){return null==s?[]:(s=Object(s),a(w(s),(function(o){return _.call(s,o)})))}:u;s.exports=x},4901:(s,o,i)=>{var a=i(72552),u=i(30294),_=i(40346),w={};w["[object Float32Array]"]=w["[object Float64Array]"]=w["[object Int8Array]"]=w["[object Int16Array]"]=w["[object Int32Array]"]=w["[object Uint8Array]"]=w["[object Uint8ClampedArray]"]=w["[object Uint16Array]"]=w["[object Uint32Array]"]=!0,w["[object Arguments]"]=w["[object Array]"]=w["[object ArrayBuffer]"]=w["[object Boolean]"]=w["[object DataView]"]=w["[object Date]"]=w["[object Error]"]=w["[object Function]"]=w["[object Map]"]=w["[object Number]"]=w["[object Object]"]=w["[object RegExp]"]=w["[object Set]"]=w["[object String]"]=w["[object WeakMap]"]=!1,s.exports=function baseIsTypedArray(s){return _(s)&&u(s.length)&&!!w[a(s)]}},4993:(s,o,i)=>{"use strict";var a=i(16946),u=i(74239);s.exports=function(s){return a(u(s))}},5187:s=>{s.exports=function isNull(s){return null===s}},5419:s=>{s.exports=function(s,o,i,a){var u=new Blob(void 0!==a?[a,s]:[s],{type:i||"application/octet-stream"});if(void 0!==window.navigator.msSaveBlob)window.navigator.msSaveBlob(u,o);else{var _=window.URL&&window.URL.createObjectURL?window.URL.createObjectURL(u):window.webkitURL.createObjectURL(u),w=document.createElement("a");w.style.display="none",w.href=_,w.setAttribute("download",o),void 0===w.download&&w.setAttribute("target","_blank"),document.body.appendChild(w),w.click(),setTimeout((function(){document.body.removeChild(w),window.URL.revokeObjectURL(_)}),200)}}},5556:(s,o,i)=>{s.exports=i(2694)()},5861:(s,o,i)=>{var a=i(55580),u=i(68223),_=i(32804),w=i(76545),x=i(28303),C=i(72552),j=i(47473),L="[object Map]",B="[object Promise]",$="[object Set]",V="[object WeakMap]",U="[object DataView]",z=j(a),Y=j(u),Z=j(_),ee=j(w),ie=j(x),ae=C;(a&&ae(new a(new ArrayBuffer(1)))!=U||u&&ae(new u)!=L||_&&ae(_.resolve())!=B||w&&ae(new w)!=$||x&&ae(new x)!=V)&&(ae=function(s){var o=C(s),i="[object Object]"==o?s.constructor:void 0,a=i?j(i):"";if(a)switch(a){case z:return U;case Y:return L;case Z:return B;case ee:return $;case ie:return V}return o}),s.exports=ae},6048:s=>{s.exports=function negate(s){if("function"!=typeof s)throw new TypeError("Expected a function");return function(){var o=arguments;switch(o.length){case 0:return!s.call(this);case 1:return!s.call(this,o[0]);case 2:return!s.call(this,o[0],o[1]);case 3:return!s.call(this,o[0],o[1],o[2])}return!s.apply(this,o)}}},6205:s=>{s.exports={ROOT:0,GROUP:1,POSITION:2,SET:3,RANGE:4,REPETITION:5,REFERENCE:6,CHAR:7}},6233:(s,o,i)=>{const a=i(6048),u=i(10316),_=i(92340);class ArrayElement extends u{constructor(s,o,i){super(s||[],o,i),this.element="array"}primitive(){return"array"}get(s){return this.content[s]}getValue(s){const o=this.get(s);if(o)return o.toValue()}getIndex(s){return this.content[s]}set(s,o){return this.content[s]=this.refract(o),this}remove(s){const o=this.content.splice(s,1);return o.length?o[0]:null}map(s,o){return this.content.map(s,o)}flatMap(s,o){return this.map(s,o).reduce(((s,o)=>s.concat(o)),[])}compactMap(s,o){const i=[];return this.forEach((a=>{const u=s.bind(o)(a);u&&i.push(u)})),i}filter(s,o){return new _(this.content.filter(s,o))}reject(s,o){return this.filter(a(s),o)}reduce(s,o){let i,a;void 0!==o?(i=0,a=this.refract(o)):(i=1,a="object"===this.primitive()?this.first.value:this.first);for(let o=i;o{s.bind(o)(i,this.refract(a))}))}shift(){return this.content.shift()}unshift(s){this.content.unshift(this.refract(s))}push(s){return this.content.push(this.refract(s)),this}add(s){this.push(s)}findElements(s,o){const i=o||{},a=!!i.recursive,u=void 0===i.results?[]:i.results;return this.forEach(((o,i,_)=>{a&&void 0!==o.findElements&&o.findElements(s,{results:u,recursive:a}),s(o,i,_)&&u.push(o)})),u}find(s){return new _(this.findElements(s,{recursive:!0}))}findByElement(s){return this.find((o=>o.element===s))}findByClass(s){return this.find((o=>o.classes.includes(s)))}getById(s){return this.find((o=>o.id.toValue()===s)).first}includes(s){return this.content.some((o=>o.equals(s)))}contains(s){return this.includes(s)}empty(){return new this.constructor([])}"fantasy-land/empty"(){return this.empty()}concat(s){return new this.constructor(this.content.concat(s.content))}"fantasy-land/concat"(s){return this.concat(s)}"fantasy-land/map"(s){return new this.constructor(this.map(s))}"fantasy-land/chain"(s){return this.map((o=>s(o)),this).reduce(((s,o)=>s.concat(o)),this.empty())}"fantasy-land/filter"(s){return new this.constructor(this.content.filter(s))}"fantasy-land/reduce"(s,o){return this.content.reduce(s,o)}get length(){return this.content.length}get isEmpty(){return 0===this.content.length}get first(){return this.getIndex(0)}get second(){return this.getIndex(1)}get last(){return this.getIndex(this.length-1)}}ArrayElement.empty=function empty(){return new this},ArrayElement["fantasy-land/empty"]=ArrayElement.empty,"undefined"!=typeof Symbol&&(ArrayElement.prototype[Symbol.iterator]=function symbol(){return this.content[Symbol.iterator]()}),s.exports=ArrayElement},6499:(s,o,i)=>{"use strict";var a=i(1907),u=0,_=Math.random(),w=a(1..toString);s.exports=function(s){return"Symbol("+(void 0===s?"":s)+")_"+w(++u+_,36)}},6925:s=>{"use strict";s.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},7057:(s,o,i)=>{"use strict";var a=i(11470).charAt,u=i(90160),_=i(64932),w=i(60183),x=i(59550),C="String Iterator",j=_.set,L=_.getterFor(C);w(String,"String",(function(s){j(this,{type:C,string:u(s),index:0})}),(function next(){var s,o=L(this),i=o.string,u=o.index;return u>=i.length?x(void 0,!0):(s=a(i,u),o.index+=s.length,x(s,!1))}))},7309:(s,o,i)=>{var a=i(62006)(i(24713));s.exports=a},7376:s=>{"use strict";s.exports=!0},7463:(s,o,i)=>{"use strict";var a=i(98828),u=i(62250),_=/#|\.prototype\./,isForced=function(s,o){var i=x[w(s)];return i===j||i!==C&&(u(o)?a(o):!!o)},w=isForced.normalize=function(s){return String(s).replace(_,".").toLowerCase()},x=isForced.data={},C=isForced.NATIVE="N",j=isForced.POLYFILL="P";s.exports=isForced},7666:(s,o,i)=>{var a=i(84851),u=i(953);function _extends(){var o;return s.exports=_extends=a?u(o=a).call(o):function(s){for(var o=1;o{const a=i(6205);o.wordBoundary=()=>({type:a.POSITION,value:"b"}),o.nonWordBoundary=()=>({type:a.POSITION,value:"B"}),o.begin=()=>({type:a.POSITION,value:"^"}),o.end=()=>({type:a.POSITION,value:"$"})},8068:s=>{"use strict";var o=(()=>{var s=Object.defineProperty,o=Object.getOwnPropertyDescriptor,i=Object.getOwnPropertyNames,a=Object.getOwnPropertySymbols,u=Object.prototype.hasOwnProperty,_=Object.prototype.propertyIsEnumerable,__defNormalProp=(o,i,a)=>i in o?s(o,i,{enumerable:!0,configurable:!0,writable:!0,value:a}):o[i]=a,__spreadValues=(s,o)=>{for(var i in o||(o={}))u.call(o,i)&&__defNormalProp(s,i,o[i]);if(a)for(var i of a(o))_.call(o,i)&&__defNormalProp(s,i,o[i]);return s},__publicField=(s,o,i)=>(__defNormalProp(s,"symbol"!=typeof o?o+"":o,i),i),w={};((o,i)=>{for(var a in i)s(o,a,{get:i[a],enumerable:!0})})(w,{DEFAULT_OPTIONS:()=>C,DEFAULT_UUID_LENGTH:()=>x,default:()=>B});var x=6,C={dictionary:"alphanum",shuffle:!0,debug:!1,length:x,counter:0},j=class _ShortUniqueId{constructor(s={}){__publicField(this,"counter"),__publicField(this,"debug"),__publicField(this,"dict"),__publicField(this,"version"),__publicField(this,"dictIndex",0),__publicField(this,"dictRange",[]),__publicField(this,"lowerBound",0),__publicField(this,"upperBound",0),__publicField(this,"dictLength",0),__publicField(this,"uuidLength"),__publicField(this,"_digit_first_ascii",48),__publicField(this,"_digit_last_ascii",58),__publicField(this,"_alpha_lower_first_ascii",97),__publicField(this,"_alpha_lower_last_ascii",123),__publicField(this,"_hex_last_ascii",103),__publicField(this,"_alpha_upper_first_ascii",65),__publicField(this,"_alpha_upper_last_ascii",91),__publicField(this,"_number_dict_ranges",{digits:[this._digit_first_ascii,this._digit_last_ascii]}),__publicField(this,"_alpha_dict_ranges",{lowerCase:[this._alpha_lower_first_ascii,this._alpha_lower_last_ascii],upperCase:[this._alpha_upper_first_ascii,this._alpha_upper_last_ascii]}),__publicField(this,"_alpha_lower_dict_ranges",{lowerCase:[this._alpha_lower_first_ascii,this._alpha_lower_last_ascii]}),__publicField(this,"_alpha_upper_dict_ranges",{upperCase:[this._alpha_upper_first_ascii,this._alpha_upper_last_ascii]}),__publicField(this,"_alphanum_dict_ranges",{digits:[this._digit_first_ascii,this._digit_last_ascii],lowerCase:[this._alpha_lower_first_ascii,this._alpha_lower_last_ascii],upperCase:[this._alpha_upper_first_ascii,this._alpha_upper_last_ascii]}),__publicField(this,"_alphanum_lower_dict_ranges",{digits:[this._digit_first_ascii,this._digit_last_ascii],lowerCase:[this._alpha_lower_first_ascii,this._alpha_lower_last_ascii]}),__publicField(this,"_alphanum_upper_dict_ranges",{digits:[this._digit_first_ascii,this._digit_last_ascii],upperCase:[this._alpha_upper_first_ascii,this._alpha_upper_last_ascii]}),__publicField(this,"_hex_dict_ranges",{decDigits:[this._digit_first_ascii,this._digit_last_ascii],alphaDigits:[this._alpha_lower_first_ascii,this._hex_last_ascii]}),__publicField(this,"_dict_ranges",{_number_dict_ranges:this._number_dict_ranges,_alpha_dict_ranges:this._alpha_dict_ranges,_alpha_lower_dict_ranges:this._alpha_lower_dict_ranges,_alpha_upper_dict_ranges:this._alpha_upper_dict_ranges,_alphanum_dict_ranges:this._alphanum_dict_ranges,_alphanum_lower_dict_ranges:this._alphanum_lower_dict_ranges,_alphanum_upper_dict_ranges:this._alphanum_upper_dict_ranges,_hex_dict_ranges:this._hex_dict_ranges}),__publicField(this,"log",((...s)=>{const o=[...s];if(o[0]=`[short-unique-id] ${s[0]}`,!0===this.debug&&"undefined"!=typeof console&&null!==console)return console.log(...o)})),__publicField(this,"_normalizeDictionary",((s,o)=>{let i;if(s&&Array.isArray(s)&&s.length>1)i=s;else{let o;i=[],this.dictIndex=o=0;const a=`_${s}_dict_ranges`,u=this._dict_ranges[a];Object.keys(u).forEach((s=>{const a=s;for(this.dictRange=u[a],this.lowerBound=this.dictRange[0],this.upperBound=this.dictRange[1],this.dictIndex=o=this.lowerBound;this.lowerBound<=this.upperBound?othis.upperBound;this.dictIndex=this.lowerBound<=this.upperBound?o+=1:o-=1)i.push(String.fromCharCode(this.dictIndex))}))}if(o){const s=.5;i=i.sort((()=>Math.random()-s))}return i})),__publicField(this,"setDictionary",((s,o)=>{this.dict=this._normalizeDictionary(s,o),this.dictLength=this.dict.length,this.setCounter(0)})),__publicField(this,"seq",(()=>this.sequentialUUID())),__publicField(this,"sequentialUUID",(()=>{let s,o,i="";s=this.counter;do{o=s%this.dictLength,s=Math.trunc(s/this.dictLength),i+=this.dict[o]}while(0!==s);return this.counter+=1,i})),__publicField(this,"rnd",((s=this.uuidLength||x)=>this.randomUUID(s))),__publicField(this,"randomUUID",((s=this.uuidLength||x)=>{let o,i,a;if(null==s||s<1)throw new Error("Invalid UUID Length Provided");for(o="",a=0;athis.formattedUUID(s,o))),__publicField(this,"formattedUUID",((s,o)=>{const i={$r:this.randomUUID,$s:this.sequentialUUID,$t:this.stamp};return s.replace(/\$[rs]\d{0,}|\$t0|\$t[1-9]\d{1,}/g,(s=>{const a=s.slice(0,2),u=parseInt(s.slice(2),10);return"$s"===a?i[a]().padStart(u,"0"):"$t"===a&&o?i[a](u,o):i[a](u)}))})),__publicField(this,"availableUUIDs",((s=this.uuidLength)=>parseFloat(Math.pow([...new Set(this.dict)].length,s).toFixed(0)))),__publicField(this,"approxMaxBeforeCollision",((s=this.availableUUIDs(this.uuidLength))=>parseFloat(Math.sqrt(Math.PI/2*s).toFixed(20)))),__publicField(this,"collisionProbability",((s=this.availableUUIDs(this.uuidLength),o=this.uuidLength)=>parseFloat((this.approxMaxBeforeCollision(s)/this.availableUUIDs(o)).toFixed(20)))),__publicField(this,"uniqueness",((s=this.availableUUIDs(this.uuidLength))=>{const o=parseFloat((1-this.approxMaxBeforeCollision(s)/s).toFixed(20));return o>1?1:o<0?0:o})),__publicField(this,"getVersion",(()=>this.version)),__publicField(this,"stamp",((s,o)=>{const i=Math.floor(+(o||new Date)/1e3).toString(16);if("number"==typeof s&&0===s)return i;if("number"!=typeof s||s<10)throw new Error(["Param finalLength must be a number greater than or equal to 10,","or 0 if you want the raw hexadecimal timestamp"].join("\n"));const a=s-9,u=Math.round(Math.random()*(a>15?15:a)),_=this.randomUUID(a);return`${_.substring(0,u)}${i}${_.substring(u)}${u.toString(16)}`})),__publicField(this,"parseStamp",((s,o)=>{if(o&&!/t0|t[1-9]\d{1,}/.test(o))throw new Error("Cannot extract date from a formated UUID with no timestamp in the format");const i=o?o.replace(/\$[rs]\d{0,}|\$t0|\$t[1-9]\d{1,}/g,(s=>{const o={$r:s=>[...Array(s)].map((()=>"r")).join(""),$s:s=>[...Array(s)].map((()=>"s")).join(""),$t:s=>[...Array(s)].map((()=>"t")).join("")},i=s.slice(0,2),a=parseInt(s.slice(2),10);return o[i](a)})).replace(/^(.*?)(t{8,})(.*)$/g,((o,i,a)=>s.substring(i.length,i.length+a.length))):s;if(8===i.length)return new Date(1e3*parseInt(i,16));if(i.length<10)throw new Error("Stamp length invalid");const a=parseInt(i.substring(i.length-1),16);return new Date(1e3*parseInt(i.substring(a,a+8),16))})),__publicField(this,"setCounter",(s=>{this.counter=s})),__publicField(this,"validate",((s,o)=>{const i=o?this._normalizeDictionary(o):this.dict;return s.split("").every((s=>i.includes(s)))}));const o=__spreadValues(__spreadValues({},C),s);this.counter=0,this.debug=!1,this.dict=[],this.version="5.2.0";const{dictionary:i,shuffle:a,length:u,counter:_}=o;return this.uuidLength=u,this.setDictionary(i,a),this.setCounter(_),this.debug=o.debug,this.log(this.dict),this.log(`Generator instantiated with Dictionary Size ${this.dictLength} and counter set to ${this.counter}`),this.log=this.log.bind(this),this.setDictionary=this.setDictionary.bind(this),this.setCounter=this.setCounter.bind(this),this.seq=this.seq.bind(this),this.sequentialUUID=this.sequentialUUID.bind(this),this.rnd=this.rnd.bind(this),this.randomUUID=this.randomUUID.bind(this),this.fmt=this.fmt.bind(this),this.formattedUUID=this.formattedUUID.bind(this),this.availableUUIDs=this.availableUUIDs.bind(this),this.approxMaxBeforeCollision=this.approxMaxBeforeCollision.bind(this),this.collisionProbability=this.collisionProbability.bind(this),this.uniqueness=this.uniqueness.bind(this),this.getVersion=this.getVersion.bind(this),this.stamp=this.stamp.bind(this),this.parseStamp=this.parseStamp.bind(this),this}};__publicField(j,"default",j);var L,B=j;return L=w,((a,_,w,x)=>{if(_&&"object"==typeof _||"function"==typeof _)for(let C of i(_))u.call(a,C)||C===w||s(a,C,{get:()=>_[C],enumerable:!(x=o(_,C))||x.enumerable});return a})(s({},"__esModule",{value:!0}),L)})();s.exports=o.default,"undefined"!=typeof window&&(o=o.default)},9325:(s,o,i)=>{var a=i(34840),u="object"==typeof self&&self&&self.Object===Object&&self,_=a||u||Function("return this")();s.exports=_},9404:function(s){s.exports=function(){"use strict";var s=Array.prototype.slice;function createClass(s,o){o&&(s.prototype=Object.create(o.prototype)),s.prototype.constructor=s}function Iterable(s){return isIterable(s)?s:Seq(s)}function KeyedIterable(s){return isKeyed(s)?s:KeyedSeq(s)}function IndexedIterable(s){return isIndexed(s)?s:IndexedSeq(s)}function SetIterable(s){return isIterable(s)&&!isAssociative(s)?s:SetSeq(s)}function isIterable(s){return!(!s||!s[o])}function isKeyed(s){return!(!s||!s[i])}function isIndexed(s){return!(!s||!s[a])}function isAssociative(s){return isKeyed(s)||isIndexed(s)}function isOrdered(s){return!(!s||!s[u])}createClass(KeyedIterable,Iterable),createClass(IndexedIterable,Iterable),createClass(SetIterable,Iterable),Iterable.isIterable=isIterable,Iterable.isKeyed=isKeyed,Iterable.isIndexed=isIndexed,Iterable.isAssociative=isAssociative,Iterable.isOrdered=isOrdered,Iterable.Keyed=KeyedIterable,Iterable.Indexed=IndexedIterable,Iterable.Set=SetIterable;var o="@@__IMMUTABLE_ITERABLE__@@",i="@@__IMMUTABLE_KEYED__@@",a="@@__IMMUTABLE_INDEXED__@@",u="@@__IMMUTABLE_ORDERED__@@",_="delete",w=5,x=1<>>0;if(""+i!==o||4294967295===i)return NaN;o=i}return o<0?ensureSize(s)+o:o}function returnTrue(){return!0}function wholeSlice(s,o,i){return(0===s||void 0!==i&&s<=-i)&&(void 0===o||void 0!==i&&o>=i)}function resolveBegin(s,o){return resolveIndex(s,o,0)}function resolveEnd(s,o){return resolveIndex(s,o,o)}function resolveIndex(s,o,i){return void 0===s?i:s<0?Math.max(0,o+s):void 0===o?s:Math.min(o,s)}var $=0,V=1,U=2,z="function"==typeof Symbol&&Symbol.iterator,Y="@@iterator",Z=z||Y;function Iterator(s){this.next=s}function iteratorValue(s,o,i,a){var u=0===s?o:1===s?i:[o,i];return a?a.value=u:a={value:u,done:!1},a}function iteratorDone(){return{value:void 0,done:!0}}function hasIterator(s){return!!getIteratorFn(s)}function isIterator(s){return s&&"function"==typeof s.next}function getIterator(s){var o=getIteratorFn(s);return o&&o.call(s)}function getIteratorFn(s){var o=s&&(z&&s[z]||s[Y]);if("function"==typeof o)return o}function isArrayLike(s){return s&&"number"==typeof s.length}function Seq(s){return null==s?emptySequence():isIterable(s)?s.toSeq():seqFromValue(s)}function KeyedSeq(s){return null==s?emptySequence().toKeyedSeq():isIterable(s)?isKeyed(s)?s.toSeq():s.fromEntrySeq():keyedSeqFromValue(s)}function IndexedSeq(s){return null==s?emptySequence():isIterable(s)?isKeyed(s)?s.entrySeq():s.toIndexedSeq():indexedSeqFromValue(s)}function SetSeq(s){return(null==s?emptySequence():isIterable(s)?isKeyed(s)?s.entrySeq():s:indexedSeqFromValue(s)).toSetSeq()}Iterator.prototype.toString=function(){return"[Iterator]"},Iterator.KEYS=$,Iterator.VALUES=V,Iterator.ENTRIES=U,Iterator.prototype.inspect=Iterator.prototype.toSource=function(){return this.toString()},Iterator.prototype[Z]=function(){return this},createClass(Seq,Iterable),Seq.of=function(){return Seq(arguments)},Seq.prototype.toSeq=function(){return this},Seq.prototype.toString=function(){return this.__toString("Seq {","}")},Seq.prototype.cacheResult=function(){return!this._cache&&this.__iterateUncached&&(this._cache=this.entrySeq().toArray(),this.size=this._cache.length),this},Seq.prototype.__iterate=function(s,o){return seqIterate(this,s,o,!0)},Seq.prototype.__iterator=function(s,o){return seqIterator(this,s,o,!0)},createClass(KeyedSeq,Seq),KeyedSeq.prototype.toKeyedSeq=function(){return this},createClass(IndexedSeq,Seq),IndexedSeq.of=function(){return IndexedSeq(arguments)},IndexedSeq.prototype.toIndexedSeq=function(){return this},IndexedSeq.prototype.toString=function(){return this.__toString("Seq [","]")},IndexedSeq.prototype.__iterate=function(s,o){return seqIterate(this,s,o,!1)},IndexedSeq.prototype.__iterator=function(s,o){return seqIterator(this,s,o,!1)},createClass(SetSeq,Seq),SetSeq.of=function(){return SetSeq(arguments)},SetSeq.prototype.toSetSeq=function(){return this},Seq.isSeq=isSeq,Seq.Keyed=KeyedSeq,Seq.Set=SetSeq,Seq.Indexed=IndexedSeq;var ee,ie,ae,ce="@@__IMMUTABLE_SEQ__@@";function ArraySeq(s){this._array=s,this.size=s.length}function ObjectSeq(s){var o=Object.keys(s);this._object=s,this._keys=o,this.size=o.length}function IterableSeq(s){this._iterable=s,this.size=s.length||s.size}function IteratorSeq(s){this._iterator=s,this._iteratorCache=[]}function isSeq(s){return!(!s||!s[ce])}function emptySequence(){return ee||(ee=new ArraySeq([]))}function keyedSeqFromValue(s){var o=Array.isArray(s)?new ArraySeq(s).fromEntrySeq():isIterator(s)?new IteratorSeq(s).fromEntrySeq():hasIterator(s)?new IterableSeq(s).fromEntrySeq():"object"==typeof s?new ObjectSeq(s):void 0;if(!o)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+s);return o}function indexedSeqFromValue(s){var o=maybeIndexedSeqFromValue(s);if(!o)throw new TypeError("Expected Array or iterable object of values: "+s);return o}function seqFromValue(s){var o=maybeIndexedSeqFromValue(s)||"object"==typeof s&&new ObjectSeq(s);if(!o)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+s);return o}function maybeIndexedSeqFromValue(s){return isArrayLike(s)?new ArraySeq(s):isIterator(s)?new IteratorSeq(s):hasIterator(s)?new IterableSeq(s):void 0}function seqIterate(s,o,i,a){var u=s._cache;if(u){for(var _=u.length-1,w=0;w<=_;w++){var x=u[i?_-w:w];if(!1===o(x[1],a?x[0]:w,s))return w+1}return w}return s.__iterateUncached(o,i)}function seqIterator(s,o,i,a){var u=s._cache;if(u){var _=u.length-1,w=0;return new Iterator((function(){var s=u[i?_-w:w];return w++>_?iteratorDone():iteratorValue(o,a?s[0]:w-1,s[1])}))}return s.__iteratorUncached(o,i)}function fromJS(s,o){return o?fromJSWith(o,s,"",{"":s}):fromJSDefault(s)}function fromJSWith(s,o,i,a){return Array.isArray(o)?s.call(a,i,IndexedSeq(o).map((function(i,a){return fromJSWith(s,i,a,o)}))):isPlainObj(o)?s.call(a,i,KeyedSeq(o).map((function(i,a){return fromJSWith(s,i,a,o)}))):o}function fromJSDefault(s){return Array.isArray(s)?IndexedSeq(s).map(fromJSDefault).toList():isPlainObj(s)?KeyedSeq(s).map(fromJSDefault).toMap():s}function isPlainObj(s){return s&&(s.constructor===Object||void 0===s.constructor)}function is(s,o){if(s===o||s!=s&&o!=o)return!0;if(!s||!o)return!1;if("function"==typeof s.valueOf&&"function"==typeof o.valueOf){if((s=s.valueOf())===(o=o.valueOf())||s!=s&&o!=o)return!0;if(!s||!o)return!1}return!("function"!=typeof s.equals||"function"!=typeof o.equals||!s.equals(o))}function deepEqual(s,o){if(s===o)return!0;if(!isIterable(o)||void 0!==s.size&&void 0!==o.size&&s.size!==o.size||void 0!==s.__hash&&void 0!==o.__hash&&s.__hash!==o.__hash||isKeyed(s)!==isKeyed(o)||isIndexed(s)!==isIndexed(o)||isOrdered(s)!==isOrdered(o))return!1;if(0===s.size&&0===o.size)return!0;var i=!isAssociative(s);if(isOrdered(s)){var a=s.entries();return o.every((function(s,o){var u=a.next().value;return u&&is(u[1],s)&&(i||is(u[0],o))}))&&a.next().done}var u=!1;if(void 0===s.size)if(void 0===o.size)"function"==typeof s.cacheResult&&s.cacheResult();else{u=!0;var _=s;s=o,o=_}var w=!0,x=o.__iterate((function(o,a){if(i?!s.has(o):u?!is(o,s.get(a,j)):!is(s.get(a,j),o))return w=!1,!1}));return w&&s.size===x}function Repeat(s,o){if(!(this instanceof Repeat))return new Repeat(s,o);if(this._value=s,this.size=void 0===o?1/0:Math.max(0,o),0===this.size){if(ie)return ie;ie=this}}function invariant(s,o){if(!s)throw new Error(o)}function Range(s,o,i){if(!(this instanceof Range))return new Range(s,o,i);if(invariant(0!==i,"Cannot step a Range by 0"),s=s||0,void 0===o&&(o=1/0),i=void 0===i?1:Math.abs(i),oa?iteratorDone():iteratorValue(s,u,i[o?a-u++:u++])}))},createClass(ObjectSeq,KeyedSeq),ObjectSeq.prototype.get=function(s,o){return void 0===o||this.has(s)?this._object[s]:o},ObjectSeq.prototype.has=function(s){return this._object.hasOwnProperty(s)},ObjectSeq.prototype.__iterate=function(s,o){for(var i=this._object,a=this._keys,u=a.length-1,_=0;_<=u;_++){var w=a[o?u-_:_];if(!1===s(i[w],w,this))return _+1}return _},ObjectSeq.prototype.__iterator=function(s,o){var i=this._object,a=this._keys,u=a.length-1,_=0;return new Iterator((function(){var w=a[o?u-_:_];return _++>u?iteratorDone():iteratorValue(s,w,i[w])}))},ObjectSeq.prototype[u]=!0,createClass(IterableSeq,IndexedSeq),IterableSeq.prototype.__iterateUncached=function(s,o){if(o)return this.cacheResult().__iterate(s,o);var i=getIterator(this._iterable),a=0;if(isIterator(i))for(var u;!(u=i.next()).done&&!1!==s(u.value,a++,this););return a},IterableSeq.prototype.__iteratorUncached=function(s,o){if(o)return this.cacheResult().__iterator(s,o);var i=getIterator(this._iterable);if(!isIterator(i))return new Iterator(iteratorDone);var a=0;return new Iterator((function(){var o=i.next();return o.done?o:iteratorValue(s,a++,o.value)}))},createClass(IteratorSeq,IndexedSeq),IteratorSeq.prototype.__iterateUncached=function(s,o){if(o)return this.cacheResult().__iterate(s,o);for(var i,a=this._iterator,u=this._iteratorCache,_=0;_=a.length){var o=i.next();if(o.done)return o;a[u]=o.value}return iteratorValue(s,u,a[u++])}))},createClass(Repeat,IndexedSeq),Repeat.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},Repeat.prototype.get=function(s,o){return this.has(s)?this._value:o},Repeat.prototype.includes=function(s){return is(this._value,s)},Repeat.prototype.slice=function(s,o){var i=this.size;return wholeSlice(s,o,i)?this:new Repeat(this._value,resolveEnd(o,i)-resolveBegin(s,i))},Repeat.prototype.reverse=function(){return this},Repeat.prototype.indexOf=function(s){return is(this._value,s)?0:-1},Repeat.prototype.lastIndexOf=function(s){return is(this._value,s)?this.size:-1},Repeat.prototype.__iterate=function(s,o){for(var i=0;i=0&&o=0&&ii?iteratorDone():iteratorValue(s,_++,w)}))},Range.prototype.equals=function(s){return s instanceof Range?this._start===s._start&&this._end===s._end&&this._step===s._step:deepEqual(this,s)},createClass(Collection,Iterable),createClass(KeyedCollection,Collection),createClass(IndexedCollection,Collection),createClass(SetCollection,Collection),Collection.Keyed=KeyedCollection,Collection.Indexed=IndexedCollection,Collection.Set=SetCollection;var le="function"==typeof Math.imul&&-2===Math.imul(4294967295,2)?Math.imul:function imul(s,o){var i=65535&(s|=0),a=65535&(o|=0);return i*a+((s>>>16)*a+i*(o>>>16)<<16>>>0)|0};function smi(s){return s>>>1&1073741824|3221225471&s}function hash(s){if(!1===s||null==s)return 0;if("function"==typeof s.valueOf&&(!1===(s=s.valueOf())||null==s))return 0;if(!0===s)return 1;var o=typeof s;if("number"===o){if(s!=s||s===1/0)return 0;var i=0|s;for(i!==s&&(i^=4294967295*s);s>4294967295;)i^=s/=4294967295;return smi(i)}if("string"===o)return s.length>Se?cachedHashString(s):hashString(s);if("function"==typeof s.hashCode)return s.hashCode();if("object"===o)return hashJSObj(s);if("function"==typeof s.toString)return hashString(s.toString());throw new Error("Value type "+o+" cannot be hashed.")}function cachedHashString(s){var o=Pe[s];return void 0===o&&(o=hashString(s),xe===we&&(xe=0,Pe={}),xe++,Pe[s]=o),o}function hashString(s){for(var o=0,i=0;i0)switch(s.nodeType){case 1:return s.uniqueID;case 9:return s.documentElement&&s.documentElement.uniqueID}}var fe,ye="function"==typeof WeakMap;ye&&(fe=new WeakMap);var be=0,_e="__immutablehash__";"function"==typeof Symbol&&(_e=Symbol(_e));var Se=16,we=255,xe=0,Pe={};function assertNotInfinite(s){invariant(s!==1/0,"Cannot perform this action with an infinite size.")}function Map(s){return null==s?emptyMap():isMap(s)&&!isOrdered(s)?s:emptyMap().withMutations((function(o){var i=KeyedIterable(s);assertNotInfinite(i.size),i.forEach((function(s,i){return o.set(i,s)}))}))}function isMap(s){return!(!s||!s[Re])}createClass(Map,KeyedCollection),Map.of=function(){var o=s.call(arguments,0);return emptyMap().withMutations((function(s){for(var i=0;i=o.length)throw new Error("Missing value for key: "+o[i]);s.set(o[i],o[i+1])}}))},Map.prototype.toString=function(){return this.__toString("Map {","}")},Map.prototype.get=function(s,o){return this._root?this._root.get(0,void 0,s,o):o},Map.prototype.set=function(s,o){return updateMap(this,s,o)},Map.prototype.setIn=function(s,o){return this.updateIn(s,j,(function(){return o}))},Map.prototype.remove=function(s){return updateMap(this,s,j)},Map.prototype.deleteIn=function(s){return this.updateIn(s,(function(){return j}))},Map.prototype.update=function(s,o,i){return 1===arguments.length?s(this):this.updateIn([s],o,i)},Map.prototype.updateIn=function(s,o,i){i||(i=o,o=void 0);var a=updateInDeepMap(this,forceIterator(s),o,i);return a===j?void 0:a},Map.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):emptyMap()},Map.prototype.merge=function(){return mergeIntoMapWith(this,void 0,arguments)},Map.prototype.mergeWith=function(o){return mergeIntoMapWith(this,o,s.call(arguments,1))},Map.prototype.mergeIn=function(o){var i=s.call(arguments,1);return this.updateIn(o,emptyMap(),(function(s){return"function"==typeof s.merge?s.merge.apply(s,i):i[i.length-1]}))},Map.prototype.mergeDeep=function(){return mergeIntoMapWith(this,deepMerger,arguments)},Map.prototype.mergeDeepWith=function(o){var i=s.call(arguments,1);return mergeIntoMapWith(this,deepMergerWith(o),i)},Map.prototype.mergeDeepIn=function(o){var i=s.call(arguments,1);return this.updateIn(o,emptyMap(),(function(s){return"function"==typeof s.mergeDeep?s.mergeDeep.apply(s,i):i[i.length-1]}))},Map.prototype.sort=function(s){return OrderedMap(sortFactory(this,s))},Map.prototype.sortBy=function(s,o){return OrderedMap(sortFactory(this,o,s))},Map.prototype.withMutations=function(s){var o=this.asMutable();return s(o),o.wasAltered()?o.__ensureOwner(this.__ownerID):this},Map.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new OwnerID)},Map.prototype.asImmutable=function(){return this.__ensureOwner()},Map.prototype.wasAltered=function(){return this.__altered},Map.prototype.__iterator=function(s,o){return new MapIterator(this,s,o)},Map.prototype.__iterate=function(s,o){var i=this,a=0;return this._root&&this._root.iterate((function(o){return a++,s(o[1],o[0],i)}),o),a},Map.prototype.__ensureOwner=function(s){return s===this.__ownerID?this:s?makeMap(this.size,this._root,s,this.__hash):(this.__ownerID=s,this.__altered=!1,this)},Map.isMap=isMap;var Te,Re="@@__IMMUTABLE_MAP__@@",qe=Map.prototype;function ArrayMapNode(s,o){this.ownerID=s,this.entries=o}function BitmapIndexedNode(s,o,i){this.ownerID=s,this.bitmap=o,this.nodes=i}function HashArrayMapNode(s,o,i){this.ownerID=s,this.count=o,this.nodes=i}function HashCollisionNode(s,o,i){this.ownerID=s,this.keyHash=o,this.entries=i}function ValueNode(s,o,i){this.ownerID=s,this.keyHash=o,this.entry=i}function MapIterator(s,o,i){this._type=o,this._reverse=i,this._stack=s._root&&mapIteratorFrame(s._root)}function mapIteratorValue(s,o){return iteratorValue(s,o[0],o[1])}function mapIteratorFrame(s,o){return{node:s,index:0,__prev:o}}function makeMap(s,o,i,a){var u=Object.create(qe);return u.size=s,u._root=o,u.__ownerID=i,u.__hash=a,u.__altered=!1,u}function emptyMap(){return Te||(Te=makeMap(0))}function updateMap(s,o,i){var a,u;if(s._root){var _=MakeRef(L),w=MakeRef(B);if(a=updateNode(s._root,s.__ownerID,0,void 0,o,i,_,w),!w.value)return s;u=s.size+(_.value?i===j?-1:1:0)}else{if(i===j)return s;u=1,a=new ArrayMapNode(s.__ownerID,[[o,i]])}return s.__ownerID?(s.size=u,s._root=a,s.__hash=void 0,s.__altered=!0,s):a?makeMap(u,a):emptyMap()}function updateNode(s,o,i,a,u,_,w,x){return s?s.update(o,i,a,u,_,w,x):_===j?s:(SetRef(x),SetRef(w),new ValueNode(o,a,[u,_]))}function isLeafNode(s){return s.constructor===ValueNode||s.constructor===HashCollisionNode}function mergeIntoNode(s,o,i,a,u){if(s.keyHash===a)return new HashCollisionNode(o,a,[s.entry,u]);var _,x=(0===i?s.keyHash:s.keyHash>>>i)&C,j=(0===i?a:a>>>i)&C;return new BitmapIndexedNode(o,1<>>=1)w[C]=1&i?o[_++]:void 0;return w[a]=u,new HashArrayMapNode(s,_+1,w)}function mergeIntoMapWith(s,o,i){for(var a=[],u=0;u>1&1431655765))+(s>>2&858993459))+(s>>4)&252645135,s+=s>>8,127&(s+=s>>16)}function setIn(s,o,i,a){var u=a?s:arrCopy(s);return u[o]=i,u}function spliceIn(s,o,i,a){var u=s.length+1;if(a&&o+1===u)return s[o]=i,s;for(var _=new Array(u),w=0,x=0;x=$e)return createNodes(s,C,a,u);var V=s&&s===this.ownerID,U=V?C:arrCopy(C);return $?x?L===B-1?U.pop():U[L]=U.pop():U[L]=[a,u]:U.push([a,u]),V?(this.entries=U,this):new ArrayMapNode(s,U)}},BitmapIndexedNode.prototype.get=function(s,o,i,a){void 0===o&&(o=hash(i));var u=1<<((0===s?o:o>>>s)&C),_=this.bitmap;return _&u?this.nodes[popCount(_&u-1)].get(s+w,o,i,a):a},BitmapIndexedNode.prototype.update=function(s,o,i,a,u,_,x){void 0===i&&(i=hash(a));var L=(0===o?i:i>>>o)&C,B=1<=ze)return expandNodes(s,z,$,L,Z);if(V&&!Z&&2===z.length&&isLeafNode(z[1^U]))return z[1^U];if(V&&Z&&1===z.length&&isLeafNode(Z))return Z;var ee=s&&s===this.ownerID,ie=V?Z?$:$^B:$|B,ae=V?Z?setIn(z,U,Z,ee):spliceOut(z,U,ee):spliceIn(z,U,Z,ee);return ee?(this.bitmap=ie,this.nodes=ae,this):new BitmapIndexedNode(s,ie,ae)},HashArrayMapNode.prototype.get=function(s,o,i,a){void 0===o&&(o=hash(i));var u=(0===s?o:o>>>s)&C,_=this.nodes[u];return _?_.get(s+w,o,i,a):a},HashArrayMapNode.prototype.update=function(s,o,i,a,u,_,x){void 0===i&&(i=hash(a));var L=(0===o?i:i>>>o)&C,B=u===j,$=this.nodes,V=$[L];if(B&&!V)return this;var U=updateNode(V,s,o+w,i,a,u,_,x);if(U===V)return this;var z=this.count;if(V){if(!U&&--z0&&a=0&&s>>o&C;if(a>=this.array.length)return new VNode([],s);var u,_=0===a;if(o>0){var x=this.array[a];if((u=x&&x.removeBefore(s,o-w,i))===x&&_)return this}if(_&&!u)return this;var j=editableVNode(this,s);if(!_)for(var L=0;L>>o&C;if(u>=this.array.length)return this;if(o>0){var _=this.array[u];if((a=_&&_.removeAfter(s,o-w,i))===_&&u===this.array.length-1)return this}var x=editableVNode(this,s);return x.array.splice(u+1),a&&(x.array[u]=a),x};var Ye,Qe,et={};function iterateList(s,o){var i=s._origin,a=s._capacity,u=getTailOffset(a),_=s._tail;return iterateNodeOrLeaf(s._root,s._level,0);function iterateNodeOrLeaf(s,o,i){return 0===o?iterateLeaf(s,i):iterateNode(s,o,i)}function iterateLeaf(s,w){var C=w===u?_&&_.array:s&&s.array,j=w>i?0:i-w,L=a-w;return L>x&&(L=x),function(){if(j===L)return et;var s=o?--L:j++;return C&&C[s]}}function iterateNode(s,u,_){var C,j=s&&s.array,L=_>i?0:i-_>>u,B=1+(a-_>>u);return B>x&&(B=x),function(){for(;;){if(C){var s=C();if(s!==et)return s;C=null}if(L===B)return et;var i=o?--B:L++;C=iterateNodeOrLeaf(j&&j[i],u-w,_+(i<=s.size||o<0)return s.withMutations((function(s){o<0?setListBounds(s,o).set(0,i):setListBounds(s,0,o+1).set(o,i)}));o+=s._origin;var a=s._tail,u=s._root,_=MakeRef(B);return o>=getTailOffset(s._capacity)?a=updateVNode(a,s.__ownerID,0,o,i,_):u=updateVNode(u,s.__ownerID,s._level,o,i,_),_.value?s.__ownerID?(s._root=u,s._tail=a,s.__hash=void 0,s.__altered=!0,s):makeList(s._origin,s._capacity,s._level,u,a):s}function updateVNode(s,o,i,a,u,_){var x,j=a>>>i&C,L=s&&j0){var B=s&&s.array[j],$=updateVNode(B,o,i-w,a,u,_);return $===B?s:((x=editableVNode(s,o)).array[j]=$,x)}return L&&s.array[j]===u?s:(SetRef(_),x=editableVNode(s,o),void 0===u&&j===x.array.length-1?x.array.pop():x.array[j]=u,x)}function editableVNode(s,o){return o&&s&&o===s.ownerID?s:new VNode(s?s.array.slice():[],o)}function listNodeFor(s,o){if(o>=getTailOffset(s._capacity))return s._tail;if(o<1<0;)i=i.array[o>>>a&C],a-=w;return i}}function setListBounds(s,o,i){void 0!==o&&(o|=0),void 0!==i&&(i|=0);var a=s.__ownerID||new OwnerID,u=s._origin,_=s._capacity,x=u+o,j=void 0===i?_:i<0?_+i:u+i;if(x===u&&j===_)return s;if(x>=j)return s.clear();for(var L=s._level,B=s._root,$=0;x+$<0;)B=new VNode(B&&B.array.length?[void 0,B]:[],a),$+=1<<(L+=w);$&&(x+=$,u+=$,j+=$,_+=$);for(var V=getTailOffset(_),U=getTailOffset(j);U>=1<V?new VNode([],a):z;if(z&&U>V&&x<_&&z.array.length){for(var Z=B=editableVNode(B,a),ee=L;ee>w;ee-=w){var ie=V>>>ee&C;Z=Z.array[ie]=editableVNode(Z.array[ie],a)}Z.array[V>>>w&C]=z}if(j<_&&(Y=Y&&Y.removeAfter(a,0,j)),x>=U)x-=U,j-=U,L=w,B=null,Y=Y&&Y.removeBefore(a,0,x);else if(x>u||U>>L&C;if(ae!==U>>>L&C)break;ae&&($+=(1<u&&(B=B.removeBefore(a,L,x-$)),B&&Uu&&(u=x.size),isIterable(w)||(x=x.map((function(s){return fromJS(s)}))),a.push(x)}return u>s.size&&(s=s.setSize(u)),mergeIntoCollectionWith(s,o,a)}function getTailOffset(s){return s>>w<=x&&w.size>=2*_.size?(a=(u=w.filter((function(s,o){return void 0!==s&&C!==o}))).toKeyedSeq().map((function(s){return s[0]})).flip().toMap(),s.__ownerID&&(a.__ownerID=u.__ownerID=s.__ownerID)):(a=_.remove(o),u=C===w.size-1?w.pop():w.set(C,void 0))}else if(L){if(i===w.get(C)[1])return s;a=_,u=w.set(C,[o,i])}else a=_.set(o,w.size),u=w.set(w.size,[o,i]);return s.__ownerID?(s.size=a.size,s._map=a,s._list=u,s.__hash=void 0,s):makeOrderedMap(a,u)}function ToKeyedSequence(s,o){this._iter=s,this._useKeys=o,this.size=s.size}function ToIndexedSequence(s){this._iter=s,this.size=s.size}function ToSetSequence(s){this._iter=s,this.size=s.size}function FromEntriesSequence(s){this._iter=s,this.size=s.size}function flipFactory(s){var o=makeSequence(s);return o._iter=s,o.size=s.size,o.flip=function(){return s},o.reverse=function(){var o=s.reverse.apply(this);return o.flip=function(){return s.reverse()},o},o.has=function(o){return s.includes(o)},o.includes=function(o){return s.has(o)},o.cacheResult=cacheResultThrough,o.__iterateUncached=function(o,i){var a=this;return s.__iterate((function(s,i){return!1!==o(i,s,a)}),i)},o.__iteratorUncached=function(o,i){if(o===U){var a=s.__iterator(o,i);return new Iterator((function(){var s=a.next();if(!s.done){var o=s.value[0];s.value[0]=s.value[1],s.value[1]=o}return s}))}return s.__iterator(o===V?$:V,i)},o}function mapFactory(s,o,i){var a=makeSequence(s);return a.size=s.size,a.has=function(o){return s.has(o)},a.get=function(a,u){var _=s.get(a,j);return _===j?u:o.call(i,_,a,s)},a.__iterateUncached=function(a,u){var _=this;return s.__iterate((function(s,u,w){return!1!==a(o.call(i,s,u,w),u,_)}),u)},a.__iteratorUncached=function(a,u){var _=s.__iterator(U,u);return new Iterator((function(){var u=_.next();if(u.done)return u;var w=u.value,x=w[0];return iteratorValue(a,x,o.call(i,w[1],x,s),u)}))},a}function reverseFactory(s,o){var i=makeSequence(s);return i._iter=s,i.size=s.size,i.reverse=function(){return s},s.flip&&(i.flip=function(){var o=flipFactory(s);return o.reverse=function(){return s.flip()},o}),i.get=function(i,a){return s.get(o?i:-1-i,a)},i.has=function(i){return s.has(o?i:-1-i)},i.includes=function(o){return s.includes(o)},i.cacheResult=cacheResultThrough,i.__iterate=function(o,i){var a=this;return s.__iterate((function(s,i){return o(s,i,a)}),!i)},i.__iterator=function(o,i){return s.__iterator(o,!i)},i}function filterFactory(s,o,i,a){var u=makeSequence(s);return a&&(u.has=function(a){var u=s.get(a,j);return u!==j&&!!o.call(i,u,a,s)},u.get=function(a,u){var _=s.get(a,j);return _!==j&&o.call(i,_,a,s)?_:u}),u.__iterateUncached=function(u,_){var w=this,x=0;return s.__iterate((function(s,_,C){if(o.call(i,s,_,C))return x++,u(s,a?_:x-1,w)}),_),x},u.__iteratorUncached=function(u,_){var w=s.__iterator(U,_),x=0;return new Iterator((function(){for(;;){var _=w.next();if(_.done)return _;var C=_.value,j=C[0],L=C[1];if(o.call(i,L,j,s))return iteratorValue(u,a?j:x++,L,_)}}))},u}function countByFactory(s,o,i){var a=Map().asMutable();return s.__iterate((function(u,_){a.update(o.call(i,u,_,s),0,(function(s){return s+1}))})),a.asImmutable()}function groupByFactory(s,o,i){var a=isKeyed(s),u=(isOrdered(s)?OrderedMap():Map()).asMutable();s.__iterate((function(_,w){u.update(o.call(i,_,w,s),(function(s){return(s=s||[]).push(a?[w,_]:_),s}))}));var _=iterableClass(s);return u.map((function(o){return reify(s,_(o))}))}function sliceFactory(s,o,i,a){var u=s.size;if(void 0!==o&&(o|=0),void 0!==i&&(i===1/0?i=u:i|=0),wholeSlice(o,i,u))return s;var _=resolveBegin(o,u),w=resolveEnd(i,u);if(_!=_||w!=w)return sliceFactory(s.toSeq().cacheResult(),o,i,a);var x,C=w-_;C==C&&(x=C<0?0:C);var j=makeSequence(s);return j.size=0===x?x:s.size&&x||void 0,!a&&isSeq(s)&&x>=0&&(j.get=function(o,i){return(o=wrapIndex(this,o))>=0&&ox)return iteratorDone();var s=u.next();return a||o===V?s:iteratorValue(o,C-1,o===$?void 0:s.value[1],s)}))},j}function takeWhileFactory(s,o,i){var a=makeSequence(s);return a.__iterateUncached=function(a,u){var _=this;if(u)return this.cacheResult().__iterate(a,u);var w=0;return s.__iterate((function(s,u,x){return o.call(i,s,u,x)&&++w&&a(s,u,_)})),w},a.__iteratorUncached=function(a,u){var _=this;if(u)return this.cacheResult().__iterator(a,u);var w=s.__iterator(U,u),x=!0;return new Iterator((function(){if(!x)return iteratorDone();var s=w.next();if(s.done)return s;var u=s.value,C=u[0],j=u[1];return o.call(i,j,C,_)?a===U?s:iteratorValue(a,C,j,s):(x=!1,iteratorDone())}))},a}function skipWhileFactory(s,o,i,a){var u=makeSequence(s);return u.__iterateUncached=function(u,_){var w=this;if(_)return this.cacheResult().__iterate(u,_);var x=!0,C=0;return s.__iterate((function(s,_,j){if(!x||!(x=o.call(i,s,_,j)))return C++,u(s,a?_:C-1,w)})),C},u.__iteratorUncached=function(u,_){var w=this;if(_)return this.cacheResult().__iterator(u,_);var x=s.__iterator(U,_),C=!0,j=0;return new Iterator((function(){var s,_,L;do{if((s=x.next()).done)return a||u===V?s:iteratorValue(u,j++,u===$?void 0:s.value[1],s);var B=s.value;_=B[0],L=B[1],C&&(C=o.call(i,L,_,w))}while(C);return u===U?s:iteratorValue(u,_,L,s)}))},u}function concatFactory(s,o){var i=isKeyed(s),a=[s].concat(o).map((function(s){return isIterable(s)?i&&(s=KeyedIterable(s)):s=i?keyedSeqFromValue(s):indexedSeqFromValue(Array.isArray(s)?s:[s]),s})).filter((function(s){return 0!==s.size}));if(0===a.length)return s;if(1===a.length){var u=a[0];if(u===s||i&&isKeyed(u)||isIndexed(s)&&isIndexed(u))return u}var _=new ArraySeq(a);return i?_=_.toKeyedSeq():isIndexed(s)||(_=_.toSetSeq()),(_=_.flatten(!0)).size=a.reduce((function(s,o){if(void 0!==s){var i=o.size;if(void 0!==i)return s+i}}),0),_}function flattenFactory(s,o,i){var a=makeSequence(s);return a.__iterateUncached=function(a,u){var _=0,w=!1;function flatDeep(s,x){var C=this;s.__iterate((function(s,u){return(!o||x0}function zipWithFactory(s,o,i){var a=makeSequence(s);return a.size=new ArraySeq(i).map((function(s){return s.size})).min(),a.__iterate=function(s,o){for(var i,a=this.__iterator(V,o),u=0;!(i=a.next()).done&&!1!==s(i.value,u++,this););return u},a.__iteratorUncached=function(s,a){var u=i.map((function(s){return s=Iterable(s),getIterator(a?s.reverse():s)})),_=0,w=!1;return new Iterator((function(){var i;return w||(i=u.map((function(s){return s.next()})),w=i.some((function(s){return s.done}))),w?iteratorDone():iteratorValue(s,_++,o.apply(null,i.map((function(s){return s.value}))))}))},a}function reify(s,o){return isSeq(s)?o:s.constructor(o)}function validateEntry(s){if(s!==Object(s))throw new TypeError("Expected [K, V] tuple: "+s)}function resolveSize(s){return assertNotInfinite(s.size),ensureSize(s)}function iterableClass(s){return isKeyed(s)?KeyedIterable:isIndexed(s)?IndexedIterable:SetIterable}function makeSequence(s){return Object.create((isKeyed(s)?KeyedSeq:isIndexed(s)?IndexedSeq:SetSeq).prototype)}function cacheResultThrough(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):Seq.prototype.cacheResult.call(this)}function defaultComparator(s,o){return s>o?1:s=0;i--)o={value:arguments[i],next:o};return this.__ownerID?(this.size=s,this._head=o,this.__hash=void 0,this.__altered=!0,this):makeStack(s,o)},Stack.prototype.pushAll=function(s){if(0===(s=IndexedIterable(s)).size)return this;assertNotInfinite(s.size);var o=this.size,i=this._head;return s.reverse().forEach((function(s){o++,i={value:s,next:i}})),this.__ownerID?(this.size=o,this._head=i,this.__hash=void 0,this.__altered=!0,this):makeStack(o,i)},Stack.prototype.pop=function(){return this.slice(1)},Stack.prototype.unshift=function(){return this.push.apply(this,arguments)},Stack.prototype.unshiftAll=function(s){return this.pushAll(s)},Stack.prototype.shift=function(){return this.pop.apply(this,arguments)},Stack.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):emptyStack()},Stack.prototype.slice=function(s,o){if(wholeSlice(s,o,this.size))return this;var i=resolveBegin(s,this.size);if(resolveEnd(o,this.size)!==this.size)return IndexedCollection.prototype.slice.call(this,s,o);for(var a=this.size-i,u=this._head;i--;)u=u.next;return this.__ownerID?(this.size=a,this._head=u,this.__hash=void 0,this.__altered=!0,this):makeStack(a,u)},Stack.prototype.__ensureOwner=function(s){return s===this.__ownerID?this:s?makeStack(this.size,this._head,s,this.__hash):(this.__ownerID=s,this.__altered=!1,this)},Stack.prototype.__iterate=function(s,o){if(o)return this.reverse().__iterate(s);for(var i=0,a=this._head;a&&!1!==s(a.value,i++,this);)a=a.next;return i},Stack.prototype.__iterator=function(s,o){if(o)return this.reverse().__iterator(s);var i=0,a=this._head;return new Iterator((function(){if(a){var o=a.value;return a=a.next,iteratorValue(s,i++,o)}return iteratorDone()}))},Stack.isStack=isStack;var at,ct="@@__IMMUTABLE_STACK__@@",lt=Stack.prototype;function makeStack(s,o,i,a){var u=Object.create(lt);return u.size=s,u._head=o,u.__ownerID=i,u.__hash=a,u.__altered=!1,u}function emptyStack(){return at||(at=makeStack(0))}function mixin(s,o){var keyCopier=function(i){s.prototype[i]=o[i]};return Object.keys(o).forEach(keyCopier),Object.getOwnPropertySymbols&&Object.getOwnPropertySymbols(o).forEach(keyCopier),s}lt[ct]=!0,lt.withMutations=qe.withMutations,lt.asMutable=qe.asMutable,lt.asImmutable=qe.asImmutable,lt.wasAltered=qe.wasAltered,Iterable.Iterator=Iterator,mixin(Iterable,{toArray:function(){assertNotInfinite(this.size);var s=new Array(this.size||0);return this.valueSeq().__iterate((function(o,i){s[i]=o})),s},toIndexedSeq:function(){return new ToIndexedSequence(this)},toJS:function(){return this.toSeq().map((function(s){return s&&"function"==typeof s.toJS?s.toJS():s})).__toJS()},toJSON:function(){return this.toSeq().map((function(s){return s&&"function"==typeof s.toJSON?s.toJSON():s})).__toJS()},toKeyedSeq:function(){return new ToKeyedSequence(this,!0)},toMap:function(){return Map(this.toKeyedSeq())},toObject:function(){assertNotInfinite(this.size);var s={};return this.__iterate((function(o,i){s[i]=o})),s},toOrderedMap:function(){return OrderedMap(this.toKeyedSeq())},toOrderedSet:function(){return OrderedSet(isKeyed(this)?this.valueSeq():this)},toSet:function(){return Set(isKeyed(this)?this.valueSeq():this)},toSetSeq:function(){return new ToSetSequence(this)},toSeq:function(){return isIndexed(this)?this.toIndexedSeq():isKeyed(this)?this.toKeyedSeq():this.toSetSeq()},toStack:function(){return Stack(isKeyed(this)?this.valueSeq():this)},toList:function(){return List(isKeyed(this)?this.valueSeq():this)},toString:function(){return"[Iterable]"},__toString:function(s,o){return 0===this.size?s+o:s+" "+this.toSeq().map(this.__toStringMapper).join(", ")+" "+o},concat:function(){return reify(this,concatFactory(this,s.call(arguments,0)))},includes:function(s){return this.some((function(o){return is(o,s)}))},entries:function(){return this.__iterator(U)},every:function(s,o){assertNotInfinite(this.size);var i=!0;return this.__iterate((function(a,u,_){if(!s.call(o,a,u,_))return i=!1,!1})),i},filter:function(s,o){return reify(this,filterFactory(this,s,o,!0))},find:function(s,o,i){var a=this.findEntry(s,o);return a?a[1]:i},forEach:function(s,o){return assertNotInfinite(this.size),this.__iterate(o?s.bind(o):s)},join:function(s){assertNotInfinite(this.size),s=void 0!==s?""+s:",";var o="",i=!0;return this.__iterate((function(a){i?i=!1:o+=s,o+=null!=a?a.toString():""})),o},keys:function(){return this.__iterator($)},map:function(s,o){return reify(this,mapFactory(this,s,o))},reduce:function(s,o,i){var a,u;return assertNotInfinite(this.size),arguments.length<2?u=!0:a=o,this.__iterate((function(o,_,w){u?(u=!1,a=o):a=s.call(i,a,o,_,w)})),a},reduceRight:function(s,o,i){var a=this.toKeyedSeq().reverse();return a.reduce.apply(a,arguments)},reverse:function(){return reify(this,reverseFactory(this,!0))},slice:function(s,o){return reify(this,sliceFactory(this,s,o,!0))},some:function(s,o){return!this.every(not(s),o)},sort:function(s){return reify(this,sortFactory(this,s))},values:function(){return this.__iterator(V)},butLast:function(){return this.slice(0,-1)},isEmpty:function(){return void 0!==this.size?0===this.size:!this.some((function(){return!0}))},count:function(s,o){return ensureSize(s?this.toSeq().filter(s,o):this)},countBy:function(s,o){return countByFactory(this,s,o)},equals:function(s){return deepEqual(this,s)},entrySeq:function(){var s=this;if(s._cache)return new ArraySeq(s._cache);var o=s.toSeq().map(entryMapper).toIndexedSeq();return o.fromEntrySeq=function(){return s.toSeq()},o},filterNot:function(s,o){return this.filter(not(s),o)},findEntry:function(s,o,i){var a=i;return this.__iterate((function(i,u,_){if(s.call(o,i,u,_))return a=[u,i],!1})),a},findKey:function(s,o){var i=this.findEntry(s,o);return i&&i[0]},findLast:function(s,o,i){return this.toKeyedSeq().reverse().find(s,o,i)},findLastEntry:function(s,o,i){return this.toKeyedSeq().reverse().findEntry(s,o,i)},findLastKey:function(s,o){return this.toKeyedSeq().reverse().findKey(s,o)},first:function(){return this.find(returnTrue)},flatMap:function(s,o){return reify(this,flatMapFactory(this,s,o))},flatten:function(s){return reify(this,flattenFactory(this,s,!0))},fromEntrySeq:function(){return new FromEntriesSequence(this)},get:function(s,o){return this.find((function(o,i){return is(i,s)}),void 0,o)},getIn:function(s,o){for(var i,a=this,u=forceIterator(s);!(i=u.next()).done;){var _=i.value;if((a=a&&a.get?a.get(_,j):j)===j)return o}return a},groupBy:function(s,o){return groupByFactory(this,s,o)},has:function(s){return this.get(s,j)!==j},hasIn:function(s){return this.getIn(s,j)!==j},isSubset:function(s){return s="function"==typeof s.includes?s:Iterable(s),this.every((function(o){return s.includes(o)}))},isSuperset:function(s){return(s="function"==typeof s.isSubset?s:Iterable(s)).isSubset(this)},keyOf:function(s){return this.findKey((function(o){return is(o,s)}))},keySeq:function(){return this.toSeq().map(keyMapper).toIndexedSeq()},last:function(){return this.toSeq().reverse().first()},lastKeyOf:function(s){return this.toKeyedSeq().reverse().keyOf(s)},max:function(s){return maxFactory(this,s)},maxBy:function(s,o){return maxFactory(this,o,s)},min:function(s){return maxFactory(this,s?neg(s):defaultNegComparator)},minBy:function(s,o){return maxFactory(this,o?neg(o):defaultNegComparator,s)},rest:function(){return this.slice(1)},skip:function(s){return this.slice(Math.max(0,s))},skipLast:function(s){return reify(this,this.toSeq().reverse().skip(s).reverse())},skipWhile:function(s,o){return reify(this,skipWhileFactory(this,s,o,!0))},skipUntil:function(s,o){return this.skipWhile(not(s),o)},sortBy:function(s,o){return reify(this,sortFactory(this,o,s))},take:function(s){return this.slice(0,Math.max(0,s))},takeLast:function(s){return reify(this,this.toSeq().reverse().take(s).reverse())},takeWhile:function(s,o){return reify(this,takeWhileFactory(this,s,o))},takeUntil:function(s,o){return this.takeWhile(not(s),o)},valueSeq:function(){return this.toIndexedSeq()},hashCode:function(){return this.__hash||(this.__hash=hashIterable(this))}});var ut=Iterable.prototype;ut[o]=!0,ut[Z]=ut.values,ut.__toJS=ut.toArray,ut.__toStringMapper=quoteString,ut.inspect=ut.toSource=function(){return this.toString()},ut.chain=ut.flatMap,ut.contains=ut.includes,mixin(KeyedIterable,{flip:function(){return reify(this,flipFactory(this))},mapEntries:function(s,o){var i=this,a=0;return reify(this,this.toSeq().map((function(u,_){return s.call(o,[_,u],a++,i)})).fromEntrySeq())},mapKeys:function(s,o){var i=this;return reify(this,this.toSeq().flip().map((function(a,u){return s.call(o,a,u,i)})).flip())}});var pt=KeyedIterable.prototype;function keyMapper(s,o){return o}function entryMapper(s,o){return[o,s]}function not(s){return function(){return!s.apply(this,arguments)}}function neg(s){return function(){return-s.apply(this,arguments)}}function quoteString(s){return"string"==typeof s?JSON.stringify(s):String(s)}function defaultZipper(){return arrCopy(arguments)}function defaultNegComparator(s,o){return so?-1:0}function hashIterable(s){if(s.size===1/0)return 0;var o=isOrdered(s),i=isKeyed(s),a=o?1:0;return murmurHashOfSize(s.__iterate(i?o?function(s,o){a=31*a+hashMerge(hash(s),hash(o))|0}:function(s,o){a=a+hashMerge(hash(s),hash(o))|0}:o?function(s){a=31*a+hash(s)|0}:function(s){a=a+hash(s)|0}),a)}function murmurHashOfSize(s,o){return o=le(o,3432918353),o=le(o<<15|o>>>-15,461845907),o=le(o<<13|o>>>-13,5),o=le((o=o+3864292196^s)^o>>>16,2246822507),o=smi((o=le(o^o>>>13,3266489909))^o>>>16)}function hashMerge(s,o){return s^o+2654435769+(s<<6)+(s>>2)}return pt[i]=!0,pt[Z]=ut.entries,pt.__toJS=ut.toObject,pt.__toStringMapper=function(s,o){return JSON.stringify(o)+": "+quoteString(s)},mixin(IndexedIterable,{toKeyedSeq:function(){return new ToKeyedSequence(this,!1)},filter:function(s,o){return reify(this,filterFactory(this,s,o,!1))},findIndex:function(s,o){var i=this.findEntry(s,o);return i?i[0]:-1},indexOf:function(s){var o=this.keyOf(s);return void 0===o?-1:o},lastIndexOf:function(s){var o=this.lastKeyOf(s);return void 0===o?-1:o},reverse:function(){return reify(this,reverseFactory(this,!1))},slice:function(s,o){return reify(this,sliceFactory(this,s,o,!1))},splice:function(s,o){var i=arguments.length;if(o=Math.max(0|o,0),0===i||2===i&&!o)return this;s=resolveBegin(s,s<0?this.count():this.size);var a=this.slice(0,s);return reify(this,1===i?a:a.concat(arrCopy(arguments,2),this.slice(s+o)))},findLastIndex:function(s,o){var i=this.findLastEntry(s,o);return i?i[0]:-1},first:function(){return this.get(0)},flatten:function(s){return reify(this,flattenFactory(this,s,!1))},get:function(s,o){return(s=wrapIndex(this,s))<0||this.size===1/0||void 0!==this.size&&s>this.size?o:this.find((function(o,i){return i===s}),void 0,o)},has:function(s){return(s=wrapIndex(this,s))>=0&&(void 0!==this.size?this.size===1/0||s{"use strict";i(71340);var a=i(92046);s.exports=a.Object.assign},9999:(s,o,i)=>{var a=i(37217),u=i(83729),_=i(16547),w=i(74733),x=i(43838),C=i(93290),j=i(23007),L=i(92271),B=i(48948),$=i(50002),V=i(83349),U=i(5861),z=i(76189),Y=i(77199),Z=i(35529),ee=i(56449),ie=i(3656),ae=i(87730),ce=i(23805),le=i(38440),pe=i(95950),de=i(37241),fe="[object Arguments]",ye="[object Function]",be="[object Object]",_e={};_e[fe]=_e["[object Array]"]=_e["[object ArrayBuffer]"]=_e["[object DataView]"]=_e["[object Boolean]"]=_e["[object Date]"]=_e["[object Float32Array]"]=_e["[object Float64Array]"]=_e["[object Int8Array]"]=_e["[object Int16Array]"]=_e["[object Int32Array]"]=_e["[object Map]"]=_e["[object Number]"]=_e[be]=_e["[object RegExp]"]=_e["[object Set]"]=_e["[object String]"]=_e["[object Symbol]"]=_e["[object Uint8Array]"]=_e["[object Uint8ClampedArray]"]=_e["[object Uint16Array]"]=_e["[object Uint32Array]"]=!0,_e["[object Error]"]=_e[ye]=_e["[object WeakMap]"]=!1,s.exports=function baseClone(s,o,i,Se,we,xe){var Pe,Te=1&o,Re=2&o,qe=4&o;if(i&&(Pe=we?i(s,Se,we,xe):i(s)),void 0!==Pe)return Pe;if(!ce(s))return s;var $e=ee(s);if($e){if(Pe=z(s),!Te)return j(s,Pe)}else{var ze=U(s),We=ze==ye||"[object GeneratorFunction]"==ze;if(ie(s))return C(s,Te);if(ze==be||ze==fe||We&&!we){if(Pe=Re||We?{}:Z(s),!Te)return Re?B(s,x(Pe,s)):L(s,w(Pe,s))}else{if(!_e[ze])return we?s:{};Pe=Y(s,ze,Te)}}xe||(xe=new a);var He=xe.get(s);if(He)return He;xe.set(s,Pe),le(s)?s.forEach((function(a){Pe.add(baseClone(a,o,i,a,s,xe))})):ae(s)&&s.forEach((function(a,u){Pe.set(u,baseClone(a,o,i,u,s,xe))}));var Xe=$e?void 0:(qe?Re?V:$:Re?de:pe)(s);return u(Xe||s,(function(a,u){Xe&&(a=s[u=a]),_(Pe,u,baseClone(a,o,i,u,s,xe))})),Pe}},10023:(s,o,i)=>{const a=i(6205),INTS=()=>[{type:a.RANGE,from:48,to:57}],WORDS=()=>[{type:a.CHAR,value:95},{type:a.RANGE,from:97,to:122},{type:a.RANGE,from:65,to:90}].concat(INTS()),WHITESPACE=()=>[{type:a.CHAR,value:9},{type:a.CHAR,value:10},{type:a.CHAR,value:11},{type:a.CHAR,value:12},{type:a.CHAR,value:13},{type:a.CHAR,value:32},{type:a.CHAR,value:160},{type:a.CHAR,value:5760},{type:a.RANGE,from:8192,to:8202},{type:a.CHAR,value:8232},{type:a.CHAR,value:8233},{type:a.CHAR,value:8239},{type:a.CHAR,value:8287},{type:a.CHAR,value:12288},{type:a.CHAR,value:65279}];o.words=()=>({type:a.SET,set:WORDS(),not:!1}),o.notWords=()=>({type:a.SET,set:WORDS(),not:!0}),o.ints=()=>({type:a.SET,set:INTS(),not:!1}),o.notInts=()=>({type:a.SET,set:INTS(),not:!0}),o.whitespace=()=>({type:a.SET,set:WHITESPACE(),not:!1}),o.notWhitespace=()=>({type:a.SET,set:WHITESPACE(),not:!0}),o.anyChar=()=>({type:a.SET,set:[{type:a.CHAR,value:10},{type:a.CHAR,value:13},{type:a.CHAR,value:8232},{type:a.CHAR,value:8233}],not:!0})},10043:(s,o,i)=>{"use strict";var a=i(54018),u=String,_=TypeError;s.exports=function(s){if(a(s))return s;throw new _("Can't set "+u(s)+" as a prototype")}},10124:(s,o,i)=>{var a=i(9325);s.exports=function(){return a.Date.now()}},10300:(s,o,i)=>{"use strict";var a=i(13930),u=i(82159),_=i(36624),w=i(4640),x=i(73448),C=TypeError;s.exports=function(s,o){var i=arguments.length<2?x(s):o;if(u(i))return _(a(i,s));throw new C(w(s)+" is not iterable")}},10316:(s,o,i)=>{const a=i(2404),u=i(55973),_=i(92340);class Element{constructor(s,o,i){o&&(this.meta=o),i&&(this.attributes=i),this.content=s}freeze(){Object.isFrozen(this)||(this._meta&&(this.meta.parent=this,this.meta.freeze()),this._attributes&&(this.attributes.parent=this,this.attributes.freeze()),this.children.forEach((s=>{s.parent=this,s.freeze()}),this),this.content&&Array.isArray(this.content)&&Object.freeze(this.content),Object.freeze(this))}primitive(){}clone(){const s=new this.constructor;return s.element=this.element,this.meta.length&&(s._meta=this.meta.clone()),this.attributes.length&&(s._attributes=this.attributes.clone()),this.content?this.content.clone?s.content=this.content.clone():Array.isArray(this.content)?s.content=this.content.map((s=>s.clone())):s.content=this.content:s.content=this.content,s}toValue(){return this.content instanceof Element?this.content.toValue():this.content instanceof u?{key:this.content.key.toValue(),value:this.content.value?this.content.value.toValue():void 0}:this.content&&this.content.map?this.content.map((s=>s.toValue()),this):this.content}toRef(s){if(""===this.id.toValue())throw Error("Cannot create reference to an element that does not contain an ID");const o=new this.RefElement(this.id.toValue());return s&&(o.path=s),o}findRecursive(...s){if(arguments.length>1&&!this.isFrozen)throw new Error("Cannot find recursive with multiple element names without first freezing the element. Call `element.freeze()`");const o=s.pop();let i=new _;const append=(s,o)=>(s.push(o),s),checkElement=(s,i)=>{i.element===o&&s.push(i);const a=i.findRecursive(o);return a&&a.reduce(append,s),i.content instanceof u&&(i.content.key&&checkElement(s,i.content.key),i.content.value&&checkElement(s,i.content.value)),s};return this.content&&(this.content.element&&checkElement(i,this.content),Array.isArray(this.content)&&this.content.reduce(checkElement,i)),s.isEmpty||(i=i.filter((o=>{let i=o.parents.map((s=>s.element));for(const o in s){const a=s[o],u=i.indexOf(a);if(-1===u)return!1;i=i.splice(0,u)}return!0}))),i}set(s){return this.content=s,this}equals(s){return a(this.toValue(),s)}getMetaProperty(s,o){if(!this.meta.hasKey(s)){if(this.isFrozen){const s=this.refract(o);return s.freeze(),s}this.meta.set(s,o)}return this.meta.get(s)}setMetaProperty(s,o){this.meta.set(s,o)}get element(){return this._storedElement||"element"}set element(s){this._storedElement=s}get content(){return this._content}set content(s){if(s instanceof Element)this._content=s;else if(s instanceof _)this.content=s.elements;else if("string"==typeof s||"number"==typeof s||"boolean"==typeof s||"null"===s||null==s)this._content=s;else if(s instanceof u)this._content=s;else if(Array.isArray(s))this._content=s.map(this.refract);else{if("object"!=typeof s)throw new Error("Cannot set content to given value");this._content=Object.keys(s).map((o=>new this.MemberElement(o,s[o])))}}get meta(){if(!this._meta){if(this.isFrozen){const s=new this.ObjectElement;return s.freeze(),s}this._meta=new this.ObjectElement}return this._meta}set meta(s){s instanceof this.ObjectElement?this._meta=s:this.meta.set(s||{})}get attributes(){if(!this._attributes){if(this.isFrozen){const s=new this.ObjectElement;return s.freeze(),s}this._attributes=new this.ObjectElement}return this._attributes}set attributes(s){s instanceof this.ObjectElement?this._attributes=s:this.attributes.set(s||{})}get id(){return this.getMetaProperty("id","")}set id(s){this.setMetaProperty("id",s)}get classes(){return this.getMetaProperty("classes",[])}set classes(s){this.setMetaProperty("classes",s)}get title(){return this.getMetaProperty("title","")}set title(s){this.setMetaProperty("title",s)}get description(){return this.getMetaProperty("description","")}set description(s){this.setMetaProperty("description",s)}get links(){return this.getMetaProperty("links",[])}set links(s){this.setMetaProperty("links",s)}get isFrozen(){return Object.isFrozen(this)}get parents(){let{parent:s}=this;const o=new _;for(;s;)o.push(s),s=s.parent;return o}get children(){if(Array.isArray(this.content))return new _(this.content);if(this.content instanceof u){const s=new _([this.content.key]);return this.content.value&&s.push(this.content.value),s}return this.content instanceof Element?new _([this.content]):new _}get recursiveChildren(){const s=new _;return this.children.forEach((o=>{s.push(o),o.recursiveChildren.forEach((o=>{s.push(o)}))})),s}}s.exports=Element},10392:s=>{s.exports=function getValue(s,o){return null==s?void 0:s[o]}},10776:(s,o,i)=>{var a=i(30756),u=i(95950);s.exports=function getMatchData(s){for(var o=u(s),i=o.length;i--;){var _=o[i],w=s[_];o[i]=[_,w,a(w)]}return o}},10866:(s,o,i)=>{const a=i(6048),u=i(92340);class ObjectSlice extends u{map(s,o){return this.elements.map((i=>s.bind(o)(i.value,i.key,i)))}filter(s,o){return new ObjectSlice(this.elements.filter((i=>s.bind(o)(i.value,i.key,i))))}reject(s,o){return this.filter(a(s.bind(o)))}forEach(s,o){return this.elements.forEach(((i,a)=>{s.bind(o)(i.value,i.key,i,a)}))}keys(){return this.map(((s,o)=>o.toValue()))}values(){return this.map((s=>s.toValue()))}}s.exports=ObjectSlice},11042:(s,o,i)=>{"use strict";var a=i(85582),u=i(1907),_=i(24443),w=i(87170),x=i(36624),C=u([].concat);s.exports=a("Reflect","ownKeys")||function ownKeys(s){var o=_.f(x(s)),i=w.f;return i?C(o,i(s)):o}},11091:(s,o,i)=>{"use strict";var a=i(45951),u=i(76024),_=i(92361),w=i(62250),x=i(13846).f,C=i(7463),j=i(92046),L=i(28311),B=i(61626),$=i(49724);i(36128);var wrapConstructor=function(s){var Wrapper=function(o,i,a){if(this instanceof Wrapper){switch(arguments.length){case 0:return new s;case 1:return new s(o);case 2:return new s(o,i)}return new s(o,i,a)}return u(s,this,arguments)};return Wrapper.prototype=s.prototype,Wrapper};s.exports=function(s,o){var i,u,V,U,z,Y,Z,ee,ie,ae=s.target,ce=s.global,le=s.stat,pe=s.proto,de=ce?a:le?a[ae]:a[ae]&&a[ae].prototype,fe=ce?j:j[ae]||B(j,ae,{})[ae],ye=fe.prototype;for(U in o)u=!(i=C(ce?U:ae+(le?".":"#")+U,s.forced))&&de&&$(de,U),Y=fe[U],u&&(Z=s.dontCallGetSet?(ie=x(de,U))&&ie.value:de[U]),z=u&&Z?Z:o[U],(i||pe||typeof Y!=typeof z)&&(ee=s.bind&&u?L(z,a):s.wrap&&u?wrapConstructor(z):pe&&w(z)?_(z):z,(s.sham||z&&z.sham||Y&&Y.sham)&&B(ee,"sham",!0),B(fe,U,ee),pe&&($(j,V=ae+"Prototype")||B(j,V,{}),B(j[V],U,z),s.real&&ye&&(i||!ye[U])&&B(ye,U,z)))}},11287:s=>{s.exports=function getHolder(s){return s.placeholder}},11331:(s,o,i)=>{var a=i(72552),u=i(28879),_=i(40346),w=Function.prototype,x=Object.prototype,C=w.toString,j=x.hasOwnProperty,L=C.call(Object);s.exports=function isPlainObject(s){if(!_(s)||"[object Object]"!=a(s))return!1;var o=u(s);if(null===o)return!0;var i=j.call(o,"constructor")&&o.constructor;return"function"==typeof i&&i instanceof i&&C.call(i)==L}},11470:(s,o,i)=>{"use strict";var a=i(1907),u=i(65482),_=i(90160),w=i(74239),x=a("".charAt),C=a("".charCodeAt),j=a("".slice),createMethod=function(s){return function(o,i){var a,L,B=_(w(o)),$=u(i),V=B.length;return $<0||$>=V?s?"":void 0:(a=C(B,$))<55296||a>56319||$+1===V||(L=C(B,$+1))<56320||L>57343?s?x(B,$):a:s?j(B,$,$+2):L-56320+(a-55296<<10)+65536}};s.exports={codeAt:createMethod(!1),charAt:createMethod(!0)}},11842:(s,o,i)=>{var a=i(82819),u=i(9325);s.exports=function createBind(s,o,i){var _=1&o,w=a(s);return function wrapper(){return(this&&this!==u&&this instanceof wrapper?w:s).apply(_?i:this,arguments)}}},12242:(s,o,i)=>{const a=i(10316);s.exports=class BooleanElement extends a{constructor(s,o,i){super(s,o,i),this.element="boolean"}primitive(){return"boolean"}}},12507:(s,o,i)=>{var a=i(28754),u=i(49698),_=i(63912),w=i(13222);s.exports=function createCaseFirst(s){return function(o){o=w(o);var i=u(o)?_(o):void 0,x=i?i[0]:o.charAt(0),C=i?a(i,1).join(""):o.slice(1);return x[s]()+C}}},12560:(s,o,i)=>{"use strict";i(99363);var a=i(19287),u=i(45951),_=i(14840),w=i(93742);for(var x in a)_(u[x],x),w[x]=w.Array},12651:(s,o,i)=>{var a=i(74218);s.exports=function getMapData(s,o){var i=s.__data__;return a(o)?i["string"==typeof o?"string":"hash"]:i.map}},12749:(s,o,i)=>{var a=i(81042),u=Object.prototype.hasOwnProperty;s.exports=function hashHas(s){var o=this.__data__;return a?void 0!==o[s]:u.call(o,s)}},13222:(s,o,i)=>{var a=i(77556);s.exports=function toString(s){return null==s?"":a(s)}},13846:(s,o,i)=>{"use strict";var a=i(39447),u=i(13930),_=i(22574),w=i(75817),x=i(4993),C=i(70470),j=i(49724),L=i(73648),B=Object.getOwnPropertyDescriptor;o.f=a?B:function getOwnPropertyDescriptor(s,o){if(s=x(s),o=C(o),L)try{return B(s,o)}catch(s){}if(j(s,o))return w(!u(_.f,s,o),s[o])}},13930:(s,o,i)=>{"use strict";var a=i(41505),u=Function.prototype.call;s.exports=a?u.bind(u):function(){return u.apply(u,arguments)}},14248:s=>{s.exports=function arraySome(s,o){for(var i=-1,a=null==s?0:s.length;++i{s.exports=function arrayPush(s,o){for(var i=-1,a=o.length,u=s.length;++i{const a=i(10316);s.exports=class RefElement extends a{constructor(s,o,i){super(s||[],o,i),this.element="ref",this.path||(this.path="element")}get path(){return this.attributes.get("path")}set path(s){this.attributes.set("path",s)}}},14744:s=>{"use strict";var o=function isMergeableObject(s){return function isNonNullObject(s){return!!s&&"object"==typeof s}(s)&&!function isSpecial(s){var o=Object.prototype.toString.call(s);return"[object RegExp]"===o||"[object Date]"===o||function isReactElement(s){return s.$$typeof===i}(s)}(s)};var i="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function cloneUnlessOtherwiseSpecified(s,o){return!1!==o.clone&&o.isMergeableObject(s)?deepmerge(function emptyTarget(s){return Array.isArray(s)?[]:{}}(s),s,o):s}function defaultArrayMerge(s,o,i){return s.concat(o).map((function(s){return cloneUnlessOtherwiseSpecified(s,i)}))}function getKeys(s){return Object.keys(s).concat(function getEnumerableOwnPropertySymbols(s){return Object.getOwnPropertySymbols?Object.getOwnPropertySymbols(s).filter((function(o){return Object.propertyIsEnumerable.call(s,o)})):[]}(s))}function propertyIsOnObject(s,o){try{return o in s}catch(s){return!1}}function mergeObject(s,o,i){var a={};return i.isMergeableObject(s)&&getKeys(s).forEach((function(o){a[o]=cloneUnlessOtherwiseSpecified(s[o],i)})),getKeys(o).forEach((function(u){(function propertyIsUnsafe(s,o){return propertyIsOnObject(s,o)&&!(Object.hasOwnProperty.call(s,o)&&Object.propertyIsEnumerable.call(s,o))})(s,u)||(propertyIsOnObject(s,u)&&i.isMergeableObject(o[u])?a[u]=function getMergeFunction(s,o){if(!o.customMerge)return deepmerge;var i=o.customMerge(s);return"function"==typeof i?i:deepmerge}(u,i)(s[u],o[u],i):a[u]=cloneUnlessOtherwiseSpecified(o[u],i))})),a}function deepmerge(s,i,a){(a=a||{}).arrayMerge=a.arrayMerge||defaultArrayMerge,a.isMergeableObject=a.isMergeableObject||o,a.cloneUnlessOtherwiseSpecified=cloneUnlessOtherwiseSpecified;var u=Array.isArray(i);return u===Array.isArray(s)?u?a.arrayMerge(s,i,a):mergeObject(s,i,a):cloneUnlessOtherwiseSpecified(i,a)}deepmerge.all=function deepmergeAll(s,o){if(!Array.isArray(s))throw new Error("first argument should be an array");return s.reduce((function(s,i){return deepmerge(s,i,o)}),{})};var a=deepmerge;s.exports=a},14792:(s,o,i)=>{var a=i(13222),u=i(55808);s.exports=function capitalize(s){return u(a(s).toLowerCase())}},14840:(s,o,i)=>{"use strict";var a=i(52623),u=i(74284).f,_=i(61626),w=i(49724),x=i(54878),C=i(76264)("toStringTag");s.exports=function(s,o,i,j){var L=i?s:s&&s.prototype;L&&(w(L,C)||u(L,C,{configurable:!0,value:o}),j&&!a&&_(L,"toString",x))}},14974:s=>{s.exports=function safeGet(s,o){if(("constructor"!==o||"function"!=typeof s[o])&&"__proto__"!=o)return s[o]}},15287:(s,o)=>{"use strict";var i=Symbol.for("react.element"),a=Symbol.for("react.portal"),u=Symbol.for("react.fragment"),_=Symbol.for("react.strict_mode"),w=Symbol.for("react.profiler"),x=Symbol.for("react.provider"),C=Symbol.for("react.context"),j=Symbol.for("react.forward_ref"),L=Symbol.for("react.suspense"),B=Symbol.for("react.memo"),$=Symbol.for("react.lazy"),V=Symbol.iterator;var U={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},z=Object.assign,Y={};function E(s,o,i){this.props=s,this.context=o,this.refs=Y,this.updater=i||U}function F(){}function G(s,o,i){this.props=s,this.context=o,this.refs=Y,this.updater=i||U}E.prototype.isReactComponent={},E.prototype.setState=function(s,o){if("object"!=typeof s&&"function"!=typeof s&&null!=s)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,s,o,"setState")},E.prototype.forceUpdate=function(s){this.updater.enqueueForceUpdate(this,s,"forceUpdate")},F.prototype=E.prototype;var Z=G.prototype=new F;Z.constructor=G,z(Z,E.prototype),Z.isPureReactComponent=!0;var ee=Array.isArray,ie=Object.prototype.hasOwnProperty,ae={current:null},ce={key:!0,ref:!0,__self:!0,__source:!0};function M(s,o,a){var u,_={},w=null,x=null;if(null!=o)for(u in void 0!==o.ref&&(x=o.ref),void 0!==o.key&&(w=""+o.key),o)ie.call(o,u)&&!ce.hasOwnProperty(u)&&(_[u]=o[u]);var C=arguments.length-2;if(1===C)_.children=a;else if(1{var a=i(96131);s.exports=function arrayIncludes(s,o){return!!(null==s?0:s.length)&&a(s,o,0)>-1}},15340:()=>{},15389:(s,o,i)=>{var a=i(93663),u=i(87978),_=i(83488),w=i(56449),x=i(50583);s.exports=function baseIteratee(s){return"function"==typeof s?s:null==s?_:"object"==typeof s?w(s)?u(s[0],s[1]):a(s):x(s)}},15972:(s,o,i)=>{"use strict";var a=i(49724),u=i(62250),_=i(39298),w=i(92522),x=i(57382),C=w("IE_PROTO"),j=Object,L=j.prototype;s.exports=x?j.getPrototypeOf:function(s){var o=_(s);if(a(o,C))return o[C];var i=o.constructor;return u(i)&&o instanceof i?i.prototype:o instanceof j?L:null}},16038:(s,o,i)=>{var a=i(5861),u=i(40346);s.exports=function baseIsSet(s){return u(s)&&"[object Set]"==a(s)}},16426:s=>{s.exports=function(){var s=document.getSelection();if(!s.rangeCount)return function(){};for(var o=document.activeElement,i=[],a=0;a{var a=i(43360),u=i(75288),_=Object.prototype.hasOwnProperty;s.exports=function assignValue(s,o,i){var w=s[o];_.call(s,o)&&u(w,i)&&(void 0!==i||o in s)||a(s,o,i)}},16708:(s,o,i)=>{"use strict";var a,u=i(65606);function CorkedRequest(s){var o=this;this.next=null,this.entry=null,this.finish=function(){!function onCorkedFinish(s,o,i){var a=s.entry;s.entry=null;for(;a;){var u=a.callback;o.pendingcb--,u(i),a=a.next}o.corkedRequestsFree.next=s}(o,s)}}s.exports=Writable,Writable.WritableState=WritableState;var _={deprecate:i(94643)},w=i(40345),x=i(48287).Buffer,C=(void 0!==i.g?i.g:"undefined"!=typeof window?window:"undefined"!=typeof self?self:{}).Uint8Array||function(){};var j,L=i(75896),B=i(65291).getHighWaterMark,$=i(86048).F,V=$.ERR_INVALID_ARG_TYPE,U=$.ERR_METHOD_NOT_IMPLEMENTED,z=$.ERR_MULTIPLE_CALLBACK,Y=$.ERR_STREAM_CANNOT_PIPE,Z=$.ERR_STREAM_DESTROYED,ee=$.ERR_STREAM_NULL_VALUES,ie=$.ERR_STREAM_WRITE_AFTER_END,ae=$.ERR_UNKNOWN_ENCODING,ce=L.errorOrDestroy;function nop(){}function WritableState(s,o,_){a=a||i(25382),s=s||{},"boolean"!=typeof _&&(_=o instanceof a),this.objectMode=!!s.objectMode,_&&(this.objectMode=this.objectMode||!!s.writableObjectMode),this.highWaterMark=B(this,s,"writableHighWaterMark",_),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var w=!1===s.decodeStrings;this.decodeStrings=!w,this.defaultEncoding=s.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(s){!function onwrite(s,o){var i=s._writableState,a=i.sync,_=i.writecb;if("function"!=typeof _)throw new z;if(function onwriteStateUpdate(s){s.writing=!1,s.writecb=null,s.length-=s.writelen,s.writelen=0}(i),o)!function onwriteError(s,o,i,a,_){--o.pendingcb,i?(u.nextTick(_,a),u.nextTick(finishMaybe,s,o),s._writableState.errorEmitted=!0,ce(s,a)):(_(a),s._writableState.errorEmitted=!0,ce(s,a),finishMaybe(s,o))}(s,i,a,o,_);else{var w=needFinish(i)||s.destroyed;w||i.corked||i.bufferProcessing||!i.bufferedRequest||clearBuffer(s,i),a?u.nextTick(afterWrite,s,i,w,_):afterWrite(s,i,w,_)}}(o,s)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.emitClose=!1!==s.emitClose,this.autoDestroy=!!s.autoDestroy,this.bufferedRequestCount=0,this.corkedRequestsFree=new CorkedRequest(this)}function Writable(s){var o=this instanceof(a=a||i(25382));if(!o&&!j.call(Writable,this))return new Writable(s);this._writableState=new WritableState(s,this,o),this.writable=!0,s&&("function"==typeof s.write&&(this._write=s.write),"function"==typeof s.writev&&(this._writev=s.writev),"function"==typeof s.destroy&&(this._destroy=s.destroy),"function"==typeof s.final&&(this._final=s.final)),w.call(this)}function doWrite(s,o,i,a,u,_,w){o.writelen=a,o.writecb=w,o.writing=!0,o.sync=!0,o.destroyed?o.onwrite(new Z("write")):i?s._writev(u,o.onwrite):s._write(u,_,o.onwrite),o.sync=!1}function afterWrite(s,o,i,a){i||function onwriteDrain(s,o){0===o.length&&o.needDrain&&(o.needDrain=!1,s.emit("drain"))}(s,o),o.pendingcb--,a(),finishMaybe(s,o)}function clearBuffer(s,o){o.bufferProcessing=!0;var i=o.bufferedRequest;if(s._writev&&i&&i.next){var a=o.bufferedRequestCount,u=new Array(a),_=o.corkedRequestsFree;_.entry=i;for(var w=0,x=!0;i;)u[w]=i,i.isBuf||(x=!1),i=i.next,w+=1;u.allBuffers=x,doWrite(s,o,!0,o.length,u,"",_.finish),o.pendingcb++,o.lastBufferedRequest=null,_.next?(o.corkedRequestsFree=_.next,_.next=null):o.corkedRequestsFree=new CorkedRequest(o),o.bufferedRequestCount=0}else{for(;i;){var C=i.chunk,j=i.encoding,L=i.callback;if(doWrite(s,o,!1,o.objectMode?1:C.length,C,j,L),i=i.next,o.bufferedRequestCount--,o.writing)break}null===i&&(o.lastBufferedRequest=null)}o.bufferedRequest=i,o.bufferProcessing=!1}function needFinish(s){return s.ending&&0===s.length&&null===s.bufferedRequest&&!s.finished&&!s.writing}function callFinal(s,o){s._final((function(i){o.pendingcb--,i&&ce(s,i),o.prefinished=!0,s.emit("prefinish"),finishMaybe(s,o)}))}function finishMaybe(s,o){var i=needFinish(o);if(i&&(function prefinish(s,o){o.prefinished||o.finalCalled||("function"!=typeof s._final||o.destroyed?(o.prefinished=!0,s.emit("prefinish")):(o.pendingcb++,o.finalCalled=!0,u.nextTick(callFinal,s,o)))}(s,o),0===o.pendingcb&&(o.finished=!0,s.emit("finish"),o.autoDestroy))){var a=s._readableState;(!a||a.autoDestroy&&a.endEmitted)&&s.destroy()}return i}i(56698)(Writable,w),WritableState.prototype.getBuffer=function getBuffer(){for(var s=this.bufferedRequest,o=[];s;)o.push(s),s=s.next;return o},function(){try{Object.defineProperty(WritableState.prototype,"buffer",{get:_.deprecate((function writableStateBufferGetter(){return this.getBuffer()}),"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(s){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(j=Function.prototype[Symbol.hasInstance],Object.defineProperty(Writable,Symbol.hasInstance,{value:function value(s){return!!j.call(this,s)||this===Writable&&(s&&s._writableState instanceof WritableState)}})):j=function realHasInstance(s){return s instanceof this},Writable.prototype.pipe=function(){ce(this,new Y)},Writable.prototype.write=function(s,o,i){var a=this._writableState,_=!1,w=!a.objectMode&&function _isUint8Array(s){return x.isBuffer(s)||s instanceof C}(s);return w&&!x.isBuffer(s)&&(s=function _uint8ArrayToBuffer(s){return x.from(s)}(s)),"function"==typeof o&&(i=o,o=null),w?o="buffer":o||(o=a.defaultEncoding),"function"!=typeof i&&(i=nop),a.ending?function writeAfterEnd(s,o){var i=new ie;ce(s,i),u.nextTick(o,i)}(this,i):(w||function validChunk(s,o,i,a){var _;return null===i?_=new ee:"string"==typeof i||o.objectMode||(_=new V("chunk",["string","Buffer"],i)),!_||(ce(s,_),u.nextTick(a,_),!1)}(this,a,s,i))&&(a.pendingcb++,_=function writeOrBuffer(s,o,i,a,u,_){if(!i){var w=function decodeChunk(s,o,i){s.objectMode||!1===s.decodeStrings||"string"!=typeof o||(o=x.from(o,i));return o}(o,a,u);a!==w&&(i=!0,u="buffer",a=w)}var C=o.objectMode?1:a.length;o.length+=C;var j=o.length-1))throw new ae(s);return this._writableState.defaultEncoding=s,this},Object.defineProperty(Writable.prototype,"writableBuffer",{enumerable:!1,get:function get(){return this._writableState&&this._writableState.getBuffer()}}),Object.defineProperty(Writable.prototype,"writableHighWaterMark",{enumerable:!1,get:function get(){return this._writableState.highWaterMark}}),Writable.prototype._write=function(s,o,i){i(new U("_write()"))},Writable.prototype._writev=null,Writable.prototype.end=function(s,o,i){var a=this._writableState;return"function"==typeof s?(i=s,s=null,o=null):"function"==typeof o&&(i=o,o=null),null!=s&&this.write(s,o),a.corked&&(a.corked=1,this.uncork()),a.ending||function endWritable(s,o,i){o.ending=!0,finishMaybe(s,o),i&&(o.finished?u.nextTick(i):s.once("finish",i));o.ended=!0,s.writable=!1}(this,a,i),this},Object.defineProperty(Writable.prototype,"writableLength",{enumerable:!1,get:function get(){return this._writableState.length}}),Object.defineProperty(Writable.prototype,"destroyed",{enumerable:!1,get:function get(){return void 0!==this._writableState&&this._writableState.destroyed},set:function set(s){this._writableState&&(this._writableState.destroyed=s)}}),Writable.prototype.destroy=L.destroy,Writable.prototype._undestroy=L.undestroy,Writable.prototype._destroy=function(s,o){o(s)}},16946:(s,o,i)=>{"use strict";var a=i(1907),u=i(98828),_=i(45807),w=Object,x=a("".split);s.exports=u((function(){return!w("z").propertyIsEnumerable(0)}))?function(s){return"String"===_(s)?x(s,""):w(s)}:w},16962:(s,o)=>{o.aliasToReal={each:"forEach",eachRight:"forEachRight",entries:"toPairs",entriesIn:"toPairsIn",extend:"assignIn",extendAll:"assignInAll",extendAllWith:"assignInAllWith",extendWith:"assignInWith",first:"head",conforms:"conformsTo",matches:"isMatch",property:"get",__:"placeholder",F:"stubFalse",T:"stubTrue",all:"every",allPass:"overEvery",always:"constant",any:"some",anyPass:"overSome",apply:"spread",assoc:"set",assocPath:"set",complement:"negate",compose:"flowRight",contains:"includes",dissoc:"unset",dissocPath:"unset",dropLast:"dropRight",dropLastWhile:"dropRightWhile",equals:"isEqual",identical:"eq",indexBy:"keyBy",init:"initial",invertObj:"invert",juxt:"over",omitAll:"omit",nAry:"ary",path:"get",pathEq:"matchesProperty",pathOr:"getOr",paths:"at",pickAll:"pick",pipe:"flow",pluck:"map",prop:"get",propEq:"matchesProperty",propOr:"getOr",props:"at",symmetricDifference:"xor",symmetricDifferenceBy:"xorBy",symmetricDifferenceWith:"xorWith",takeLast:"takeRight",takeLastWhile:"takeRightWhile",unapply:"rest",unnest:"flatten",useWith:"overArgs",where:"conformsTo",whereEq:"isMatch",zipObj:"zipObject"},o.aryMethod={1:["assignAll","assignInAll","attempt","castArray","ceil","create","curry","curryRight","defaultsAll","defaultsDeepAll","floor","flow","flowRight","fromPairs","invert","iteratee","memoize","method","mergeAll","methodOf","mixin","nthArg","over","overEvery","overSome","rest","reverse","round","runInContext","spread","template","trim","trimEnd","trimStart","uniqueId","words","zipAll"],2:["add","after","ary","assign","assignAllWith","assignIn","assignInAllWith","at","before","bind","bindAll","bindKey","chunk","cloneDeepWith","cloneWith","concat","conformsTo","countBy","curryN","curryRightN","debounce","defaults","defaultsDeep","defaultTo","delay","difference","divide","drop","dropRight","dropRightWhile","dropWhile","endsWith","eq","every","filter","find","findIndex","findKey","findLast","findLastIndex","findLastKey","flatMap","flatMapDeep","flattenDepth","forEach","forEachRight","forIn","forInRight","forOwn","forOwnRight","get","groupBy","gt","gte","has","hasIn","includes","indexOf","intersection","invertBy","invoke","invokeMap","isEqual","isMatch","join","keyBy","lastIndexOf","lt","lte","map","mapKeys","mapValues","matchesProperty","maxBy","meanBy","merge","mergeAllWith","minBy","multiply","nth","omit","omitBy","overArgs","pad","padEnd","padStart","parseInt","partial","partialRight","partition","pick","pickBy","propertyOf","pull","pullAll","pullAt","random","range","rangeRight","rearg","reject","remove","repeat","restFrom","result","sampleSize","some","sortBy","sortedIndex","sortedIndexOf","sortedLastIndex","sortedLastIndexOf","sortedUniqBy","split","spreadFrom","startsWith","subtract","sumBy","take","takeRight","takeRightWhile","takeWhile","tap","throttle","thru","times","trimChars","trimCharsEnd","trimCharsStart","truncate","union","uniqBy","uniqWith","unset","unzipWith","without","wrap","xor","zip","zipObject","zipObjectDeep"],3:["assignInWith","assignWith","clamp","differenceBy","differenceWith","findFrom","findIndexFrom","findLastFrom","findLastIndexFrom","getOr","includesFrom","indexOfFrom","inRange","intersectionBy","intersectionWith","invokeArgs","invokeArgsMap","isEqualWith","isMatchWith","flatMapDepth","lastIndexOfFrom","mergeWith","orderBy","padChars","padCharsEnd","padCharsStart","pullAllBy","pullAllWith","rangeStep","rangeStepRight","reduce","reduceRight","replace","set","slice","sortedIndexBy","sortedLastIndexBy","transform","unionBy","unionWith","update","xorBy","xorWith","zipWith"],4:["fill","setWith","updateWith"]},o.aryRearg={2:[1,0],3:[2,0,1],4:[3,2,0,1]},o.iterateeAry={dropRightWhile:1,dropWhile:1,every:1,filter:1,find:1,findFrom:1,findIndex:1,findIndexFrom:1,findKey:1,findLast:1,findLastFrom:1,findLastIndex:1,findLastIndexFrom:1,findLastKey:1,flatMap:1,flatMapDeep:1,flatMapDepth:1,forEach:1,forEachRight:1,forIn:1,forInRight:1,forOwn:1,forOwnRight:1,map:1,mapKeys:1,mapValues:1,partition:1,reduce:2,reduceRight:2,reject:1,remove:1,some:1,takeRightWhile:1,takeWhile:1,times:1,transform:2},o.iterateeRearg={mapKeys:[1],reduceRight:[1,0]},o.methodRearg={assignInAllWith:[1,0],assignInWith:[1,2,0],assignAllWith:[1,0],assignWith:[1,2,0],differenceBy:[1,2,0],differenceWith:[1,2,0],getOr:[2,1,0],intersectionBy:[1,2,0],intersectionWith:[1,2,0],isEqualWith:[1,2,0],isMatchWith:[2,1,0],mergeAllWith:[1,0],mergeWith:[1,2,0],padChars:[2,1,0],padCharsEnd:[2,1,0],padCharsStart:[2,1,0],pullAllBy:[2,1,0],pullAllWith:[2,1,0],rangeStep:[1,2,0],rangeStepRight:[1,2,0],setWith:[3,1,2,0],sortedIndexBy:[2,1,0],sortedLastIndexBy:[2,1,0],unionBy:[1,2,0],unionWith:[1,2,0],updateWith:[3,1,2,0],xorBy:[1,2,0],xorWith:[1,2,0],zipWith:[1,2,0]},o.methodSpread={assignAll:{start:0},assignAllWith:{start:0},assignInAll:{start:0},assignInAllWith:{start:0},defaultsAll:{start:0},defaultsDeepAll:{start:0},invokeArgs:{start:2},invokeArgsMap:{start:2},mergeAll:{start:0},mergeAllWith:{start:0},partial:{start:1},partialRight:{start:1},without:{start:1},zipAll:{start:0}},o.mutate={array:{fill:!0,pull:!0,pullAll:!0,pullAllBy:!0,pullAllWith:!0,pullAt:!0,remove:!0,reverse:!0},object:{assign:!0,assignAll:!0,assignAllWith:!0,assignIn:!0,assignInAll:!0,assignInAllWith:!0,assignInWith:!0,assignWith:!0,defaults:!0,defaultsAll:!0,defaultsDeep:!0,defaultsDeepAll:!0,merge:!0,mergeAll:!0,mergeAllWith:!0,mergeWith:!0},set:{set:!0,setWith:!0,unset:!0,update:!0,updateWith:!0}},o.realToAlias=function(){var s=Object.prototype.hasOwnProperty,i=o.aliasToReal,a={};for(var u in i){var _=i[u];s.call(a,_)?a[_].push(u):a[_]=[u]}return a}(),o.remap={assignAll:"assign",assignAllWith:"assignWith",assignInAll:"assignIn",assignInAllWith:"assignInWith",curryN:"curry",curryRightN:"curryRight",defaultsAll:"defaults",defaultsDeepAll:"defaultsDeep",findFrom:"find",findIndexFrom:"findIndex",findLastFrom:"findLast",findLastIndexFrom:"findLastIndex",getOr:"get",includesFrom:"includes",indexOfFrom:"indexOf",invokeArgs:"invoke",invokeArgsMap:"invokeMap",lastIndexOfFrom:"lastIndexOf",mergeAll:"merge",mergeAllWith:"mergeWith",padChars:"pad",padCharsEnd:"padEnd",padCharsStart:"padStart",propertyOf:"get",rangeStep:"range",rangeStepRight:"rangeRight",restFrom:"rest",spreadFrom:"spread",trimChars:"trim",trimCharsEnd:"trimEnd",trimCharsStart:"trimStart",zipAll:"zip"},o.skipFixed={castArray:!0,flow:!0,flowRight:!0,iteratee:!0,mixin:!0,rearg:!0,runInContext:!0},o.skipRearg={add:!0,assign:!0,assignIn:!0,bind:!0,bindKey:!0,concat:!0,difference:!0,divide:!0,eq:!0,gt:!0,gte:!0,isEqual:!0,lt:!0,lte:!0,matchesProperty:!0,merge:!0,multiply:!0,overArgs:!0,partial:!0,partialRight:!0,propertyOf:!0,random:!0,range:!0,rangeRight:!0,subtract:!0,zip:!0,zipObject:!0,zipObjectDeep:!0}},17255:(s,o,i)=>{var a=i(47422);s.exports=function basePropertyDeep(s){return function(o){return a(o,s)}}},17285:s=>{function source(s){return s?"string"==typeof s?s:s.source:null}function lookahead(s){return concat("(?=",s,")")}function concat(...s){return s.map((s=>source(s))).join("")}function either(...s){return"("+s.map((s=>source(s))).join("|")+")"}s.exports=function xml(s){const o=concat(/[A-Z_]/,function optional(s){return concat("(",s,")?")}(/[A-Z0-9_.-]*:/),/[A-Z0-9_.-]*/),i={className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},a={begin:/\s/,contains:[{className:"meta-keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}]},u=s.inherit(a,{begin:/\(/,end:/\)/}),_=s.inherit(s.APOS_STRING_MODE,{className:"meta-string"}),w=s.inherit(s.QUOTE_STRING_MODE,{className:"meta-string"}),x={endsWithParent:!0,illegal:/`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin://,relevance:10,contains:[a,w,_,u,{begin:/\[/,end:/\]/,contains:[{className:"meta",begin://,contains:[a,u,w,_]}]}]},s.COMMENT(//,{relevance:10}),{begin://,relevance:10},i,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:/)/,end:/>/,keywords:{name:"style"},contains:[x],starts:{end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:/)/,end:/>/,keywords:{name:"script"},contains:[x],starts:{end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:/<>|<\/>/},{className:"tag",begin:concat(//,/>/,/\s/)))),end:/\/?>/,contains:[{className:"name",begin:o,relevance:0,starts:x}]},{className:"tag",begin:concat(/<\//,lookahead(concat(o,/>/))),contains:[{className:"name",begin:o,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]}}},17400:(s,o,i)=>{var a=i(99374),u=1/0;s.exports=function toFinite(s){return s?(s=a(s))===u||s===-1/0?17976931348623157e292*(s<0?-1:1):s==s?s:0:0===s?s:0}},17533:s=>{s.exports=function yaml(s){var o="true false yes no null",i="[\\w#;/?:@&=+$,.~*'()[\\]]+",a={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[s.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},u=s.inherit(a,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),_={className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},w={end:",",endsWithParent:!0,excludeEnd:!0,keywords:o,relevance:0},x={begin:/\{/,end:/\}/,contains:[w],illegal:"\\n",relevance:0},C={begin:"\\[",end:"\\]",contains:[w],illegal:"\\n",relevance:0},j=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$",relevance:10},{className:"string",begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+i},{className:"type",begin:"!<"+i+">"},{className:"type",begin:"!"+i},{className:"type",begin:"!!"+i},{className:"meta",begin:"&"+s.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+s.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)",relevance:0},s.HASH_COMMENT_MODE,{beginKeywords:o,keywords:{literal:o}},_,{className:"number",begin:s.C_NUMBER_RE+"\\b",relevance:0},x,C,a],L=[...j];return L.pop(),L.push(u),w.contains=L,{name:"YAML",case_insensitive:!0,aliases:["yml"],contains:j}}},17670:(s,o,i)=>{var a=i(12651);s.exports=function mapCacheDelete(s){var o=a(this,s).delete(s);return this.size-=o?1:0,o}},17965:(s,o,i)=>{"use strict";var a=i(16426),u={"text/plain":"Text","text/html":"Url",default:"Text"};s.exports=function copy(s,o){var i,_,w,x,C,j,L=!1;o||(o={}),i=o.debug||!1;try{if(w=a(),x=document.createRange(),C=document.getSelection(),(j=document.createElement("span")).textContent=s,j.ariaHidden="true",j.style.all="unset",j.style.position="fixed",j.style.top=0,j.style.clip="rect(0, 0, 0, 0)",j.style.whiteSpace="pre",j.style.webkitUserSelect="text",j.style.MozUserSelect="text",j.style.msUserSelect="text",j.style.userSelect="text",j.addEventListener("copy",(function(a){if(a.stopPropagation(),o.format)if(a.preventDefault(),void 0===a.clipboardData){i&&console.warn("unable to use e.clipboardData"),i&&console.warn("trying IE specific stuff"),window.clipboardData.clearData();var _=u[o.format]||u.default;window.clipboardData.setData(_,s)}else a.clipboardData.clearData(),a.clipboardData.setData(o.format,s);o.onCopy&&(a.preventDefault(),o.onCopy(a.clipboardData))})),document.body.appendChild(j),x.selectNodeContents(j),C.addRange(x),!document.execCommand("copy"))throw new Error("copy command was unsuccessful");L=!0}catch(a){i&&console.error("unable to copy using execCommand: ",a),i&&console.warn("trying IE specific stuff");try{window.clipboardData.setData(o.format||"text",s),o.onCopy&&o.onCopy(window.clipboardData),L=!0}catch(a){i&&console.error("unable to copy using clipboardData: ",a),i&&console.error("falling back to prompt"),_=function format(s){var o=(/mac os x/i.test(navigator.userAgent)?"⌘":"Ctrl")+"+C";return s.replace(/#{\s*key\s*}/g,o)}("message"in o?o.message:"Copy to clipboard: #{key}, Enter"),window.prompt(_,s)}}finally{C&&("function"==typeof C.removeRange?C.removeRange(x):C.removeAllRanges()),j&&document.body.removeChild(j),w()}return L}},18073:(s,o,i)=>{var a=i(85087),u=i(54641),_=i(70981);s.exports=function createRecurry(s,o,i,w,x,C,j,L,B,$){var V=8&o;o|=V?32:64,4&(o&=~(V?64:32))||(o&=-4);var U=[s,o,x,V?C:void 0,V?j:void 0,V?void 0:C,V?void 0:j,L,B,$],z=i.apply(void 0,U);return a(s)&&u(z,U),z.placeholder=w,_(z,s,o)}},19123:(s,o,i)=>{var a=i(65606),u=i(31499),_=i(88310).Stream;function resolve(s,o,i){var a,_=function create_indent(s,o){return new Array(o||0).join(s||"")}(o,i=i||0),w=s;if("object"==typeof s&&((w=s[a=Object.keys(s)[0]])&&w._elem))return w._elem.name=a,w._elem.icount=i,w._elem.indent=o,w._elem.indents=_,w._elem.interrupt=w,w._elem;var x,C=[],j=[];function get_attributes(s){Object.keys(s).forEach((function(o){C.push(function attribute(s,o){return s+'="'+u(o)+'"'}(o,s[o]))}))}switch(typeof w){case"object":if(null===w)break;w._attr&&get_attributes(w._attr),w._cdata&&j.push(("/g,"]]]]>")+"]]>"),w.forEach&&(x=!1,j.push(""),w.forEach((function(s){"object"==typeof s?"_attr"==Object.keys(s)[0]?get_attributes(s._attr):j.push(resolve(s,o,i+1)):(j.pop(),x=!0,j.push(u(s)))})),x||j.push(""));break;default:j.push(u(w))}return{name:a,interrupt:!1,attributes:C,content:j,icount:i,indents:_,indent:o}}function format(s,o,i){if("object"!=typeof o)return s(!1,o);var a=o.interrupt?1:o.content.length;function proceed(){for(;o.content.length;){var u=o.content.shift();if(void 0!==u){if(interrupt(u))return;format(s,u)}}s(!1,(a>1?o.indents:"")+(o.name?"":"")+(o.indent&&!i?"\n":"")),i&&i()}function interrupt(o){return!!o.interrupt&&(o.interrupt.append=s,o.interrupt.end=proceed,o.interrupt=!1,s(!0),!0)}if(s(!1,o.indents+(o.name?"<"+o.name:"")+(o.attributes.length?" "+o.attributes.join(" "):"")+(a?o.name?">":"":o.name?"/>":"")+(o.indent&&a>1?"\n":"")),!a)return s(!1,o.indent?"\n":"");interrupt(o)||proceed()}s.exports=function xml(s,o){"object"!=typeof o&&(o={indent:o});var i=o.stream?new _:null,u="",w=!1,x=o.indent?!0===o.indent?" ":o.indent:"",C=!0;function delay(s){C?a.nextTick(s):s()}function append(s,o){if(void 0!==o&&(u+=o),s&&!w&&(i=i||new _,w=!0),s&&w){var a=u;delay((function(){i.emit("data",a)})),u=""}}function add(s,o){format(append,resolve(s,x,x?1:0),o)}function end(){if(i){var s=u;delay((function(){i.emit("data",s),i.emit("end"),i.readable=!1,i.emit("close")}))}}return delay((function(){C=!1})),o.declaration&&function addXmlDeclaration(s){var o={version:"1.0",encoding:s.encoding||"UTF-8"};s.standalone&&(o.standalone=s.standalone),add({"?xml":{_attr:o}}),u=u.replace("/>","?>")}(o.declaration),s&&s.forEach?s.forEach((function(o,i){var a;i+1===s.length&&(a=end),add(o,a)})):add(s,end),i?(i.readable=!0,i):u},s.exports.element=s.exports.Element=function element(){var s={_elem:resolve(Array.prototype.slice.call(arguments)),push:function(s){if(!this.append)throw new Error("not assigned to a parent!");var o=this,i=this._elem.indent;format(this.append,resolve(s,i,this._elem.icount+(i?1:0)),(function(){o.append(!0)}))},close:function(s){void 0!==s&&this.push(s),this.end&&this.end()}};return s}},19219:s=>{s.exports=function cacheHas(s,o){return s.has(o)}},19287:s=>{"use strict";s.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},19358:(s,o,i)=>{"use strict";var a=i(85582),u=i(49724),_=i(61626),w=i(88280),x=i(79192),C=i(19595),j=i(54829),L=i(34084),B=i(32096),$=i(39259),V=i(85884),U=i(39447),z=i(7376);s.exports=function(s,o,i,Y){var Z="stackTraceLimit",ee=Y?2:1,ie=s.split("."),ae=ie[ie.length-1],ce=a.apply(null,ie);if(ce){var le=ce.prototype;if(!z&&u(le,"cause")&&delete le.cause,!i)return ce;var pe=a("Error"),de=o((function(s,o){var i=B(Y?o:s,void 0),a=Y?new ce(s):new ce;return void 0!==i&&_(a,"message",i),V(a,de,a.stack,2),this&&w(le,this)&&L(a,this,de),arguments.length>ee&&$(a,arguments[ee]),a}));if(de.prototype=le,"Error"!==ae?x?x(de,pe):C(de,pe,{name:!0}):U&&Z in ce&&(j(de,ce,Z),j(de,ce,"prepareStackTrace")),C(de,ce),!z)try{le.name!==ae&&_(le,"name",ae),le.constructor=de}catch(s){}return de}}},19570:(s,o,i)=>{var a=i(37334),u=i(93243),_=i(83488),w=u?function(s,o){return u(s,"toString",{configurable:!0,enumerable:!1,value:a(o),writable:!0})}:_;s.exports=w},19595:(s,o,i)=>{"use strict";var a=i(49724),u=i(11042),_=i(13846),w=i(74284);s.exports=function(s,o,i){for(var x=u(o),C=w.f,j=_.f,L=0;L{"use strict";var a=i(23034);s.exports=a},19846:(s,o,i)=>{"use strict";var a=i(20798),u=i(98828),_=i(45951).String;s.exports=!!Object.getOwnPropertySymbols&&!u((function(){var s=Symbol("symbol detection");return!_(s)||!(Object(s)instanceof Symbol)||!Symbol.sham&&a&&a<41}))},19931:(s,o,i)=>{var a=i(31769),u=i(68090),_=i(68969),w=i(77797);s.exports=function baseUnset(s,o){return o=a(o,s),null==(s=_(s,o))||delete s[w(u(o))]}},20181:(s,o,i)=>{var a=/^\s+|\s+$/g,u=/^[-+]0x[0-9a-f]+$/i,_=/^0b[01]+$/i,w=/^0o[0-7]+$/i,x=parseInt,C="object"==typeof i.g&&i.g&&i.g.Object===Object&&i.g,j="object"==typeof self&&self&&self.Object===Object&&self,L=C||j||Function("return this")(),B=Object.prototype.toString,$=Math.max,V=Math.min,now=function(){return L.Date.now()};function isObject(s){var o=typeof s;return!!s&&("object"==o||"function"==o)}function toNumber(s){if("number"==typeof s)return s;if(function isSymbol(s){return"symbol"==typeof s||function isObjectLike(s){return!!s&&"object"==typeof s}(s)&&"[object Symbol]"==B.call(s)}(s))return NaN;if(isObject(s)){var o="function"==typeof s.valueOf?s.valueOf():s;s=isObject(o)?o+"":o}if("string"!=typeof s)return 0===s?s:+s;s=s.replace(a,"");var i=_.test(s);return i||w.test(s)?x(s.slice(2),i?2:8):u.test(s)?NaN:+s}s.exports=function debounce(s,o,i){var a,u,_,w,x,C,j=0,L=!1,B=!1,U=!0;if("function"!=typeof s)throw new TypeError("Expected a function");function invokeFunc(o){var i=a,_=u;return a=u=void 0,j=o,w=s.apply(_,i)}function shouldInvoke(s){var i=s-C;return void 0===C||i>=o||i<0||B&&s-j>=_}function timerExpired(){var s=now();if(shouldInvoke(s))return trailingEdge(s);x=setTimeout(timerExpired,function remainingWait(s){var i=o-(s-C);return B?V(i,_-(s-j)):i}(s))}function trailingEdge(s){return x=void 0,U&&a?invokeFunc(s):(a=u=void 0,w)}function debounced(){var s=now(),i=shouldInvoke(s);if(a=arguments,u=this,C=s,i){if(void 0===x)return function leadingEdge(s){return j=s,x=setTimeout(timerExpired,o),L?invokeFunc(s):w}(C);if(B)return x=setTimeout(timerExpired,o),invokeFunc(C)}return void 0===x&&(x=setTimeout(timerExpired,o)),w}return o=toNumber(o)||0,isObject(i)&&(L=!!i.leading,_=(B="maxWait"in i)?$(toNumber(i.maxWait)||0,o):_,U="trailing"in i?!!i.trailing:U),debounced.cancel=function cancel(){void 0!==x&&clearTimeout(x),j=0,a=C=u=x=void 0},debounced.flush=function flush(){return void 0===x?w:trailingEdge(now())},debounced}},20317:s=>{s.exports=function mapToArray(s){var o=-1,i=Array(s.size);return s.forEach((function(s,a){i[++o]=[a,s]})),i}},20334:(s,o,i)=>{"use strict";var a=i(48287).Buffer;class NonError extends Error{constructor(s){super(NonError._prepareSuperMessage(s)),Object.defineProperty(this,"name",{value:"NonError",configurable:!0,writable:!0}),Error.captureStackTrace&&Error.captureStackTrace(this,NonError)}static _prepareSuperMessage(s){try{return JSON.stringify(s)}catch{return String(s)}}}const u=[{property:"name",enumerable:!1},{property:"message",enumerable:!1},{property:"stack",enumerable:!1},{property:"code",enumerable:!0}],_=Symbol(".toJSON called"),destroyCircular=({from:s,seen:o,to_:i,forceEnumerable:w,maxDepth:x,depth:C})=>{const j=i||(Array.isArray(s)?[]:{});if(o.push(s),C>=x)return j;if("function"==typeof s.toJSON&&!0!==s[_])return(s=>{s[_]=!0;const o=s.toJSON();return delete s[_],o})(s);for(const[i,u]of Object.entries(s))"function"==typeof a&&a.isBuffer(u)?j[i]="[object Buffer]":"function"!=typeof u&&(u&&"object"==typeof u?o.includes(s[i])?j[i]="[Circular]":(C++,j[i]=destroyCircular({from:s[i],seen:o.slice(),forceEnumerable:w,maxDepth:x,depth:C})):j[i]=u);for(const{property:o,enumerable:i}of u)"string"==typeof s[o]&&Object.defineProperty(j,o,{value:s[o],enumerable:!!w||i,configurable:!0,writable:!0});return j};s.exports={serializeError:(s,o={})=>{const{maxDepth:i=Number.POSITIVE_INFINITY}=o;return"object"==typeof s&&null!==s?destroyCircular({from:s,seen:[],forceEnumerable:!0,maxDepth:i,depth:0}):"function"==typeof s?`[Function: ${s.name||"anonymous"}]`:s},deserializeError:(s,o={})=>{const{maxDepth:i=Number.POSITIVE_INFINITY}=o;if(s instanceof Error)return s;if("object"==typeof s&&null!==s&&!Array.isArray(s)){const o=new Error;return destroyCircular({from:s,seen:[],to_:o,maxDepth:i,depth:0}),o}return new NonError(s)}}},20426:s=>{var o=Object.prototype.hasOwnProperty;s.exports=function baseHas(s,i){return null!=s&&o.call(s,i)}},20575:(s,o,i)=>{"use strict";var a=i(3121);s.exports=function(s){return a(s.length)}},20798:(s,o,i)=>{"use strict";var a,u,_=i(45951),w=i(96794),x=_.process,C=_.Deno,j=x&&x.versions||C&&C.version,L=j&&j.v8;L&&(u=(a=L.split("."))[0]>0&&a[0]<4?1:+(a[0]+a[1])),!u&&w&&(!(a=w.match(/Edge\/(\d+)/))||a[1]>=74)&&(a=w.match(/Chrome\/(\d+)/))&&(u=+a[1]),s.exports=u},20850:(s,o,i)=>{"use strict";s.exports=i(46076)},20999:(s,o,i)=>{var a=i(69302),u=i(36800);s.exports=function createAssigner(s){return a((function(o,i){var a=-1,_=i.length,w=_>1?i[_-1]:void 0,x=_>2?i[2]:void 0;for(w=s.length>3&&"function"==typeof w?(_--,w):void 0,x&&u(i[0],i[1],x)&&(w=_<3?void 0:w,_=1),o=Object(o);++a<_;){var C=i[a];C&&s(o,C,a,w)}return o}))}},21549:(s,o,i)=>{var a=i(22032),u=i(63862),_=i(66721),w=i(12749),x=i(35749);function Hash(s){var o=-1,i=null==s?0:s.length;for(this.clear();++o{var a=i(16547),u=i(43360);s.exports=function copyObject(s,o,i,_){var w=!i;i||(i={});for(var x=-1,C=o.length;++x{var a=i(51873),u=i(37828),_=i(75288),w=i(25911),x=i(20317),C=i(84247),j=a?a.prototype:void 0,L=j?j.valueOf:void 0;s.exports=function equalByTag(s,o,i,a,j,B,$){switch(i){case"[object DataView]":if(s.byteLength!=o.byteLength||s.byteOffset!=o.byteOffset)return!1;s=s.buffer,o=o.buffer;case"[object ArrayBuffer]":return!(s.byteLength!=o.byteLength||!B(new u(s),new u(o)));case"[object Boolean]":case"[object Date]":case"[object Number]":return _(+s,+o);case"[object Error]":return s.name==o.name&&s.message==o.message;case"[object RegExp]":case"[object String]":return s==o+"";case"[object Map]":var V=x;case"[object Set]":var U=1&a;if(V||(V=C),s.size!=o.size&&!U)return!1;var z=$.get(s);if(z)return z==o;a|=2,$.set(s,o);var Y=w(V(s),V(o),a,j,B,$);return $.delete(s),Y;case"[object Symbol]":if(L)return L.call(s)==L.call(o)}return!1}},22032:(s,o,i)=>{var a=i(81042);s.exports=function hashClear(){this.__data__=a?a(null):{},this.size=0}},22225:s=>{var o="\\ud800-\\udfff",i="\\u2700-\\u27bf",a="a-z\\xdf-\\xf6\\xf8-\\xff",u="A-Z\\xc0-\\xd6\\xd8-\\xde",_="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",w="["+_+"]",x="\\d+",C="["+i+"]",j="["+a+"]",L="[^"+o+_+x+i+a+u+"]",B="(?:\\ud83c[\\udde6-\\uddff]){2}",$="[\\ud800-\\udbff][\\udc00-\\udfff]",V="["+u+"]",U="(?:"+j+"|"+L+")",z="(?:"+V+"|"+L+")",Y="(?:['’](?:d|ll|m|re|s|t|ve))?",Z="(?:['’](?:D|LL|M|RE|S|T|VE))?",ee="(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?",ie="[\\ufe0e\\ufe0f]?",ae=ie+ee+("(?:\\u200d(?:"+["[^"+o+"]",B,$].join("|")+")"+ie+ee+")*"),ce="(?:"+[C,B,$].join("|")+")"+ae,le=RegExp([V+"?"+j+"+"+Y+"(?="+[w,V,"$"].join("|")+")",z+"+"+Z+"(?="+[w,V+U,"$"].join("|")+")",V+"?"+U+"+"+Y,V+"+"+Z,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",x,ce].join("|"),"g");s.exports=function unicodeWords(s){return s.match(le)||[]}},22551:(s,o,i)=>{"use strict";var a=i(96540),u=i(69982);function p(s){for(var o="https://reactjs.org/docs/error-decoder.html?invariant="+s,i=1;i
\n )\n}\n\nRequestSnippets.propTypes = {\n request: PropTypes.object.isRequired,\n requestSnippetsSelectors: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n requestSnippetsActions: PropTypes.object,\n}\n\nexport default RequestSnippets\n","import { requestSnippetGenerator_curl_bash, requestSnippetGenerator_curl_cmd, requestSnippetGenerator_curl_powershell } from \"./fn\"\nimport * as selectors from \"./selectors\"\nimport RequestSnippets from \"./request-snippets\"\n\nexport default () => {\n return {\n components: {\n RequestSnippets\n },\n fn: {\n requestSnippetGenerator_curl_bash,\n requestSnippetGenerator_curl_cmd,\n requestSnippetGenerator_curl_powershell,\n },\n statePlugins: {\n requestSnippets: {\n selectors\n }\n }\n }\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport Im from \"immutable\"\n\nexport default class ModelCollapse extends Component {\n static propTypes = {\n collapsedContent: PropTypes.any,\n expanded: PropTypes.bool,\n children: PropTypes.any,\n title: PropTypes.element,\n modelName: PropTypes.string,\n classes: PropTypes.string,\n onToggle: PropTypes.func,\n hideSelfOnExpand: PropTypes.bool,\n layoutActions: PropTypes.object,\n layoutSelectors: PropTypes.object.isRequired,\n specPath: ImPropTypes.list.isRequired,\n }\n\n static defaultProps = {\n collapsedContent: \"{...}\",\n expanded: false,\n title: null,\n onToggle: () => {},\n hideSelfOnExpand: false,\n specPath: Im.List([]),\n }\n\n constructor(props, context) {\n super(props, context)\n\n let { expanded, collapsedContent } = this.props\n\n this.state = {\n expanded : expanded,\n collapsedContent: collapsedContent || ModelCollapse.defaultProps.collapsedContent\n }\n }\n\n componentDidMount() {\n const { hideSelfOnExpand, expanded, modelName } = this.props\n if(hideSelfOnExpand && expanded) {\n // We just mounted pre-expanded, and we won't be going back..\n // So let's give our parent an `onToggle` call..\n // Since otherwise it will never be called.\n this.props.onToggle(modelName, expanded)\n }\n }\n\n UNSAFE_componentWillReceiveProps(nextProps){\n if(this.props.expanded !== nextProps.expanded){\n this.setState({expanded: nextProps.expanded})\n }\n }\n\n toggleCollapsed=()=>{\n if(this.props.onToggle){\n this.props.onToggle(this.props.modelName,!this.state.expanded)\n }\n\n this.setState({\n expanded: !this.state.expanded\n })\n }\n\n onLoad = (ref) => {\n if (ref && this.props.layoutSelectors) {\n const scrollToKey = this.props.layoutSelectors.getScrollToKey()\n\n if( Im.is(scrollToKey, this.props.specPath) ) this.toggleCollapsed()\n this.props.layoutActions.readyToScroll(this.props.specPath, ref.parentElement)\n }\n }\n\n render () {\n const { title, classes } = this.props\n\n if(this.state.expanded ) {\n if(this.props.hideSelfOnExpand) {\n return \n {this.props.children}\n \n }\n }\n\n return (\n \n \n\n { this.state.expanded && this.props.children }\n \n )\n }\n}\n","/**\n * @prettier\n */\nimport React, { useMemo, useState, useEffect, useCallback, useRef } from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport cx from \"classnames\"\nimport randomBytes from \"randombytes\"\n\nconst usePrevious = (value) => {\n const ref = useRef()\n useEffect(() => {\n ref.current = value\n })\n return ref.current\n}\n\nconst useTabs = ({ initialTab, isExecute, schema, example }) => {\n const tabs = useMemo(() => ({ example: \"example\", model: \"model\" }), [])\n const allowedTabs = useMemo(() => Object.keys(tabs), [tabs])\n const tab =\n !allowedTabs.includes(initialTab) || !schema || isExecute\n ? tabs.example\n : initialTab\n const prevIsExecute = usePrevious(isExecute)\n const [activeTab, setActiveTab] = useState(tab)\n const handleTabChange = useCallback((e) => {\n setActiveTab(e.target.dataset.name)\n }, [])\n\n useEffect(() => {\n if (prevIsExecute && !isExecute && example) {\n setActiveTab(tabs.example)\n }\n }, [prevIsExecute, isExecute, example])\n\n return { activeTab, onTabChange: handleTabChange, tabs }\n}\n\nconst ModelExample = ({\n schema,\n example,\n isExecute = false,\n specPath,\n includeWriteOnly = false,\n includeReadOnly = false,\n getComponent,\n getConfigs,\n specSelectors,\n}) => {\n const { defaultModelRendering, defaultModelExpandDepth } = getConfigs()\n const ModelWrapper = getComponent(\"ModelWrapper\")\n const HighlightCode = getComponent(\"HighlightCode\", true)\n const exampleTabId = randomBytes(5).toString(\"base64\")\n const examplePanelId = randomBytes(5).toString(\"base64\")\n const modelTabId = randomBytes(5).toString(\"base64\")\n const modelPanelId = randomBytes(5).toString(\"base64\")\n const isOAS3 = specSelectors.isOAS3()\n const { activeTab, tabs, onTabChange } = useTabs({\n initialTab: defaultModelRendering,\n isExecute,\n schema,\n example,\n })\n\n return (\n
\n
    \n \n \n {isExecute ? \"Edit Value\" : \"Example Value\"}\n \n \n {schema && (\n \n \n {isOAS3 ? \"Schema\" : \"Model\"}\n \n \n )}\n
\n {activeTab === tabs.example && (\n \n {example ? (\n example\n ) : (\n (no example available\n )}\n
\n )}\n\n {activeTab === tabs.model && (\n \n \n \n )}\n \n )\n}\n\nModelExample.propTypes = {\n getComponent: PropTypes.func.isRequired,\n specSelectors: PropTypes.shape({ isOAS3: PropTypes.func.isRequired })\n .isRequired,\n schema: PropTypes.object.isRequired,\n example: PropTypes.any.isRequired,\n isExecute: PropTypes.bool,\n getConfigs: PropTypes.func.isRequired,\n specPath: ImPropTypes.list.isRequired,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n}\n\nexport default ModelExample\n","import React, { Component, } from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class ModelWrapper extends Component {\n\n static propTypes = {\n schema: PropTypes.object.isRequired,\n name: PropTypes.string,\n displayName: PropTypes.string,\n fullPath: PropTypes.array.isRequired,\n specPath: ImPropTypes.list.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n expandDepth: PropTypes.number,\n layoutActions: PropTypes.object,\n layoutSelectors: PropTypes.object.isRequired,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n }\n\n onToggle = (name,isShown) => {\n // If this prop is present, we'll have deepLinking for it\n if(this.props.layoutActions) {\n this.props.layoutActions.show(this.props.fullPath, isShown)\n }\n }\n\n render(){\n let { getComponent, getConfigs } = this.props\n const Model = getComponent(\"Model\")\n\n let expanded\n if(this.props.layoutSelectors) {\n // If this is prop is present, we'll have deepLinking for it\n expanded = this.props.layoutSelectors.isShown(this.props.fullPath)\n }\n\n return
\n \n
\n }\n}\n","var x = function(y) {\n\tvar x = {}; __webpack_require__.d(x, y); return x\n} \nvar y = function(x) { return function() { return x; }; }\nvar __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: function() { return __WEBPACK_EXTERNAL_MODULE_react_immutable_pure_component_cbcfaebd__[\"default\"]; } });","var _circle;\nfunction _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }\nimport * as React from \"react\";\nconst SvgRollingLoad = props => /*#__PURE__*/React.createElement(\"svg\", _extends({\n xmlns: \"http://www.w3.org/2000/svg\",\n width: 200,\n height: 200,\n className: \"rolling-load_svg__lds-rolling\",\n preserveAspectRatio: \"xMidYMid\",\n style: {\n backgroundImage: \"none\",\n backgroundPosition: \"initial initial\",\n backgroundRepeat: \"initial initial\"\n },\n viewBox: \"0 0 100 100\"\n}, props), _circle || (_circle = /*#__PURE__*/React.createElement(\"circle\", {\n cx: 50,\n cy: 50,\n r: 35,\n fill: \"none\",\n stroke: \"#555\",\n strokeDasharray: \"164.93361431346415 56.97787143782138\",\n strokeWidth: 10\n}, /*#__PURE__*/React.createElement(\"animateTransform\", {\n attributeName: \"transform\",\n begin: \"0s\",\n calcMode: \"linear\",\n dur: \"1s\",\n keyTimes: \"0;1\",\n repeatCount: \"indefinite\",\n type: \"rotate\",\n values: \"0 50 50;360 50 50\"\n}))));\nexport default SvgRollingLoad;","import React from \"react\"\nimport ImmutablePureComponent from \"react-immutable-pure-component\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport PropTypes from \"prop-types\"\nimport { Map } from \"immutable\"\n\nimport RollingLoadSVG from \"core/assets/rolling-load.svg\"\n\nconst decodeRefName = uri => {\n const unescaped = uri.replace(/~1/g, \"/\").replace(/~0/g, \"~\")\n\n try {\n return decodeURIComponent(unescaped)\n } catch {\n return unescaped\n }\n}\n\nexport default class Model extends ImmutablePureComponent {\n static propTypes = {\n schema: ImPropTypes.map.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n name: PropTypes.string,\n displayName: PropTypes.string,\n isRef: PropTypes.bool,\n required: PropTypes.bool,\n expandDepth: PropTypes.number,\n depth: PropTypes.number,\n specPath: ImPropTypes.list.isRequired,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n }\n\n getModelName =( ref )=> {\n if ( ref.indexOf(\"#/definitions/\") !== -1 ) {\n return decodeRefName(ref.replace(/^.*#\\/definitions\\//, \"\"))\n }\n if ( ref.indexOf(\"#/components/schemas/\") !== -1 ) {\n return decodeRefName(ref.replace(/^.*#\\/components\\/schemas\\//, \"\"))\n }\n }\n\n getRefSchema =( model )=> {\n let { specSelectors } = this.props\n\n return specSelectors.findDefinition(model)\n }\n\n render () {\n let { getComponent, getConfigs, specSelectors, schema, required, name, isRef, specPath, displayName,\n includeReadOnly, includeWriteOnly} = this.props\n const ObjectModel = getComponent(\"ObjectModel\")\n const ArrayModel = getComponent(\"ArrayModel\")\n const PrimitiveModel = getComponent(\"PrimitiveModel\")\n let type = \"object\"\n let $$ref = schema && schema.get(\"$$ref\")\n let $ref = schema && schema.get(\"$ref\")\n\n // If we weren't passed a `name` but have a resolved ref, grab the name from the ref\n if (!name && $$ref) {\n name = this.getModelName($$ref)\n }\n\n /*\n * If we have an unresolved ref, get the schema and name from the ref.\n * If the ref is external, we can't resolve it, so we just display the ref location.\n * This is for situations where:\n * - the ref was not resolved by Swagger Client because we reached the traversal depth limit\n * - we had a circular ref inside the allOf keyword\n */\n if ($ref) {\n const refName = this.getModelName($ref)\n const refSchema = this.getRefSchema(refName)\n if (Map.isMap(refSchema)) {\n schema = refSchema.mergeDeep(schema)\n if (!$$ref) {\n schema = schema.set(\"$$ref\", $ref)\n $$ref = $ref\n }\n } else if (Map.isMap(schema) && schema.size === 1) {\n schema = null\n name = $ref\n }\n }\n\n if(!schema) {\n return \n { displayName || name }\n {!$ref && }\n \n }\n\n const deprecated = specSelectors.isOAS3() && schema.get(\"deprecated\")\n isRef = isRef !== undefined ? isRef : !!$$ref\n type = schema && schema.get(\"type\") || type\n\n switch(type) {\n case \"object\":\n return \n case \"array\":\n return \n case \"string\":\n case \"number\":\n case \"integer\":\n case \"boolean\":\n default:\n return \n }\n }\n}\n","import React, { Component } from \"react\"\nimport Im, { Map } from \"immutable\"\nimport PropTypes from \"prop-types\"\n\n/* eslint-disable react/jsx-no-bind */\n\nexport default class Models extends Component {\n static propTypes = {\n getComponent: PropTypes.func,\n specSelectors: PropTypes.object,\n specActions: PropTypes.object.isRequired,\n layoutSelectors: PropTypes.object,\n layoutActions: PropTypes.object,\n getConfigs: PropTypes.func.isRequired\n }\n\n getSchemaBasePath = () => {\n const isOAS3 = this.props.specSelectors.isOAS3()\n return isOAS3 ? [\"components\", \"schemas\"] : [\"definitions\"]\n }\n\n getCollapsedContent = () => {\n return \" \"\n }\n\n handleToggle = (name, isExpanded) => {\n const { layoutActions } = this.props\n layoutActions.show([...this.getSchemaBasePath(), name], isExpanded)\n if(isExpanded) {\n this.props.specActions.requestResolvedSubtree([...this.getSchemaBasePath(), name])\n }\n }\n\n onLoadModels = (ref) => {\n if (ref) {\n this.props.layoutActions.readyToScroll(this.getSchemaBasePath(), ref)\n }\n }\n\n onLoadModel = (ref) => {\n if (ref) {\n const name = ref.getAttribute(\"data-name\")\n this.props.layoutActions.readyToScroll([...this.getSchemaBasePath(), name], ref)\n }\n }\n\n render(){\n let { specSelectors, getComponent, layoutSelectors, layoutActions, getConfigs } = this.props\n let definitions = specSelectors.definitions()\n let { docExpansion, defaultModelsExpandDepth } = getConfigs()\n if (!definitions.size || defaultModelsExpandDepth < 0) return null\n\n const specPathBase = this.getSchemaBasePath()\n let showModels = layoutSelectors.isShown(specPathBase, defaultModelsExpandDepth > 0 && docExpansion !== \"none\")\n const isOAS3 = specSelectors.isOAS3()\n\n const ModelWrapper = getComponent(\"ModelWrapper\")\n const Collapse = getComponent(\"Collapse\")\n const ModelCollapse = getComponent(\"ModelCollapse\")\n const JumpToPath = getComponent(\"JumpToPath\", true)\n const ArrowUpIcon = getComponent(\"ArrowUpIcon\")\n const ArrowDownIcon = getComponent(\"ArrowDownIcon\")\n\n return
\n

\n layoutActions.show(specPathBase, !showModels)}\n >\n {isOAS3 ? \"Schemas\" : \"Models\"}\n {showModels ? : }\n \n

\n \n {\n definitions.entrySeq().map(([name])=>{\n\n const fullPath = [...specPathBase, name]\n const specPath = Im.List(fullPath)\n\n const schemaValue = specSelectors.specResolvedSubtree(fullPath)\n const rawSchemaValue = specSelectors.specJson().getIn(fullPath)\n\n const schema = Map.isMap(schemaValue) ? schemaValue : Im.Map()\n const rawSchema = Map.isMap(rawSchemaValue) ? rawSchemaValue : Im.Map()\n\n const displayName = schema.get(\"title\") || rawSchema.get(\"title\") || name\n const isShown = layoutSelectors.isShown(fullPath, false)\n\n if( isShown && (schema.size === 0 && rawSchema.size > 0) ) {\n // Firing an action in a container render is not great,\n // but it works for now.\n this.props.specActions.requestResolvedSubtree(fullPath)\n }\n\n const content = \n\n const title = \n \n {displayName}\n \n \n\n return
\n \n 0 && isShown }\n >{content}\n
\n }).toArray()\n }\n
\n
\n }\n}\n","import React from \"react\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nconst EnumModel = ({ value, getComponent }) => {\n let ModelCollapse = getComponent(\"ModelCollapse\")\n let collapsedContent = Array [ { value.count() } ]\n return \n Enum:
\n \n [ { value.map(String).join(\", \") } ]\n \n
\n}\nEnumModel.propTypes = {\n value: ImPropTypes.iterable,\n getComponent: ImPropTypes.func\n}\n\nexport default EnumModel\n","export function isAbsoluteUrl(url) {\n return url.match(/^(?:[a-z]+:)?\\/\\//i) // Matches http://, HTTP://, https://, ftp://, //example.com,\n}\n\nexport function addProtocol(url) {\n if (!url.match(/^\\/\\//i)) return url // Checks if protocol is missing e.g. //example.com\n\n return `${window.location.protocol}${url}`\n}\n\nexport function buildBaseUrl(selectedServer, specUrl) {\n if (!selectedServer) return specUrl\n if (isAbsoluteUrl(selectedServer)) return addProtocol(selectedServer)\n\n return new URL(selectedServer, specUrl).href\n}\n\nexport function buildUrl(url, specUrl, { selectedServer=\"\" } = {}) {\n if (!url) return undefined\n if (isAbsoluteUrl(url)) return url\n\n const baseUrl = buildBaseUrl(selectedServer, specUrl)\n if (!isAbsoluteUrl(baseUrl)) {\n return new URL(url, window.location.href).href\n }\n return new URL(url, baseUrl).href\n}\n\n/**\n * Safe version of buildUrl function. `selectedServer` can contain server variables\n * which can fail the URL resolution.\n */\nexport function safeBuildUrl(url, specUrl, { selectedServer=\"\" } = {}) {\n try {\n return buildUrl(url, specUrl, { selectedServer })\n } catch {\n return undefined\n }\n}\n\nexport function sanitizeUrl(url) {\n if (typeof url !== \"string\" || url.trim() === \"\") {\n return \"\"\n }\n\n const urlTrimmed = url.trim()\n const blankURL = \"about:blank\"\n\n try {\n const base = `https://base${String(Math.random()).slice(2)}`\n const urlObject = new URL(urlTrimmed, base)\n const scheme = urlObject.protocol.slice(0, -1)\n\n // check for invalid schemes\n if ([\"javascript\", \"data\", \"vbscript\"].includes(scheme.toLowerCase())) {\n return blankURL\n }\n\n // return sanitized URI reference\n if (urlObject.origin === base) {\n return urlTrimmed.startsWith(\"/\")\n ? `${urlObject.pathname}${urlObject.search}${urlObject.hash}`\n : urlTrimmed.startsWith(\".\")\n ? `.${urlObject.pathname}${urlObject.search}${urlObject.hash}`\n : `${urlObject.pathname.substring(1)}${urlObject.search}${urlObject.hash}`\n }\n\n return String(urlObject)\n } catch {\n return blankURL\n }\n}\n\n","/**\n * @prettier\n */\nimport React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { List } from \"immutable\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { sanitizeUrl } from \"core/utils/url\"\nimport classNames from \"classnames\"\nimport { getExtensions } from \"../../../utils\"\n\nconst braceOpen = \"{\"\nconst braceClose = \"}\"\nconst propClass = \"property\"\n\nexport default class ObjectModel extends Component {\n static propTypes = {\n schema: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n expanded: PropTypes.bool,\n onToggle: PropTypes.func,\n specSelectors: PropTypes.object.isRequired,\n name: PropTypes.string,\n displayName: PropTypes.string,\n isRef: PropTypes.bool,\n expandDepth: PropTypes.number,\n depth: PropTypes.number,\n specPath: ImPropTypes.list.isRequired,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n }\n\n render() {\n let {\n schema,\n name,\n displayName,\n isRef,\n getComponent,\n getConfigs,\n depth,\n onToggle,\n expanded,\n specPath,\n ...otherProps\n } = this.props\n let { specSelectors, expandDepth, includeReadOnly, includeWriteOnly } =\n otherProps\n const { isOAS3 } = specSelectors\n const isEmbedded = depth > 2 || (depth === 2 && specPath.last() !== \"items\")\n\n if (!schema) {\n return null\n }\n\n const { showExtensions } = getConfigs()\n const extensions = showExtensions ? getExtensions(schema) : List()\n\n let description = schema.get(\"description\")\n let properties = schema.get(\"properties\")\n let additionalProperties = schema.get(\"additionalProperties\")\n let title = schema.get(\"title\") || displayName || name\n let requiredProperties = schema.get(\"required\")\n let infoProperties = schema.filter(\n (v, key) =>\n [\"maxProperties\", \"minProperties\", \"nullable\", \"example\"].indexOf(\n key\n ) !== -1\n )\n let deprecated = schema.get(\"deprecated\")\n let externalDocsUrl = schema.getIn([\"externalDocs\", \"url\"])\n let externalDocsDescription = schema.getIn([\"externalDocs\", \"description\"])\n\n const JumpToPath = getComponent(\"JumpToPath\", true)\n const Markdown = getComponent(\"Markdown\", true)\n const Model = getComponent(\"Model\")\n const ModelCollapse = getComponent(\"ModelCollapse\")\n const Property = getComponent(\"Property\")\n const Link = getComponent(\"Link\")\n const ModelExtensions = getComponent(\"ModelExtensions\")\n\n const JumpToPathSection = () => {\n return (\n \n \n \n )\n }\n const collapsedContent = (\n \n {braceOpen}...{braceClose}\n {isRef ? : \"\"}\n \n )\n\n const allOf = specSelectors.isOAS3() ? schema.get(\"allOf\") : null\n const anyOf = specSelectors.isOAS3() ? schema.get(\"anyOf\") : null\n const oneOf = specSelectors.isOAS3() ? schema.get(\"oneOf\") : null\n const not = specSelectors.isOAS3() ? schema.get(\"not\") : null\n\n const titleEl = title && (\n \n {isRef && schema.get(\"$$ref\") && (\n \n {schema.get(\"$$ref\")}\n \n )}\n {title}\n \n )\n\n return (\n \n \n {braceOpen}\n {!isRef ? null : }\n \n {\n \n \n {!description ? null : (\n \n \n \n \n )}\n {externalDocsUrl && (\n \n \n \n \n )}\n {!deprecated ? null : (\n \n \n \n \n )}\n {!(properties && properties.size)\n ? null\n : properties\n .entrySeq()\n .filter(([, value]) => {\n return (\n (!value.get(\"readOnly\") || includeReadOnly) &&\n (!value.get(\"writeOnly\") || includeWriteOnly)\n )\n })\n .map(([key, value]) => {\n let isDeprecated = isOAS3() && value.get(\"deprecated\")\n let isRequired =\n List.isList(requiredProperties) &&\n requiredProperties.contains(key)\n\n let classNames = [\"property-row\"]\n\n if (isDeprecated) {\n classNames.push(\"deprecated\")\n }\n\n if (isRequired) {\n classNames.push(\"required\")\n }\n\n return (\n \n \n \n \n )\n })\n .toArray()}\n {extensions.size === 0 ? null : (\n <>\n \n \n \n \n \n )}\n {!additionalProperties ||\n !additionalProperties.size ? null : (\n \n \n \n \n )}\n {!allOf ? null : (\n \n \n \n \n )}\n {!anyOf ? null : (\n \n \n \n \n )}\n {!oneOf ? null : (\n \n \n \n \n )}\n {!not ? null : (\n \n \n \n \n )}\n \n
description:\n \n
externalDocs:\n \n {externalDocsDescription || externalDocsUrl}\n \n
deprecated:true
\n {key}\n {isRequired && *}\n \n \n
 
{\"< * >:\"}\n \n
{\"allOf ->\"}\n {allOf.map((schema, k) => {\n return (\n
\n \n
\n )\n })}\n
{\"anyOf ->\"}\n {anyOf.map((schema, k) => {\n return (\n
\n \n
\n )\n })}\n
{\"oneOf ->\"}\n {oneOf.map((schema, k) => {\n return (\n
\n \n
\n )\n })}\n
{\"not ->\"}\n
\n \n
\n
\n }\n
\n {braceClose}\n \n {infoProperties.size\n ? infoProperties\n .entrySeq()\n .map(([key, v]) => (\n \n ))\n : null}\n
\n )\n }\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { sanitizeUrl } from \"core/utils/url\"\n\nconst propClass = \"property\"\n\nexport default class ArrayModel extends Component {\n static propTypes = {\n schema: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n name: PropTypes.string,\n displayName: PropTypes.string,\n required: PropTypes.bool,\n expandDepth: PropTypes.number,\n specPath: ImPropTypes.list.isRequired,\n depth: PropTypes.number,\n includeReadOnly: PropTypes.bool,\n includeWriteOnly: PropTypes.bool,\n }\n\n render(){\n let { getComponent, getConfigs, schema, depth, expandDepth, name, displayName, specPath } = this.props\n let description = schema.get(\"description\")\n let items = schema.get(\"items\")\n let title = schema.get(\"title\") || displayName || name\n let properties = schema.filter( ( v, key) => [\"type\", \"items\", \"description\", \"$$ref\", \"externalDocs\"].indexOf(key) === -1 )\n let externalDocsUrl = schema.getIn([\"externalDocs\", \"url\"])\n let externalDocsDescription = schema.getIn([\"externalDocs\", \"description\"])\n\n\n const Markdown = getComponent(\"Markdown\", true)\n const ModelCollapse = getComponent(\"ModelCollapse\")\n const Model = getComponent(\"Model\")\n const Property = getComponent(\"Property\")\n const Link = getComponent(\"Link\")\n\n const titleEl = title &&\n \n { title }\n \n\n /*\n Note: we set `name={null}` in below because we don't want\n the name of the current Model passed (and displayed) as the name of the array element Model\n */\n\n return \n \n [\n {\n properties.size ? properties.entrySeq().map( ( [ key, v ] ) => ) : null\n }\n {\n !description ? (properties.size ?
: null) :\n \n }\n { externalDocsUrl &&\n
\n {externalDocsDescription || externalDocsUrl}\n
\n }\n \n \n \n ]\n
\n
\n }\n}\n","/**\n * @prettier\n */\nimport React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { getExtensions } from \"core/utils\"\nimport { sanitizeUrl } from \"core/utils/url\"\n\nconst propClass = \"property primitive\"\n\nexport default class Primitive extends Component {\n static propTypes = {\n schema: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n name: PropTypes.string,\n displayName: PropTypes.string,\n depth: PropTypes.number,\n expandDepth: PropTypes.number,\n }\n\n render() {\n let {\n schema,\n getComponent,\n getConfigs,\n name,\n displayName,\n depth,\n expandDepth,\n } = this.props\n\n const { showExtensions } = getConfigs()\n\n if (!schema || !schema.get) {\n // don't render if schema isn't correctly formed\n return
\n }\n\n let type = schema.get(\"type\")\n let format = schema.get(\"format\")\n let xml = schema.get(\"xml\")\n let enumArray = schema.get(\"enum\")\n let title = schema.get(\"title\") || displayName || name\n let description = schema.get(\"description\")\n const extensions = getExtensions(schema)\n\n let properties = schema\n .filter(\n (_, key) =>\n [\n \"enum\",\n \"type\",\n \"format\",\n \"description\",\n \"$$ref\",\n \"externalDocs\",\n ].indexOf(key) === -1\n )\n .filterNot((_, key) => extensions.has(key))\n let externalDocsUrl = schema.getIn([\"externalDocs\", \"url\"])\n let externalDocsDescription = schema.getIn([\"externalDocs\", \"description\"])\n\n const Markdown = getComponent(\"Markdown\", true)\n const EnumModel = getComponent(\"EnumModel\")\n const Property = getComponent(\"Property\")\n const ModelCollapse = getComponent(\"ModelCollapse\")\n const Link = getComponent(\"Link\")\n const ModelExtensions = getComponent(\"ModelExtensions\")\n\n const titleEl = title && (\n \n {title}\n \n )\n\n return (\n \n \n \n {name && depth > 1 && {title}}\n {type}\n {format && (${format})}\n {properties.size\n ? properties\n .entrySeq()\n .map(([key, v]) => (\n \n ))\n : null}\n {showExtensions && extensions.size > 0 ? (\n \n ) : null}\n {!description ? null : }\n {externalDocsUrl && (\n
\n \n {externalDocsDescription || externalDocsUrl}\n \n
\n )}\n {xml && xml.size ? (\n \n
\n xml:\n {xml\n .entrySeq()\n .map(([key, v]) => (\n \n
\n    {key}: {String(v)}\n
\n ))\n .toArray()}\n
\n ) : null}\n {enumArray && (\n \n )}\n
\n \n
\n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class Schemes extends React.Component {\n\n static propTypes = {\n specActions: PropTypes.object.isRequired,\n schemes: PropTypes.object.isRequired,\n currentScheme: PropTypes.string.isRequired,\n path: PropTypes.string,\n method: PropTypes.string,\n }\n\n UNSAFE_componentWillMount() {\n let { schemes } = this.props\n\n //fire 'change' event to set default 'value' of select\n this.setScheme(schemes.first())\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n if ( !this.props.currentScheme || !nextProps.schemes.includes(this.props.currentScheme) ) {\n // if we don't have a selected currentScheme or if our selected scheme is no longer an option,\n // then fire 'change' event and select the first scheme in the list of options\n this.setScheme(nextProps.schemes.first())\n }\n }\n\n onChange =( e ) => {\n this.setScheme( e.target.value )\n }\n\n setScheme = ( value ) => {\n let { path, method, specActions } = this.props\n\n specActions.setScheme( value, path, method )\n }\n\n render() {\n let { schemes, currentScheme } = this.props\n\n return (\n \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class SchemesContainer extends React.Component {\n\n static propTypes = {\n specActions: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired\n }\n\n render () {\n const {specActions, specSelectors, getComponent} = this.props\n\n const currentScheme = specSelectors.operationScheme()\n const schemes = specSelectors.schemes()\n\n const Schemes = getComponent(\"schemes\")\n\n const schemesArePresent = schemes && schemes.size\n\n return schemesArePresent ? (\n \n ) : null\n }\n}\n","var x = function(y) {\n\tvar x = {}; __webpack_require__.d(x, y); return x\n} \nvar y = function(x) { return function() { return x; }; }\nvar __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: function() { return __WEBPACK_EXTERNAL_MODULE_react_debounce_input_7ed3e068__[\"default\"]; } });","import React, { PureComponent, Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { List, fromJS } from \"immutable\"\nimport cx from \"classnames\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport DebounceInput from \"react-debounce-input\"\nimport { stringify, isImmutable, immutableToJS } from \"core/utils\"\n\n/* eslint-disable react/jsx-no-bind */\n\nconst noop = ()=> {}\nconst JsonSchemaPropShape = {\n getComponent: PropTypes.func.isRequired,\n value: PropTypes.any,\n onChange: PropTypes.func,\n keyName: PropTypes.any,\n fn: PropTypes.object.isRequired,\n schema: PropTypes.object,\n errors: ImPropTypes.list,\n required: PropTypes.bool,\n dispatchInitialValue: PropTypes.bool,\n description: PropTypes.any,\n disabled: PropTypes.bool,\n}\n\nconst JsonSchemaDefaultProps = {\n value: \"\",\n onChange: noop,\n schema: {},\n keyName: \"\",\n required: false,\n errors: List()\n}\n\nexport class JsonSchemaForm extends Component {\n\n static propTypes = JsonSchemaPropShape\n static defaultProps = JsonSchemaDefaultProps\n\n componentDidMount() {\n const { dispatchInitialValue, value, onChange } = this.props\n if(dispatchInitialValue) {\n onChange(value)\n } else if(dispatchInitialValue === false) {\n onChange(\"\")\n }\n }\n\n render() {\n let { schema, errors, value, onChange, getComponent, fn, disabled } = this.props\n const format = schema && schema.get ? schema.get(\"format\") : null\n const type = schema && schema.get ? schema.get(\"type\") : null\n const foldedType = fn.jsonSchema202012.foldType(immutableToJS(type))\n const isFileUploadIntended = fn.isFileUploadIntended(schema)\n\n let getComponentSilently = (name) => getComponent(name, false, { failSilently: true })\n let Comp = type ? format ?\n getComponentSilently(`JsonSchema_${type}_${format}`) :\n getComponentSilently(`JsonSchema_${type}`) :\n getComponent(\"JsonSchema_string\")\n\n if (!isFileUploadIntended && List.isList(type) && (foldedType === \"array\" || foldedType === \"object\")) {\n Comp = getComponent(\"JsonSchema_object\")\n }\n\n if (!Comp) {\n Comp = getComponent(\"JsonSchema_string\")\n }\n\n return \n }\n}\n\nexport class JsonSchema_string extends Component {\n static propTypes = JsonSchemaPropShape\n static defaultProps = JsonSchemaDefaultProps\n onChange = (e) => {\n const value = this.props.schema && this.props.schema.get(\"type\") === \"file\" ? e.target.files[0] : e.target.value\n this.props.onChange(value, this.props.keyName)\n }\n onEnumChange = (val) => this.props.onChange(val)\n render() {\n let { getComponent, value, schema, errors, required, description, disabled } = this.props\n const enumValue = schema && schema.get ? schema.get(\"enum\") : null\n const format = schema && schema.get ? schema.get(\"format\") : null\n const type = schema && schema.get ? schema.get(\"type\") : null\n const schemaIn = schema && schema.get ? schema.get(\"in\") : null\n if (!value) {\n value = \"\" // value should not be null; this fixes a Debounce error\n } else if (isImmutable(value) || typeof value === \"object\") {\n value = stringify(value)\n }\n\n errors = errors.toJS ? errors.toJS() : []\n\n if ( enumValue ) {\n const Select = getComponent(\"Select\")\n return (\n )\n }\n else {\n return (\n \n )\n }\n }\n}\n\nexport class JsonSchema_array extends PureComponent {\n\n static propTypes = JsonSchemaPropShape\n static defaultProps = JsonSchemaDefaultProps\n\n constructor(props, context) {\n super(props, context)\n this.state = { value: valueOrEmptyList(props.value), schema: props.schema}\n }\n\n UNSAFE_componentWillReceiveProps(props) {\n const value = valueOrEmptyList(props.value)\n if(value !== this.state.value)\n this.setState({ value })\n\n if(props.schema !== this.state.schema)\n this.setState({ schema: props.schema })\n }\n\n onChange = () => {\n this.props.onChange(this.state.value)\n }\n\n onItemChange = (itemVal, i) => {\n this.setState(({ value }) => ({\n value: value.set(i, itemVal)\n }), this.onChange)\n }\n\n removeItem = (i) => {\n this.setState(({ value }) => ({\n value: value.delete(i)\n }), this.onChange)\n }\n\n addItem = () => {\n const { fn } = this.props\n let newValue = valueOrEmptyList(this.state.value)\n this.setState(() => ({\n value: newValue.push(fn.getSampleSchema(this.state.schema.get(\"items\"), false, {\n includeWriteOnly: true\n }))\n }), this.onChange)\n }\n\n onEnumChange = (value) => {\n this.setState(() => ({\n value: value\n }), this.onChange)\n }\n\n render() {\n let { getComponent, required, schema, errors, fn, disabled } = this.props\n\n errors = errors.toJS ? errors.toJS() : Array.isArray(errors) ? errors : []\n const arrayErrors = errors.filter(e => typeof e === \"string\")\n const needsRemoveError = errors.filter(e => e.needRemove !== undefined)\n .map(e => e.error)\n const value = this.state.value // expect Im List\n const shouldRenderValue =\n value && value.count && value.count() > 0 ? true : false\n const schemaItemsEnum = schema.getIn([\"items\", \"enum\"])\n const schemaItemsType = schema.getIn([\"items\", \"type\"])\n const foldedSchemaItemsType = fn.jsonSchema202012.foldType(immutableToJS(schemaItemsType))\n const schemaItemsTypeLabel = fn.jsonSchema202012.getType(immutableToJS(schema.get(\"items\")))\n const schemaItemsFormat = schema.getIn([\"items\", \"format\"])\n const schemaItemsSchema = schema.get(\"items\")\n let ArrayItemsComponent\n let isArrayItemText = false\n let isArrayItemFile = (schemaItemsType === \"file\" || (schemaItemsType === \"string\" && schemaItemsFormat === \"binary\")) ? true : false\n if (schemaItemsType && schemaItemsFormat) {\n ArrayItemsComponent = getComponent(`JsonSchema_${schemaItemsType}_${schemaItemsFormat}`)\n } else if (schemaItemsType === \"boolean\" || schemaItemsType === \"array\" || schemaItemsType === \"object\") {\n ArrayItemsComponent = getComponent(`JsonSchema_${schemaItemsType}`)\n }\n\n if (List.isList(schemaItemsType) && (foldedSchemaItemsType === \"array\" || foldedSchemaItemsType === \"object\")) {\n ArrayItemsComponent = getComponent(`JsonSchema_object`)\n }\n\n // if ArrayItemsComponent not assigned or does not exist,\n // use default schemaItemsType === \"string\" & JsonSchemaArrayItemText component\n if (!ArrayItemsComponent && !isArrayItemFile) {\n isArrayItemText = true\n }\n\n if ( schemaItemsEnum ) {\n const Select = getComponent(\"Select\")\n return ()\n }\n}\n\nexport class JsonSchema_boolean extends Component {\n static propTypes = JsonSchemaPropShape\n static defaultProps = JsonSchemaDefaultProps\n\n onEnumChange = (val) => this.props.onChange(val)\n render() {\n let { getComponent, value, errors, schema, required, disabled } = this.props\n errors = errors.toJS ? errors.toJS() : []\n let enumValue = schema && schema.get ? schema.get(\"enum\") : null\n let allowEmptyValue = !enumValue || !required\n let booleanValue = !enumValue && [\"true\", \"false\"]\n const Select = getComponent(\"Select\")\n\n return (\n \n }\n \n {\n errors.valueSeq().map( (error, key) => {\n return \n } )\n }\n \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class BasicAuth extends React.Component {\n static propTypes = {\n authorized: ImPropTypes.map,\n schema: ImPropTypes.map,\n getComponent: PropTypes.func.isRequired,\n onChange: PropTypes.func.isRequired,\n name: PropTypes.string.isRequired,\n errSelectors: PropTypes.object.isRequired,\n authSelectors: PropTypes.object.isRequired\n }\n\n constructor(props, context) {\n super(props, context)\n let { schema, name } = this.props\n\n let value = this.getValue()\n let username = value.username\n\n this.state = {\n name: name,\n schema: schema,\n value: !username ? {} : {\n username: username\n }\n }\n }\n\n getValue () {\n let { authorized, name } = this.props\n\n return authorized && authorized.getIn([name, \"value\"]) || {}\n }\n\n onChange =(e) => {\n let { onChange } = this.props\n let { value, name } = e.target\n\n let newValue = this.state.value\n newValue[name] = value\n\n this.setState({ value: newValue })\n\n onChange(this.state)\n }\n\n render() {\n let { schema, getComponent, name, errSelectors, authSelectors } = this.props\n const Input = getComponent(\"Input\")\n const Row = getComponent(\"Row\")\n const Col = getComponent(\"Col\")\n const AuthError = getComponent(\"authError\")\n const JumpToPath = getComponent(\"JumpToPath\", true)\n const Markdown = getComponent(\"Markdown\", true)\n const path = authSelectors.selectAuthPath(name)\n let username = this.getValue().username\n let errors = errSelectors.allErrors().filter( err => err.get(\"authId\") === name)\n\n return (\n
\n

Basic authorization

\n { username &&
Authorized
}\n \n \n \n \n \n {\n username ? { username } \n : \n \n \n }\n \n \n \n {\n username ? ****** \n : \n \n \n }\n \n {\n errors.valueSeq().map( (error, key) => {\n return \n } )\n }\n
\n )\n }\n\n}\n","/**\n * @prettier\n */\n\nimport React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { stringify } from \"core/utils\"\nimport { Map } from \"immutable\"\n\nexport default function Example(props) {\n const { example, showValue, getComponent } = props\n\n const Markdown = getComponent(\"Markdown\", true)\n const HighlightCode = getComponent(\"HighlightCode\", true)\n\n if (!example || !Map.isMap(example)) return null\n\n return (\n
\n {example.get(\"description\") ? (\n
\n
Example Description
\n

\n \n

\n
\n ) : null}\n {showValue && example.has(\"value\") ? (\n
\n
Example Value
\n {stringify(example.get(\"value\"))}\n
\n ) : null}\n
\n )\n}\n\nExample.propTypes = {\n example: ImPropTypes.map.isRequired,\n showValue: PropTypes.bool,\n getComponent: PropTypes.func.isRequired,\n}\n","/**\n * @prettier\n */\n\nimport React from \"react\"\nimport { Map } from \"immutable\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class ExamplesSelect extends React.PureComponent {\n static propTypes = {\n examples: ImPropTypes.map.isRequired,\n onSelect: PropTypes.func,\n currentExampleKey: PropTypes.string,\n isModifiedValueAvailable: PropTypes.bool,\n isValueModified: PropTypes.bool,\n showLabels: PropTypes.bool,\n }\n\n static defaultProps = {\n examples: Map({}),\n onSelect: (...args) =>\n // eslint-disable-next-line no-console\n console.log(\n // FIXME: remove before merging to master...\n `DEBUG: ExamplesSelect was not given an onSelect callback`,\n ...args\n ),\n currentExampleKey: null,\n showLabels: true,\n }\n\n _onSelect = (key, { isSyntheticChange = false } = {}) => {\n if (typeof this.props.onSelect === \"function\") {\n this.props.onSelect(key, {\n isSyntheticChange,\n })\n }\n }\n\n _onDomSelect = (e) => {\n if (typeof this.props.onSelect === \"function\") {\n const element = e.target.selectedOptions[0]\n const key = element.getAttribute(\"value\")\n\n this._onSelect(key, {\n isSyntheticChange: false,\n })\n }\n }\n\n getCurrentExample = () => {\n const { examples, currentExampleKey } = this.props\n\n const currentExamplePerProps = examples.get(currentExampleKey)\n\n const firstExamplesKey = examples.keySeq().first()\n const firstExample = examples.get(firstExamplesKey)\n\n return currentExamplePerProps || firstExample || Map({})\n }\n\n componentDidMount() {\n // this is the not-so-great part of ExamplesSelect... here we're\n // artificially kicking off an onSelect event in order to set a default\n // value in state. the consumer has the option to avoid this by checking\n // `isSyntheticEvent`, but we should really be doing this in a selector.\n // TODO: clean this up\n // FIXME: should this only trigger if `currentExamplesKey` is nullish?\n const { onSelect, examples } = this.props\n\n if (typeof onSelect === \"function\") {\n const firstExample = examples.first()\n const firstExampleKey = examples.keyOf(firstExample)\n\n this._onSelect(firstExampleKey, {\n isSyntheticChange: true,\n })\n }\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n const { currentExampleKey, examples } = nextProps\n if (examples !== this.props.examples && !examples.has(currentExampleKey)) {\n // examples have changed from under us, and the currentExampleKey is no longer\n // valid.\n const firstExample = examples.first()\n const firstExampleKey = examples.keyOf(firstExample)\n\n this._onSelect(firstExampleKey, {\n isSyntheticChange: true,\n })\n }\n }\n\n render() {\n const {\n examples,\n currentExampleKey,\n isValueModified,\n isModifiedValueAvailable,\n showLabels,\n } = this.props\n\n return (\n
\n {showLabels ? (\n Examples: \n ) : null}\n \n {isModifiedValueAvailable ? (\n \n ) : null}\n {examples\n .map((example, exampleName) => {\n return (\n \n {(Map.isMap(example) && example.get(\"summary\")) ||\n exampleName}\n \n )\n })\n .valueSeq()}\n \n
\n )\n }\n}\n","/**\n * @prettier\n */\nimport React from \"react\"\nimport { Map, List } from \"immutable\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nimport { stringify } from \"core/utils\"\n\n// This stateful component lets us avoid writing competing values (user\n// modifications vs example values) into global state, and the mess that comes\n// with that: tracking which of the two values are currently used for\n// Try-It-Out, which example a modified value came from, etc...\n//\n// The solution here is to retain the last user-modified value in\n// ExamplesSelectValueRetainer's component state, so that our global state can stay\n// clean, always simply being the source of truth for what value should be both\n// displayed to the user and used as a value during request execution.\n//\n// This approach/tradeoff was chosen in order to encapsulate the particular\n// logic of Examples within the Examples component tree, and to avoid\n// regressions within our current implementation elsewhere (non-Examples\n// definitions, OpenAPI 2.0, etc). A future refactor to global state might make\n// this component unnecessary.\n//\n// TL;DR: this is not our usual approach, but the choice was made consciously.\n\n// Note that `currentNamespace` isn't currently used anywhere!\n\nconst stringifyUnlessList = (input) =>\n List.isList(input) ? input : stringify(input)\n\nexport default class ExamplesSelectValueRetainer extends React.PureComponent {\n static propTypes = {\n examples: ImPropTypes.map,\n onSelect: PropTypes.func,\n updateValue: PropTypes.func, // mechanism to update upstream value\n userHasEditedBody: PropTypes.bool,\n getComponent: PropTypes.func.isRequired,\n currentUserInputValue: PropTypes.any,\n currentKey: PropTypes.string,\n currentNamespace: PropTypes.string,\n setRetainRequestBodyValueFlag: PropTypes.func.isRequired,\n // (also proxies props for Examples)\n }\n\n static defaultProps = {\n userHasEditedBody: false,\n examples: Map({}),\n currentNamespace: \"__DEFAULT__NAMESPACE__\",\n setRetainRequestBodyValueFlag: () => {\n // NOOP\n },\n onSelect: (...args) =>\n // eslint-disable-next-line no-console\n console.log(\n \"ExamplesSelectValueRetainer: no `onSelect` function was provided\",\n ...args\n ),\n updateValue: (...args) =>\n // eslint-disable-next-line no-console\n console.log(\n \"ExamplesSelectValueRetainer: no `updateValue` function was provided\",\n ...args\n ),\n }\n\n constructor(props) {\n super(props)\n\n const valueFromExample = this._getCurrentExampleValue()\n\n this.state = {\n // user edited: last value that came from the world around us, and didn't\n // match the current example's value\n // internal: last value that came from user selecting an Example\n [props.currentNamespace]: Map({\n lastUserEditedValue: this.props.currentUserInputValue,\n lastDownstreamValue: valueFromExample,\n isModifiedValueSelected:\n // valueFromExample !== undefined &&\n this.props.userHasEditedBody ||\n this.props.currentUserInputValue !== valueFromExample,\n }),\n }\n }\n\n componentWillUnmount() {\n this.props.setRetainRequestBodyValueFlag(false)\n }\n\n _getStateForCurrentNamespace = () => {\n const { currentNamespace } = this.props\n\n return (this.state[currentNamespace] || Map()).toObject()\n }\n\n _setStateForCurrentNamespace = (obj) => {\n const { currentNamespace } = this.props\n\n return this._setStateForNamespace(currentNamespace, obj)\n }\n\n _setStateForNamespace = (namespace, obj) => {\n const oldStateForNamespace = this.state[namespace] || Map()\n const newStateForNamespace = oldStateForNamespace.mergeDeep(obj)\n return this.setState({\n [namespace]: newStateForNamespace,\n })\n }\n\n _isCurrentUserInputSameAsExampleValue = () => {\n const { currentUserInputValue } = this.props\n\n const valueFromExample = this._getCurrentExampleValue()\n\n return valueFromExample === currentUserInputValue\n }\n\n _getValueForExample = (exampleKey, props) => {\n // props are accepted so that this can be used in UNSAFE_componentWillReceiveProps,\n // which has access to `nextProps`\n const { examples } = props || this.props\n return stringifyUnlessList(\n (examples || Map({})).getIn([exampleKey, \"value\"])\n )\n }\n\n _getCurrentExampleValue = (props) => {\n // props are accepted so that this can be used in UNSAFE_componentWillReceiveProps,\n // which has access to `nextProps`\n const { currentKey } = props || this.props\n return this._getValueForExample(currentKey, props || this.props)\n }\n\n _onExamplesSelect = (key, { isSyntheticChange } = {}, ...otherArgs) => {\n const { onSelect, updateValue, currentUserInputValue, userHasEditedBody } =\n this.props\n const { lastUserEditedValue } = this._getStateForCurrentNamespace()\n\n const valueFromExample = this._getValueForExample(key)\n\n if (key === \"__MODIFIED__VALUE__\") {\n updateValue(stringifyUnlessList(lastUserEditedValue))\n return this._setStateForCurrentNamespace({\n isModifiedValueSelected: true,\n })\n }\n\n if (typeof onSelect === \"function\") {\n onSelect(key, { isSyntheticChange }, ...otherArgs)\n }\n\n this._setStateForCurrentNamespace({\n lastDownstreamValue: valueFromExample,\n isModifiedValueSelected:\n (isSyntheticChange && userHasEditedBody) ||\n (!!currentUserInputValue && currentUserInputValue !== valueFromExample),\n })\n\n // we never want to send up value updates from synthetic changes\n if (isSyntheticChange) return\n\n if (typeof updateValue === \"function\") {\n updateValue(stringifyUnlessList(valueFromExample))\n }\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n // update `lastUserEditedValue` as new currentUserInput values come in\n\n const {\n currentUserInputValue: newValue,\n examples,\n onSelect,\n userHasEditedBody,\n } = nextProps\n\n const { lastUserEditedValue, lastDownstreamValue } =\n this._getStateForCurrentNamespace()\n\n const valueFromCurrentExample = this._getValueForExample(\n nextProps.currentKey,\n nextProps\n )\n\n const examplesMatchingNewValue = examples.filter(\n (example) =>\n example.get(\"value\") === newValue ||\n // sometimes data is stored as a string (e.g. in Request Bodies), so\n // let's check against a stringified version of our example too\n stringify(example.get(\"value\")) === newValue\n )\n\n if (examplesMatchingNewValue.size) {\n let key\n if (examplesMatchingNewValue.has(nextProps.currentKey)) {\n key = nextProps.currentKey\n } else {\n key = examplesMatchingNewValue.keySeq().first()\n }\n onSelect(key, {\n isSyntheticChange: true,\n })\n } else if (\n newValue !== this.props.currentUserInputValue && // value has changed\n newValue !== lastUserEditedValue && // value isn't already tracked\n newValue !== lastDownstreamValue // value isn't what we've seen on the other side\n ) {\n this.props.setRetainRequestBodyValueFlag(true)\n this._setStateForNamespace(nextProps.currentNamespace, {\n lastUserEditedValue: nextProps.currentUserInputValue,\n isModifiedValueSelected:\n userHasEditedBody || newValue !== valueFromCurrentExample,\n })\n }\n }\n\n render() {\n const {\n currentUserInputValue,\n examples,\n currentKey,\n getComponent,\n userHasEditedBody,\n } = this.props\n const {\n lastDownstreamValue,\n lastUserEditedValue,\n isModifiedValueSelected,\n } = this._getStateForCurrentNamespace()\n\n const ExamplesSelect = getComponent(\"ExamplesSelect\")\n\n return (\n \n )\n }\n}\n","import parseUrl from \"url-parse\"\nimport Im from \"immutable\"\nimport { btoa, generateCodeVerifier, createCodeChallenge } from \"core/utils\"\nimport { sanitizeUrl } from \"core/utils/url\"\n\nexport default function authorize ( { auth, authActions, errActions, configs, authConfigs={}, currentServer } ) {\n let { schema, scopes, name, clientId } = auth\n let flow = schema.get(\"flow\")\n let query = []\n\n switch (flow) {\n case \"password\":\n authActions.authorizePassword(auth)\n return\n\n case \"application\":\n authActions.authorizeApplication(auth)\n return\n\n case \"accessCode\":\n query.push(\"response_type=code\")\n break\n\n case \"implicit\":\n query.push(\"response_type=token\")\n break\n\n case \"clientCredentials\":\n case \"client_credentials\":\n // OAS3\n authActions.authorizeApplication(auth)\n return\n\n case \"authorizationCode\":\n case \"authorization_code\":\n // OAS3\n query.push(\"response_type=code\")\n break\n }\n\n if (typeof clientId === \"string\") {\n query.push(\"client_id=\" + encodeURIComponent(clientId))\n }\n\n let redirectUrl = configs.oauth2RedirectUrl\n\n // todo move to parser\n if (typeof redirectUrl === \"undefined\") {\n errActions.newAuthErr( {\n authId: name,\n source: \"validation\",\n level: \"error\",\n message: \"oauth2RedirectUrl configuration is not passed. Oauth2 authorization cannot be performed.\"\n })\n return\n }\n query.push(\"redirect_uri=\" + encodeURIComponent(redirectUrl))\n\n let scopesArray = []\n if (Array.isArray(scopes)) {\n scopesArray = scopes\n } else if (Im.List.isList(scopes)) {\n scopesArray = scopes.toArray()\n }\n\n if (scopesArray.length > 0) {\n let scopeSeparator = authConfigs.scopeSeparator || \" \"\n\n query.push(\"scope=\" + encodeURIComponent(scopesArray.join(scopeSeparator)))\n }\n\n let state = btoa(new Date())\n\n query.push(\"state=\" + encodeURIComponent(state))\n\n if (typeof authConfigs.realm !== \"undefined\") {\n query.push(\"realm=\" + encodeURIComponent(authConfigs.realm))\n }\n\n if ((flow === \"authorizationCode\" || flow === \"authorization_code\" || flow === \"accessCode\") && authConfigs.usePkceWithAuthorizationCodeGrant) {\n const codeVerifier = generateCodeVerifier()\n const codeChallenge = createCodeChallenge(codeVerifier)\n\n query.push(\"code_challenge=\" + codeChallenge)\n query.push(\"code_challenge_method=S256\")\n\n // storing the Code Verifier so it can be sent to the token endpoint\n // when exchanging the Authorization Code for an Access Token\n auth.codeVerifier = codeVerifier\n }\n\n let { additionalQueryStringParams } = authConfigs\n\n for (let key in additionalQueryStringParams) {\n if (typeof additionalQueryStringParams[key] !== \"undefined\") {\n query.push([key, additionalQueryStringParams[key]].map(encodeURIComponent).join(\"=\"))\n }\n }\n\n const authorizationUrl = schema.get(\"authorizationUrl\")\n let sanitizedAuthorizationUrl\n if (currentServer) {\n // OpenAPI 3\n sanitizedAuthorizationUrl = parseUrl(\n sanitizeUrl(authorizationUrl),\n currentServer,\n true\n ).toString()\n } else {\n sanitizedAuthorizationUrl = sanitizeUrl(authorizationUrl)\n }\n let url = [sanitizedAuthorizationUrl, query.join(\"&\")].join(authorizationUrl.indexOf(\"?\") === -1 ? \"?\" : \"&\")\n\n // pass action authorizeOauth2 and authentication data through window\n // to authorize with oauth2\n\n let callback\n if (flow === \"implicit\") {\n callback = authActions.preAuthorizeImplicit\n } else if (authConfigs.useBasicAuthenticationWithAccessCodeGrant) {\n callback = authActions.authorizeAccessCodeWithBasicAuthentication\n } else {\n callback = authActions.authorizeAccessCodeWithFormParams\n }\n\n authActions.authPopup(url, {\n auth: auth,\n state: state,\n redirectUrl: redirectUrl,\n callback: callback,\n errCb: errActions.newAuthErr\n })\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport oauth2Authorize from \"core/oauth2-authorize\"\n\nexport default class Oauth2 extends React.Component {\n static propTypes = {\n name: PropTypes.string,\n authorized: PropTypes.object,\n getComponent: PropTypes.func.isRequired,\n schema: PropTypes.object.isRequired,\n authSelectors: PropTypes.object.isRequired,\n authActions: PropTypes.object.isRequired,\n errSelectors: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n errActions: PropTypes.object.isRequired,\n getConfigs: PropTypes.any\n }\n\n constructor(props, context) {\n super(props, context)\n let { name, schema, authorized, authSelectors } = this.props\n let auth = authorized && authorized.get(name)\n let authConfigs = authSelectors.getConfigs() || {}\n let username = auth && auth.get(\"username\") || \"\"\n let clientId = auth && auth.get(\"clientId\") || authConfigs.clientId || \"\"\n let clientSecret = auth && auth.get(\"clientSecret\") || authConfigs.clientSecret || \"\"\n let passwordType = auth && auth.get(\"passwordType\") || \"basic\"\n let scopes = auth && auth.get(\"scopes\") || authConfigs.scopes || []\n if (typeof scopes === \"string\") {\n scopes = scopes.split(authConfigs.scopeSeparator || \" \")\n }\n\n this.state = {\n appName: authConfigs.appName,\n name: name,\n schema: schema,\n scopes: scopes,\n clientId: clientId,\n clientSecret: clientSecret,\n username: username,\n password: \"\",\n passwordType: passwordType\n }\n }\n\n close = (e) => {\n e.preventDefault()\n let { authActions } = this.props\n\n authActions.showDefinitions(false)\n }\n\n authorize =() => {\n let { authActions, errActions, getConfigs, authSelectors, oas3Selectors } = this.props\n let configs = getConfigs()\n let authConfigs = authSelectors.getConfigs()\n\n errActions.clear({authId: name,type: \"auth\", source: \"auth\"})\n oauth2Authorize({\n auth: this.state,\n currentServer: oas3Selectors.serverEffectiveValue(oas3Selectors.selectedServer()),\n authActions,\n errActions,\n configs,\n authConfigs\n })\n }\n\n onScopeChange =(e) => {\n let { target } = e\n let { checked } = target\n let scope = target.dataset.value\n\n if ( checked && this.state.scopes.indexOf(scope) === -1 ) {\n let newScopes = this.state.scopes.concat([scope])\n this.setState({ scopes: newScopes })\n } else if ( !checked && this.state.scopes.indexOf(scope) > -1) {\n this.setState({ scopes: this.state.scopes.filter((val) => val !== scope) })\n }\n }\n\n onInputChange =(e) => {\n let { target : { dataset : { name }, value } } = e\n let state = {\n [name]: value\n }\n\n this.setState(state)\n }\n\n selectScopes =(e) => {\n if (e.target.dataset.all) {\n this.setState({\n scopes: Array.from((this.props.schema.get(\"allowedScopes\") || this.props.schema.get(\"scopes\")).keys())\n })\n } else {\n this.setState({ scopes: [] })\n }\n }\n\n logout =(e) => {\n e.preventDefault()\n let { authActions, errActions, name } = this.props\n\n errActions.clear({authId: name, type: \"auth\", source: \"auth\"})\n authActions.logoutWithPersistOption([ name ])\n }\n\n render() {\n let {\n schema, getComponent, authSelectors, errSelectors, name, specSelectors\n } = this.props\n const Input = getComponent(\"Input\")\n const Row = getComponent(\"Row\")\n const Col = getComponent(\"Col\")\n const Button = getComponent(\"Button\")\n const AuthError = getComponent(\"authError\")\n const JumpToPath = getComponent(\"JumpToPath\", true)\n const Markdown = getComponent(\"Markdown\", true)\n const InitializedInput = getComponent(\"InitializedInput\")\n\n const { isOAS3 } = specSelectors\n\n let oidcUrl = isOAS3() ? schema.get(\"openIdConnectUrl\") : null\n\n // Auth type consts\n const AUTH_FLOW_IMPLICIT = \"implicit\"\n const AUTH_FLOW_PASSWORD = \"password\"\n const AUTH_FLOW_ACCESS_CODE = isOAS3() ? (oidcUrl ? \"authorization_code\" : \"authorizationCode\") : \"accessCode\"\n const AUTH_FLOW_APPLICATION = isOAS3() ? (oidcUrl ? \"client_credentials\" : \"clientCredentials\") : \"application\"\n\n const path = authSelectors.selectAuthPath(name)\n\n let authConfigs = authSelectors.getConfigs() || {}\n let isPkceCodeGrant = !!authConfigs.usePkceWithAuthorizationCodeGrant\n\n let flow = schema.get(\"flow\")\n let flowToDisplay = flow === AUTH_FLOW_ACCESS_CODE && isPkceCodeGrant ? flow + \" with PKCE\" : flow\n let scopes = schema.get(\"allowedScopes\") || schema.get(\"scopes\")\n let authorizedAuth = authSelectors.authorized().get(name)\n let isAuthorized = !!authorizedAuth\n let errors = errSelectors.allErrors().filter( err => err.get(\"authId\") === name)\n let isValid = !errors.filter( err => err.get(\"source\") === \"validation\").size\n let description = schema.get(\"description\")\n\n return (\n
\n

{name} (OAuth2, { flowToDisplay })

\n { !this.state.appName ? null :
Application: { this.state.appName }
}\n { description && }\n\n { isAuthorized &&
Authorized
}\n\n { oidcUrl &&

OpenID Connect URL: { oidcUrl }

}\n { ( flow === AUTH_FLOW_IMPLICIT || flow === AUTH_FLOW_ACCESS_CODE ) &&

Authorization URL: { schema.get(\"authorizationUrl\") }

}\n { ( flow === AUTH_FLOW_PASSWORD || flow === AUTH_FLOW_ACCESS_CODE || flow === AUTH_FLOW_APPLICATION ) &&

Token URL: { schema.get(\"tokenUrl\") }

}\n

Flow: { flowToDisplay }

\n\n {\n flow !== AUTH_FLOW_PASSWORD ? null\n : \n \n \n {\n isAuthorized ? { this.state.username } \n : \n \n \n }\n \n {\n\n }\n \n \n {\n isAuthorized ? ****** \n : \n \n \n }\n \n \n \n {\n isAuthorized ? { this.state.passwordType } \n : \n \n \n }\n \n \n }\n {\n ( flow === AUTH_FLOW_APPLICATION || flow === AUTH_FLOW_IMPLICIT || flow === AUTH_FLOW_ACCESS_CODE || flow === AUTH_FLOW_PASSWORD ) &&\n ( !isAuthorized || isAuthorized && this.state.clientId) && \n \n {\n isAuthorized ? ****** \n : \n \n \n }\n \n }\n\n {\n ( (flow === AUTH_FLOW_APPLICATION || flow === AUTH_FLOW_ACCESS_CODE || flow === AUTH_FLOW_PASSWORD) && \n \n {\n isAuthorized ? ****** \n : \n \n \n }\n\n \n )}\n\n {\n !isAuthorized && scopes && scopes.size ?
\n

\n Scopes:\n select all\n select none\n

\n { scopes.map((description, name) => {\n return (\n \n
\n \n \n
\n
\n )\n }).toArray()\n }\n
: null\n }\n\n {\n errors.valueSeq().map( (error, key) => {\n return \n } )\n }\n
\n { isValid &&\n ( isAuthorized ? \n : \n )\n }\n \n
\n\n
\n )\n }\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class Clear extends Component {\n\n onClick =() => {\n let { specActions, path, method } = this.props\n specActions.clearResponse( path, method )\n specActions.clearRequest( path, method )\n }\n\n render(){\n return (\n \n )\n }\n\n static propTypes = {\n specActions: PropTypes.object.isRequired,\n path: PropTypes.string.isRequired,\n method: PropTypes.string.isRequired,\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nconst Headers = ( { headers } )=>{\n return (\n
\n
Response headers
\n
{headers}
\n
)\n}\nHeaders.propTypes = {\n headers: PropTypes.array.isRequired\n}\n\nconst Duration = ( { duration } ) => {\n return (\n
\n
Request duration
\n
{duration} ms
\n
\n )\n}\nDuration.propTypes = {\n duration: PropTypes.number.isRequired\n}\n\n\nexport default class LiveResponse extends React.Component {\n static propTypes = {\n response: ImPropTypes.map,\n path: PropTypes.string.isRequired,\n method: PropTypes.string.isRequired,\n displayRequestDuration: PropTypes.bool.isRequired,\n specSelectors: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired\n }\n\n shouldComponentUpdate(nextProps) {\n // BUG: props.response is always coming back as a new Immutable instance\n // same issue as responses.jsx (tryItOutResponse)\n return this.props.response !== nextProps.response\n || this.props.path !== nextProps.path\n || this.props.method !== nextProps.method\n || this.props.displayRequestDuration !== nextProps.displayRequestDuration\n }\n\n render() {\n const { response, getComponent, getConfigs, displayRequestDuration, specSelectors, path, method } = this.props\n const { showMutatedRequest, requestSnippetsEnabled } = getConfigs()\n\n const curlRequest = showMutatedRequest ? specSelectors.mutatedRequestFor(path, method) : specSelectors.requestFor(path, method)\n const status = response.get(\"status\")\n const url = curlRequest.get(\"url\")\n const headers = response.get(\"headers\").toJS()\n const notDocumented = response.get(\"notDocumented\")\n const isError = response.get(\"error\")\n const body = response.get(\"text\")\n const duration = response.get(\"duration\")\n const headersKeys = Object.keys(headers)\n const contentType = headers[\"content-type\"] || headers[\"Content-Type\"]\n\n const ResponseBody = getComponent(\"responseBody\")\n const returnObject = headersKeys.map(key => {\n var joinedHeaders = Array.isArray(headers[key]) ? headers[key].join() : headers[key]\n return {key}: {joinedHeaders} \n })\n const hasHeaders = returnObject.length !== 0\n const Markdown = getComponent(\"Markdown\", true)\n const RequestSnippets = getComponent(\"RequestSnippets\", true)\n const Curl = getComponent(\"curl\", true)\n\n return (\n
\n { curlRequest && requestSnippetsEnabled \n ? \n : \n }\n { url &&
\n
\n

Request URL

\n
{url}
\n
\n
\n }\n

Server response

\n \n \n \n \n \n \n \n \n \n \n \n \n \n
CodeDetails
\n { status }\n {\n notDocumented ?
\n Undocumented \n
\n : null\n }\n
\n {\n isError ? \n : null\n }\n {\n body ? \n : null\n }\n {\n hasHeaders ? : null\n }\n {\n displayRequestDuration && duration ? : null\n }\n
\n
\n )\n }\n}\n","import React from \"react\"\nimport URL from \"url-parse\"\n\nimport PropTypes from \"prop-types\"\nimport { requiresValidationURL } from \"core/utils\"\nimport { sanitizeUrl } from \"core/utils/url\"\nimport win from \"core/window\"\n\nexport default class OnlineValidatorBadge extends React.Component {\n static propTypes = {\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired\n }\n\n constructor(props, context) {\n super(props, context)\n let { getConfigs } = props\n let { validatorUrl } = getConfigs()\n this.state = {\n url: this.getDefinitionUrl(),\n validatorUrl: validatorUrl === undefined ? \"https://validator.swagger.io/validator\" : validatorUrl\n }\n }\n\n getDefinitionUrl = () => {\n // TODO: test this behavior by stubbing `window.location` in an Enzyme/JSDom env\n let { specSelectors } = this.props\n\n const urlObject = new URL(specSelectors.url(), win.location)\n return urlObject.toString()\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n let { getConfigs } = nextProps\n let { validatorUrl } = getConfigs()\n\n this.setState({\n url: this.getDefinitionUrl(),\n validatorUrl: validatorUrl === undefined ? \"https://validator.swagger.io/validator\" : validatorUrl\n })\n }\n\n render() {\n let { getConfigs } = this.props\n let { spec } = getConfigs()\n\n let sanitizedValidatorUrl = sanitizeUrl(this.state.validatorUrl)\n\n if ( typeof spec === \"object\" && Object.keys(spec).length) return null\n\n if (!this.state.url || !requiresValidationURL(this.state.validatorUrl)\n || !requiresValidationURL(this.state.url)) {\n return null\n }\n\n return (\n \n \n \n )\n }\n}\n\n\nclass ValidatorImage extends React.Component {\n static propTypes = {\n src: PropTypes.string,\n alt: PropTypes.string\n }\n\n constructor(props) {\n super(props)\n this.state = {\n loaded: false,\n error: false\n }\n }\n\n componentDidMount() {\n const img = new Image()\n img.onload = () => {\n this.setState({\n loaded: true\n })\n }\n img.onerror = () => {\n this.setState({\n error: true\n })\n }\n img.src = this.props.src\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n if (nextProps.src !== this.props.src) {\n const img = new Image()\n img.onload = () => {\n this.setState({\n loaded: true\n })\n }\n img.onerror = () => {\n this.setState({\n error: true\n })\n }\n img.src = nextProps.src\n }\n }\n\n render() {\n if (this.state.error) {\n return {\"Error\"}\n } else if (!this.state.loaded) {\n return null\n }\n return {this.props.alt}\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport Im from \"immutable\"\n\nexport default class Operations extends React.Component {\n\n static propTypes = {\n specSelectors: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n oas3Selectors: PropTypes.func.isRequired,\n layoutSelectors: PropTypes.object.isRequired,\n layoutActions: PropTypes.object.isRequired,\n authActions: PropTypes.object.isRequired,\n authSelectors: PropTypes.object.isRequired,\n getConfigs: PropTypes.func.isRequired,\n fn: PropTypes.func.isRequired\n }\n\n render() {\n let {\n specSelectors,\n } = this.props\n\n const taggedOps = specSelectors.taggedOperations()\n\n if(taggedOps.size === 0) {\n return

No operations defined in spec!

\n }\n\n return (\n
\n { taggedOps.map(this.renderOperationTag).toArray() }\n { taggedOps.size < 1 ?

No operations defined in spec!

: null }\n
\n )\n }\n\n renderOperationTag = (tagObj, tag) => {\n const {\n specSelectors,\n getComponent,\n oas3Selectors,\n layoutSelectors,\n layoutActions,\n getConfigs,\n } = this.props\n const validOperationMethods = specSelectors.validOperationMethods()\n const OperationContainer = getComponent(\"OperationContainer\", true)\n const OperationTag = getComponent(\"OperationTag\")\n const operations = tagObj.get(\"operations\")\n return (\n \n
\n {\n operations.map(op => {\n const path = op.get(\"path\")\n const method = op.get(\"method\")\n const specPath = Im.List([\"paths\", path, method])\n\n if (validOperationMethods.indexOf(method) === -1) {\n return null\n }\n\n return (\n \n )\n }).toArray()\n }\n
\n \n )\n }\n\n}\n\nOperations.propTypes = {\n layoutActions: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n layoutSelectors: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n fn: PropTypes.object.isRequired\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport Im from \"immutable\"\nimport { createDeepLinkPath, escapeDeepLinkPath, isFunc } from \"core/utils\"\nimport { safeBuildUrl, sanitizeUrl } from \"core/utils/url\"\n\n/* eslint-disable react/jsx-no-bind */\n\nexport default class OperationTag extends React.Component {\n\n static defaultProps = {\n tagObj: Im.fromJS({}),\n tag: \"\",\n }\n\n static propTypes = {\n tagObj: ImPropTypes.map.isRequired,\n tag: PropTypes.string.isRequired,\n\n oas3Selectors: PropTypes.func.isRequired,\n layoutSelectors: PropTypes.object.isRequired,\n layoutActions: PropTypes.object.isRequired,\n\n getConfigs: PropTypes.func.isRequired,\n getComponent: PropTypes.func.isRequired,\n\n specUrl: PropTypes.string.isRequired,\n\n children: PropTypes.element,\n }\n\n render() {\n const {\n tagObj,\n tag,\n children,\n oas3Selectors,\n layoutSelectors,\n layoutActions,\n getConfigs,\n getComponent,\n specUrl,\n } = this.props\n\n let {\n docExpansion,\n deepLinking,\n } = getConfigs()\n\n const Collapse = getComponent(\"Collapse\")\n const Markdown = getComponent(\"Markdown\", true)\n const DeepLink = getComponent(\"DeepLink\")\n const Link = getComponent(\"Link\")\n const ArrowUpIcon = getComponent(\"ArrowUpIcon\")\n const ArrowDownIcon = getComponent(\"ArrowDownIcon\")\n\n let tagDescription = tagObj.getIn([\"tagDetails\", \"description\"], null)\n let tagExternalDocsDescription = tagObj.getIn([\"tagDetails\", \"externalDocs\", \"description\"])\n let rawTagExternalDocsUrl = tagObj.getIn([\"tagDetails\", \"externalDocs\", \"url\"])\n let tagExternalDocsUrl\n if (isFunc(oas3Selectors) && isFunc(oas3Selectors.selectedServer)) {\n tagExternalDocsUrl = safeBuildUrl(rawTagExternalDocsUrl, specUrl, { selectedServer: oas3Selectors.selectedServer() })\n } else {\n tagExternalDocsUrl = rawTagExternalDocsUrl\n }\n\n let isShownKey = [\"operations-tag\", tag]\n let showTag = layoutSelectors.isShown(isShownKey, docExpansion === \"full\" || docExpansion === \"list\")\n\n return (\n
\n\n layoutActions.show(isShownKey, !showTag)}\n className={!tagDescription ? \"opblock-tag no-desc\" : \"opblock-tag\"}\n id={isShownKey.map(v => escapeDeepLinkPath(v)).join(\"-\")}\n data-tag={tag}\n data-is-open={showTag}\n >\n \n {!tagDescription ? :\n \n \n \n }\n\n {!tagExternalDocsUrl ? null :\n
\n \n e.stopPropagation()}\n target=\"_blank\"\n >{tagExternalDocsDescription || tagExternalDocsUrl}\n \n
\n }\n\n\n layoutActions.show(isShownKey, !showTag)}>\n\n {showTag ? : }\n \n \n\n \n {children}\n \n
\n )\n }\n}\n","import React, { PureComponent } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { getExtensions, escapeDeepLinkPath, getList } from \"core/utils\"\nimport { safeBuildUrl, sanitizeUrl } from \"core/utils/url\"\nimport { Iterable, List } from \"immutable\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nimport RollingLoadSVG from \"core/assets/rolling-load.svg\"\n\nexport default class Operation extends PureComponent {\n static propTypes = {\n specPath: ImPropTypes.list.isRequired,\n operation: PropTypes.instanceOf(Iterable).isRequired,\n summary: PropTypes.string,\n response: PropTypes.instanceOf(Iterable),\n request: PropTypes.instanceOf(Iterable),\n\n toggleShown: PropTypes.func.isRequired,\n onTryoutClick: PropTypes.func.isRequired,\n onResetClick: PropTypes.func.isRequired,\n onCancelClick: PropTypes.func.isRequired,\n onExecute: PropTypes.func.isRequired,\n\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n authActions: PropTypes.object,\n authSelectors: PropTypes.object,\n specActions: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n layoutActions: PropTypes.object.isRequired,\n layoutSelectors: PropTypes.object.isRequired,\n fn: PropTypes.object.isRequired\n }\n\n static defaultProps = {\n operation: null,\n response: null,\n request: null,\n specPath: List(),\n summary: \"\"\n }\n\n render() {\n let {\n specPath,\n response,\n request,\n toggleShown,\n onTryoutClick,\n onResetClick,\n onCancelClick,\n onExecute,\n fn,\n getComponent,\n getConfigs,\n specActions,\n specSelectors,\n authActions,\n authSelectors,\n oas3Actions,\n oas3Selectors\n } = this.props\n let operationProps = this.props.operation\n\n let {\n deprecated,\n isShown,\n path,\n method,\n op,\n tag,\n operationId,\n allowTryItOut,\n displayRequestDuration,\n tryItOutEnabled,\n executeInProgress\n } = operationProps.toJS()\n\n let {\n description,\n externalDocs,\n schemes\n } = op\n\n const externalDocsUrl = externalDocs ? safeBuildUrl(externalDocs.url, specSelectors.url(), { selectedServer: oas3Selectors.selectedServer() }) : \"\"\n let operation = operationProps.getIn([\"op\"])\n let responses = operation.get(\"responses\")\n let parameters = getList(operation, [\"parameters\"])\n let operationScheme = specSelectors.operationScheme(path, method)\n let isShownKey = [\"operations\", tag, operationId]\n let extensions = getExtensions(operation)\n\n const Responses = getComponent(\"responses\")\n const Parameters = getComponent( \"parameters\" )\n const Execute = getComponent( \"execute\" )\n const Clear = getComponent( \"clear\" )\n const Collapse = getComponent( \"Collapse\" )\n const Markdown = getComponent(\"Markdown\", true)\n const Schemes = getComponent( \"schemes\" )\n const OperationServers = getComponent( \"OperationServers\" )\n const OperationExt = getComponent( \"OperationExt\" )\n const OperationSummary = getComponent( \"OperationSummary\" )\n const Link = getComponent( \"Link\" )\n\n const { showExtensions } = getConfigs()\n\n // Merge in Live Response\n if(responses && response && response.size > 0) {\n let notDocumented = !responses.get(String(response.get(\"status\"))) && !responses.get(\"default\")\n response = response.set(\"notDocumented\", notDocumented)\n }\n\n let onChangeKey = [ path, method ] // Used to add values to _this_ operation ( indexed by path and method )\n\n const validationErrors = specSelectors.validationErrors([path, method])\n\n return (\n
\n \n \n
\n { (operation && operation.size) || operation === null ? null :\n \n }\n { deprecated &&

Warning: Deprecated

}\n { description &&\n
\n
\n \n
\n
\n }\n {\n externalDocsUrl ?\n
\n

Find more details

\n
\n {externalDocs.description &&\n \n \n \n }\n {externalDocsUrl}\n
\n
: null\n }\n\n { !operation || !operation.size ? null :\n \n }\n\n { !tryItOutEnabled ? null :\n \n }\n\n {!tryItOutEnabled || !allowTryItOut ? null : schemes && schemes.size ?
\n \n
: null\n }\n\n { !tryItOutEnabled || !allowTryItOut || validationErrors.length <= 0 ? null :
\n Please correct the following validation errors and try again.\n
    \n { validationErrors.map((error, index) =>
  • { error }
  • ) }\n
\n
\n }\n\n
\n { !tryItOutEnabled || !allowTryItOut ? null :\n\n \n }\n\n { (!tryItOutEnabled || !response || !allowTryItOut) ? null :\n \n }\n
\n\n {executeInProgress ?
: null}\n\n { !responses ? null :\n \n }\n\n { !showExtensions || !extensions.size ? null :\n \n }\n
\n
\n
\n )\n }\n\n}\n","import React, { PureComponent } from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { opId } from \"swagger-client/es/helpers\"\nimport { Iterable, fromJS, Map } from \"immutable\"\n\nexport default class OperationContainer extends PureComponent {\n constructor(props, context) {\n super(props, context)\n\n const { tryItOutEnabled } = props.getConfigs()\n\n this.state = {\n tryItOutEnabled,\n executeInProgress: false\n }\n }\n\n static propTypes = {\n op: PropTypes.instanceOf(Iterable).isRequired,\n tag: PropTypes.string.isRequired,\n path: PropTypes.string.isRequired,\n method: PropTypes.string.isRequired,\n operationId: PropTypes.string.isRequired,\n showSummary: PropTypes.bool.isRequired,\n isShown: PropTypes.bool.isRequired,\n jumpToKey: PropTypes.string.isRequired,\n allowTryItOut: PropTypes.bool,\n displayOperationId: PropTypes.bool,\n isAuthorized: PropTypes.bool,\n displayRequestDuration: PropTypes.bool,\n response: PropTypes.instanceOf(Iterable),\n request: PropTypes.instanceOf(Iterable),\n security: PropTypes.instanceOf(Iterable),\n isDeepLinkingEnabled: PropTypes.bool.isRequired,\n specPath: ImPropTypes.list.isRequired,\n getComponent: PropTypes.func.isRequired,\n authActions: PropTypes.object,\n oas3Actions: PropTypes.object,\n oas3Selectors: PropTypes.object,\n authSelectors: PropTypes.object,\n specActions: PropTypes.object.isRequired,\n specSelectors: PropTypes.object.isRequired,\n layoutActions: PropTypes.object.isRequired,\n layoutSelectors: PropTypes.object.isRequired,\n fn: PropTypes.object.isRequired,\n getConfigs: PropTypes.func.isRequired\n }\n\n static defaultProps = {\n showSummary: true,\n response: null,\n allowTryItOut: true,\n displayOperationId: false,\n displayRequestDuration: false\n }\n\n mapStateToProps(nextState, props) {\n const { op, layoutSelectors, getConfigs } = props\n const { docExpansion, deepLinking, displayOperationId, displayRequestDuration, supportedSubmitMethods } = getConfigs()\n const showSummary = layoutSelectors.showSummary()\n const operationId = op.getIn([\"operation\", \"__originalOperationId\"]) || op.getIn([\"operation\", \"operationId\"]) || opId(op.get(\"operation\"), props.path, props.method) || op.get(\"id\")\n const isShownKey = [\"operations\", props.tag, operationId]\n const allowTryItOut = supportedSubmitMethods.indexOf(props.method) >= 0 && (typeof props.allowTryItOut === \"undefined\" ?\n props.specSelectors.allowTryItOutFor(props.path, props.method) : props.allowTryItOut)\n const security = op.getIn([\"operation\", \"security\"]) || props.specSelectors.security()\n\n return {\n operationId,\n isDeepLinkingEnabled: deepLinking,\n showSummary,\n displayOperationId,\n displayRequestDuration,\n allowTryItOut,\n security,\n isAuthorized: props.authSelectors.isAuthorized(security),\n isShown: layoutSelectors.isShown(isShownKey, docExpansion === \"full\" ),\n jumpToKey: `paths.${props.path}.${props.method}`,\n response: props.specSelectors.responseFor(props.path, props.method),\n request: props.specSelectors.requestFor(props.path, props.method)\n }\n }\n\n componentDidMount() {\n const { isShown } = this.props\n const resolvedSubtree = this.getResolvedSubtree()\n\n if(isShown && resolvedSubtree === undefined) {\n this.requestResolvedSubtree()\n }\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n const { response, isShown } = nextProps\n const resolvedSubtree = this.getResolvedSubtree()\n\n if(response !== this.props.response) {\n this.setState({ executeInProgress: false })\n }\n\n if(isShown && resolvedSubtree === undefined) {\n this.requestResolvedSubtree()\n }\n }\n\n toggleShown =() => {\n let { layoutActions, tag, operationId, isShown } = this.props\n const resolvedSubtree = this.getResolvedSubtree()\n if(!isShown && resolvedSubtree === undefined) {\n // transitioning from collapsed to expanded\n this.requestResolvedSubtree()\n }\n layoutActions.show([\"operations\", tag, operationId], !isShown)\n }\n\n onCancelClick=() => {\n this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})\n }\n\n onTryoutClick =() => {\n this.setState({tryItOutEnabled: !this.state.tryItOutEnabled})\n }\n\n onResetClick = (pathMethod) => {\n const defaultRequestBodyValue = this.props.oas3Selectors.selectDefaultRequestBodyValue(...pathMethod)\n this.props.oas3Actions.setRequestBodyValue({ value: defaultRequestBodyValue, pathMethod })\n }\n\n onExecute = () => {\n this.setState({ executeInProgress: true })\n }\n\n getResolvedSubtree = () => {\n const {\n specSelectors,\n path,\n method,\n specPath\n } = this.props\n\n if(specPath) {\n return specSelectors.specResolvedSubtree(specPath.toJS())\n }\n\n return specSelectors.specResolvedSubtree([\"paths\", path, method])\n }\n\n requestResolvedSubtree = () => {\n const {\n specActions,\n path,\n method,\n specPath\n } = this.props\n\n\n if(specPath) {\n return specActions.requestResolvedSubtree(specPath.toJS())\n }\n\n return specActions.requestResolvedSubtree([\"paths\", path, method])\n }\n\n render() {\n let {\n op: unresolvedOp,\n tag,\n path,\n method,\n security,\n isAuthorized,\n operationId,\n showSummary,\n isShown,\n jumpToKey,\n allowTryItOut,\n response,\n request,\n displayOperationId,\n displayRequestDuration,\n isDeepLinkingEnabled,\n specPath,\n specSelectors,\n specActions,\n getComponent,\n getConfigs,\n layoutSelectors,\n layoutActions,\n authActions,\n authSelectors,\n oas3Actions,\n oas3Selectors,\n fn\n } = this.props\n\n const Operation = getComponent( \"operation\" )\n\n const resolvedSubtree = this.getResolvedSubtree() || Map()\n\n const operationProps = fromJS({\n op: resolvedSubtree,\n tag,\n path,\n summary: unresolvedOp.getIn([\"operation\", \"summary\"]) || \"\",\n deprecated: resolvedSubtree.get(\"deprecated\") || unresolvedOp.getIn([\"operation\", \"deprecated\"]) || false,\n method,\n security,\n isAuthorized,\n operationId,\n originalOperationId: resolvedSubtree.getIn([\"operation\", \"__originalOperationId\"]),\n showSummary,\n isShown,\n jumpToKey,\n allowTryItOut,\n request,\n displayOperationId,\n displayRequestDuration,\n isDeepLinkingEnabled,\n executeInProgress: this.state.executeInProgress,\n tryItOutEnabled: this.state.tryItOutEnabled\n })\n\n return (\n \n )\n }\n\n}\n","var x = function(y) {\n\tvar x = {}; __webpack_require__.d(x, y); return x\n} \nvar y = function(x) { return function() { return x; }; }\nvar __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: function() { return __WEBPACK_EXTERNAL_MODULE_lodash_toString_da931f05__[\"default\"]; } });","import React, { PureComponent } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { Iterable, List } from \"immutable\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport toString from \"lodash/toString\"\n\n\nexport default class OperationSummary extends PureComponent {\n\n static propTypes = {\n specPath: ImPropTypes.list.isRequired,\n operationProps: PropTypes.instanceOf(Iterable).isRequired,\n isShown: PropTypes.bool.isRequired,\n toggleShown: PropTypes.func.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n authActions: PropTypes.object,\n authSelectors: PropTypes.object,\n }\n\n static defaultProps = {\n operationProps: null,\n specPath: List(),\n summary: \"\"\n }\n\n render() {\n\n let {\n isShown,\n toggleShown,\n getComponent,\n authActions,\n authSelectors,\n operationProps,\n specPath,\n } = this.props\n\n let {\n summary,\n isAuthorized,\n method,\n op,\n showSummary,\n path,\n operationId,\n originalOperationId,\n displayOperationId,\n } = operationProps.toJS()\n\n let {\n summary: resolvedSummary,\n } = op\n\n let security = operationProps.get(\"security\")\n\n const AuthorizeOperationBtn = getComponent(\"authorizeOperationBtn\", true)\n const OperationSummaryMethod = getComponent(\"OperationSummaryMethod\")\n const OperationSummaryPath = getComponent(\"OperationSummaryPath\")\n const JumpToPath = getComponent(\"JumpToPath\", true)\n const CopyToClipboardBtn = getComponent(\"CopyToClipboardBtn\", true)\n const ArrowUpIcon = getComponent(\"ArrowUpIcon\")\n const ArrowDownIcon = getComponent(\"ArrowDownIcon\")\n\n const hasSecurity = security && !!security.count()\n const securityIsOptional = hasSecurity && security.size === 1 && security.first().isEmpty()\n const allowAnonymous = !hasSecurity || securityIsOptional\n return (\n
\n \n \n
\n \n\n {!showSummary ? null :\n
\n {toString(resolvedSummary || summary)}\n
\n }\n
\n\n {displayOperationId && (originalOperationId || operationId) ? {originalOperationId || operationId} : null}\n \n \n {\n allowAnonymous ? null :\n {\n const applicableDefinitions = authSelectors.definitionsForRequirements(security)\n authActions.showDefinitions(applicableDefinitions)\n }}\n />\n }\n {/* TODO: use wrapComponents here, swagger-ui doesn't care about jumpToPath */}\n \n {isShown ? : }\n \n
\n )\n }\n}\n","import React, { PureComponent } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { Iterable } from \"immutable\"\n\nexport default class OperationSummaryMethod extends PureComponent {\n\n static propTypes = {\n operationProps: PropTypes.instanceOf(Iterable).isRequired,\n method: PropTypes.string.isRequired,\n }\n\n static defaultProps = {\n operationProps: null,\n }\n render() {\n\n let {\n method,\n } = this.props\n\n return (\n {method.toUpperCase()}\n )\n }\n}\n","import React, { PureComponent } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { Iterable } from \"immutable\"\nimport { createDeepLinkPath } from \"core/utils\"\nimport ImPropTypes from \"react-immutable-proptypes\"\n\nexport default class OperationSummaryPath extends PureComponent{\n\n static propTypes = {\n specPath: ImPropTypes.list.isRequired,\n operationProps: PropTypes.instanceOf(Iterable).isRequired,\n getComponent: PropTypes.func.isRequired,\n }\n\n render(){\n let {\n getComponent,\n operationProps,\n } = this.props\n\n\n let {\n deprecated,\n isShown,\n path,\n tag,\n operationId,\n isDeepLinkingEnabled,\n } = operationProps.toJS()\n\n /**\n * Add word-break elements between each segment, before the slash\n * to allow browsers an opportunity to break long paths into sensible segments.\n */\n const pathParts = path.split(/(?=\\/)/g)\n for (let i = 1; i < pathParts.length; i += 2) {\n pathParts.splice(i, 0, )\n }\n\n const DeepLink = getComponent( \"DeepLink\" )\n\n return(\n \n \n \n\n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport const OperationExt = ({ extensions, getComponent }) => {\n let OperationExtRow = getComponent(\"OperationExtRow\")\n return (\n
\n
\n

Extensions

\n
\n
\n\n \n \n \n \n \n \n \n \n {\n extensions.entrySeq().map(([k, v]) => )\n }\n \n
FieldValue
\n
\n
\n )\n}\nOperationExt.propTypes = {\n extensions: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired\n}\n\nexport default OperationExt\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport const OperationExtRow = ({ xKey, xVal }) => {\n const xNormalizedValue = !xVal ? null : xVal.toJS ? xVal.toJS() : xVal\n\n return (\n { xKey }\n { JSON.stringify(xNormalizedValue) }\n )\n}\nOperationExtRow.propTypes = {\n xKey: PropTypes.string,\n xVal: PropTypes.any\n}\n\nexport default OperationExtRow\n","/**\n * Replace invalid characters from a string to create an html-ready ID\n *\n * @param {string} id A string that may contain invalid characters for the HTML ID attribute\n * @param {string} [replacement=_] The string to replace invalid characters with; \"_\" by default\n * @return {string} Information about the parameter schema\n */\nexport default function createHtmlReadyId(id, replacement = \"_\") {\n return id.replace(/[^\\w-]/g, replacement)\n}\n","import React from \"react\"\nimport { fromJS, Iterable } from \"immutable\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { defaultStatusCode, getAcceptControllingResponse } from \"core/utils\"\nimport createHtmlReadyId from \"core/utils/create-html-ready-id\"\n\nexport default class Responses extends React.Component {\n static propTypes = {\n tryItOutResponse: PropTypes.instanceOf(Iterable),\n responses: PropTypes.instanceOf(Iterable).isRequired,\n produces: PropTypes.instanceOf(Iterable),\n producesValue: PropTypes.any,\n displayRequestDuration: PropTypes.bool.isRequired,\n path: PropTypes.string.isRequired,\n method: PropTypes.string.isRequired,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n specPath: ImPropTypes.list.isRequired,\n fn: PropTypes.object.isRequired\n }\n\n static defaultProps = {\n tryItOutResponse: null,\n produces: fromJS([\"application/json\"]),\n displayRequestDuration: false\n }\n\n // These performance-enhancing checks were disabled as part of Multiple Examples\n // because they were causing data-consistency issues\n //\n // shouldComponentUpdate(nextProps) {\n // // BUG: props.tryItOutResponse is always coming back as a new Immutable instance\n // let render = this.props.tryItOutResponse !== nextProps.tryItOutResponse\n // || this.props.responses !== nextProps.responses\n // || this.props.produces !== nextProps.produces\n // || this.props.producesValue !== nextProps.producesValue\n // || this.props.displayRequestDuration !== nextProps.displayRequestDuration\n // || this.props.path !== nextProps.path\n // || this.props.method !== nextProps.method\n // return render\n // }\n\n\tonChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue([this.props.path, this.props.method], val)\n\n onResponseContentTypeChange = ({ controlsAcceptHeader, value }) => {\n const { oas3Actions, path, method } = this.props\n if(controlsAcceptHeader) {\n oas3Actions.setResponseContentType({\n value,\n path,\n method\n })\n }\n }\n\n render() {\n let {\n responses,\n tryItOutResponse,\n getComponent,\n getConfigs,\n specSelectors,\n fn,\n producesValue,\n displayRequestDuration,\n specPath,\n path,\n method,\n oas3Selectors,\n oas3Actions,\n } = this.props\n let defaultCode = defaultStatusCode( responses )\n\n const ContentType = getComponent( \"contentType\" )\n const LiveResponse = getComponent( \"liveResponse\" )\n const Response = getComponent( \"response\" )\n\n let produces = this.props.produces && this.props.produces.size ? this.props.produces : Responses.defaultProps.produces\n\n const isSpecOAS3 = specSelectors.isOAS3()\n\n const acceptControllingResponse = isSpecOAS3 ?\n getAcceptControllingResponse(responses) : null\n\n const regionId = createHtmlReadyId(`${method}${path}_responses`)\n const controlId = `${regionId}_select`\n\n return (\n
\n
\n

Responses

\n { specSelectors.isOAS3() ? null : }\n
\n
\n {\n !tryItOutResponse ? null\n :
\n \n

Responses

\n
\n\n }\n\n \n \n \n \n \n { specSelectors.isOAS3() ? : null }\n \n \n \n {\n responses.entrySeq().map( ([code, response]) => {\n\n let className = tryItOutResponse && tryItOutResponse.get(\"status\") == code ? \"response_current\" : \"\"\n return (\n \n )\n }).toArray()\n }\n \n
CodeDescriptionLinks
\n
\n
\n )\n }\n}\n","export function canJsonParse(str) {\n try {\n let testValueForJson = JSON.parse(str)\n return testValueForJson ? true : false\n } catch (e) {\n // exception: string is not valid json\n return null\n }\n}\n\nexport function getKnownSyntaxHighlighterLanguage(val) {\n // to start, only check for json. can expand as needed in future\n const isValidJson = canJsonParse(val)\n return isValidJson ? \"json\" : null\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport cx from \"classnames\"\nimport { fromJS, Seq, Iterable, List, Map } from \"immutable\"\nimport { getExtensions, fromJSOrdered, stringify } from \"core/utils\"\nimport { getKnownSyntaxHighlighterLanguage } from \"core/utils/jsonParse\"\n\n/* eslint-disable react/jsx-no-bind */\n\nconst getExampleComponent = ( sampleResponse, HighlightCode ) => {\n if (sampleResponse == null) return null\n\n const testValueForJson = getKnownSyntaxHighlighterLanguage(sampleResponse)\n const language = testValueForJson ? \"json\" : null\n\n return (\n
\n {stringify(sampleResponse)}\n
\n )\n}\n\nexport default class Response extends React.Component {\n constructor(props, context) {\n super(props, context)\n\n this.state = {\n responseContentType: \"\",\n }\n }\n\n static propTypes = {\n path: PropTypes.string.isRequired,\n method: PropTypes.string.isRequired,\n code: PropTypes.string.isRequired,\n response: PropTypes.instanceOf(Iterable),\n className: PropTypes.string,\n getComponent: PropTypes.func.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n specPath: ImPropTypes.list.isRequired,\n fn: PropTypes.object.isRequired,\n contentType: PropTypes.string,\n activeExamplesKey: PropTypes.string,\n controlsAcceptHeader: PropTypes.bool,\n onContentTypeChange: PropTypes.func\n }\n\n static defaultProps = {\n response: fromJS({}),\n onContentTypeChange: () => {}\n }\n\n _onContentTypeChange = (value) => {\n const { onContentTypeChange, controlsAcceptHeader } = this.props\n this.setState({ responseContentType: value })\n onContentTypeChange({\n value: value,\n controlsAcceptHeader\n })\n }\n\n getTargetExamplesKey = () => {\n const { response, contentType, activeExamplesKey } = this.props\n\n const activeContentType = this.state.responseContentType || contentType\n const activeMediaType = response.getIn([\"content\", activeContentType], Map({}))\n const examplesForMediaType = activeMediaType.get(\"examples\", null)\n\n const firstExamplesKey = examplesForMediaType.keySeq().first()\n return activeExamplesKey || firstExamplesKey\n }\n\n render() {\n let {\n path,\n method,\n code,\n response,\n className,\n specPath,\n fn,\n getComponent,\n getConfigs,\n specSelectors,\n contentType,\n controlsAcceptHeader,\n oas3Actions,\n } = this.props\n\n let { inferSchema, getSampleSchema } = fn\n let isOAS3 = specSelectors.isOAS3()\n const { showExtensions } = getConfigs()\n\n let extensions = showExtensions ? getExtensions(response) : null\n let headers = response.get(\"headers\")\n let links = response.get(\"links\")\n const ResponseExtension = getComponent(\"ResponseExtension\")\n const Headers = getComponent(\"headers\")\n const HighlightCode = getComponent(\"HighlightCode\", true)\n const ModelExample = getComponent(\"modelExample\")\n const Markdown = getComponent(\"Markdown\", true)\n const OperationLink = getComponent(\"operationLink\")\n const ContentType = getComponent(\"contentType\")\n const ExamplesSelect = getComponent(\"ExamplesSelect\")\n const Example = getComponent(\"Example\")\n\n\n var schema, specPathWithPossibleSchema\n\n const activeContentType = this.state.responseContentType || contentType\n const activeMediaType = response.getIn([\"content\", activeContentType], Map({}))\n const examplesForMediaType = activeMediaType.get(\"examples\", null)\n\n // Goal: find a schema value for `schema`\n if(isOAS3) {\n const oas3SchemaForContentType = activeMediaType.get(\"schema\")\n\n schema = oas3SchemaForContentType ? inferSchema(oas3SchemaForContentType.toJS()) : null\n specPathWithPossibleSchema = oas3SchemaForContentType ? List([\"content\", this.state.responseContentType, \"schema\"]) : specPath\n } else {\n schema = response.get(\"schema\")\n specPathWithPossibleSchema = response.has(\"schema\") ? specPath.push(\"schema\") : specPath\n }\n\n let mediaTypeExample\n let shouldOverrideSchemaExample = false\n let sampleSchema\n let sampleGenConfig = {\n includeReadOnly: true\n }\n\n // Goal: find an example value for `sampleResponse`\n if(isOAS3) {\n sampleSchema = activeMediaType.get(\"schema\")?.toJS()\n if(Map.isMap(examplesForMediaType) && !examplesForMediaType.isEmpty()) {\n const targetExamplesKey = this.getTargetExamplesKey()\n const targetExample = examplesForMediaType\n .get(targetExamplesKey, Map({}))\n const getMediaTypeExample = (targetExample) =>\n Map.isMap(targetExample)\n ? targetExample.get(\"value\")\n : undefined\n mediaTypeExample = getMediaTypeExample(targetExample)\n if(mediaTypeExample === undefined) {\n mediaTypeExample = getMediaTypeExample(examplesForMediaType.values().next().value)\n }\n shouldOverrideSchemaExample = true\n } else if(activeMediaType.get(\"example\") !== undefined) {\n // use the example key's value\n mediaTypeExample = activeMediaType.get(\"example\")\n shouldOverrideSchemaExample = true\n }\n } else {\n sampleSchema = schema\n sampleGenConfig = {...sampleGenConfig, includeWriteOnly: true}\n const oldOASMediaTypeExample = response.getIn([\"examples\", activeContentType])\n if(oldOASMediaTypeExample) {\n mediaTypeExample = oldOASMediaTypeExample\n shouldOverrideSchemaExample = true\n }\n }\n\n const sampleResponse = getSampleSchema(\n sampleSchema,\n activeContentType,\n sampleGenConfig,\n shouldOverrideSchemaExample ? mediaTypeExample : undefined\n )\n\n const example = getExampleComponent( sampleResponse, HighlightCode )\n\n return (\n \n \n { code }\n \n \n\n
\n \n
\n\n { !showExtensions || !extensions.size ? null : extensions.entrySeq().map(([key, v]) => )}\n\n {isOAS3 && response.get(\"content\") ? (\n
\n \n \n Media type\n \n \n {controlsAcceptHeader ? (\n \n Controls Accept header.\n \n ) : null}\n \n {Map.isMap(examplesForMediaType) && !examplesForMediaType.isEmpty() ? (\n
\n \n Examples\n \n \n oas3Actions.setActiveExamplesMember({\n name: key,\n pathMethod: [path, method],\n contextType: \"responses\",\n contextName: code\n })\n }\n showLabels={false}\n />\n
\n ) : null}\n
\n ) : null}\n\n { example || schema ? (\n \n ) : null }\n\n { isOAS3 && examplesForMediaType ? (\n \n ) : null}\n\n { headers ? (\n \n ) : null}\n\n \n {isOAS3 ? \n { links ?\n links.toSeq().entrySeq().map(([key, link]) => {\n return \n })\n : No links}\n : null}\n \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport const ResponseExtension = ({ xKey, xVal }) => {\n return
{ xKey }: { String(xVal) }
\n}\nResponseExtension.propTypes = {\n xKey: PropTypes.string,\n xVal: PropTypes.any\n}\n\nexport default ResponseExtension\n","var x = function(y) {\n\tvar x = {}; __webpack_require__.d(x, y); return x\n} \nvar y = function(x) { return function() { return x; }; }\nvar __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: function() { return __WEBPACK_EXTERNAL_MODULE_xml_but_prettier_2ed4d5cb__[\"default\"]; } });","var x = function(y) {\n\tvar x = {}; __webpack_require__.d(x, y); return x\n} \nvar y = function(x) { return function() { return x; }; }\nvar __WEBPACK_NAMESPACE_OBJECT__ = x({ [\"default\"]: function() { return __WEBPACK_EXTERNAL_MODULE_lodash_toLower_c29ee2b0__[\"default\"]; } });","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport formatXml from \"xml-but-prettier\"\nimport toLower from \"lodash/toLower\"\nimport { extractFileNameFromContentDispositionHeader } from \"core/utils\"\nimport { getKnownSyntaxHighlighterLanguage } from \"core/utils/jsonParse\"\nimport win from \"core/window\"\n\nexport default class ResponseBody extends React.PureComponent {\n state = {\n parsedContent: null\n }\n\n static propTypes = {\n content: PropTypes.any.isRequired,\n contentType: PropTypes.string,\n getComponent: PropTypes.func.isRequired,\n headers: PropTypes.object,\n url: PropTypes.string\n }\n\n updateParsedContent = (prevContent) => {\n const { content } = this.props\n\n if(prevContent === content) {\n return\n }\n\n if(content && content instanceof Blob) {\n var reader = new FileReader()\n reader.onload = () => {\n this.setState({\n parsedContent: reader.result\n })\n }\n reader.readAsText(content)\n } else {\n this.setState({\n parsedContent: content.toString()\n })\n }\n }\n\n componentDidMount() {\n this.updateParsedContent(null)\n }\n\n componentDidUpdate(prevProps) {\n this.updateParsedContent(prevProps.content)\n }\n\n render() {\n let { content, contentType, url, headers={}, getComponent } = this.props\n const { parsedContent } = this.state\n const HighlightCode = getComponent(\"HighlightCode\", true)\n const downloadName = \"response_\" + new Date().getTime()\n let body, bodyEl\n url = url || \"\"\n\n if (\n (/^application\\/octet-stream/i.test(contentType) ||\n (headers[\"Content-Disposition\"] && /attachment/i.test(headers[\"Content-Disposition\"])) ||\n (headers[\"content-disposition\"] && /attachment/i.test(headers[\"content-disposition\"])) ||\n (headers[\"Content-Description\"] && /File Transfer/i.test(headers[\"Content-Description\"])) ||\n (headers[\"content-description\"] && /File Transfer/i.test(headers[\"content-description\"]))) &&\n (content.size > 0 || content.length > 0)\n ) {\n // Download\n\n if (\"Blob\" in window) {\n let type = contentType || \"text/html\"\n let blob = (content instanceof Blob) ? content : new Blob([content], {type: type})\n let href = window.URL.createObjectURL(blob)\n let fileName = url.substr(url.lastIndexOf(\"/\") + 1)\n let download = [type, fileName, href].join(\":\")\n\n // Use filename from response header,\n // First check if filename is quoted (e.g. contains space), if no, fallback to not quoted check\n let disposition = headers[\"content-disposition\"] || headers[\"Content-Disposition\"]\n if (typeof disposition !== \"undefined\") {\n let responseFilename = extractFileNameFromContentDispositionHeader(disposition)\n if (responseFilename !== null) {\n download = responseFilename\n }\n }\n\n if(win.navigator && win.navigator.msSaveOrOpenBlob) {\n bodyEl = \n } else {\n bodyEl = \n }\n } else {\n bodyEl =
Download headers detected but your browser does not support downloading binary via XHR (Blob).
\n }\n\n // Anything else (CORS)\n } else if (/json/i.test(contentType)) {\n // JSON\n let language = null\n let testValueForJson = getKnownSyntaxHighlighterLanguage(content)\n if (testValueForJson) {\n language = \"json\"\n }\n try {\n body = JSON.stringify(JSON.parse(content), null, \" \")\n } catch (error) {\n body = \"can't parse JSON. Raw result:\\n\\n\" + content\n }\n\n bodyEl = {body}\n\n // XML\n } else if (/xml/i.test(contentType)) {\n body = formatXml(content, {\n textNodesOnSameLine: true,\n indentor: \" \"\n })\n bodyEl = {body}\n\n // HTML or Plain Text\n } else if (toLower(contentType) === \"text/html\" || /text\\/plain/.test(contentType)) {\n bodyEl = {content}\n\n // CSV\n } else if (toLower(contentType) === \"text/csv\" || /text\\/csv/.test(contentType)) {\n bodyEl = {content}\n\n // Image\n } else if (/^image\\//i.test(contentType)) {\n if(contentType.includes(\"svg\")) {\n bodyEl =
{ content }
\n } else {\n bodyEl = \n }\n\n // Audio\n } else if (/^audio\\//i.test(contentType)) {\n bodyEl =
\n } else if (typeof content === \"string\") {\n bodyEl = {content}\n } else if ( content.size > 0 ) {\n // We don't know the contentType, but there was some content returned\n if(parsedContent) {\n // We were able to squeeze something out of content\n // in `updateParsedContent`, so let's display it\n bodyEl =
\n

\n Unrecognized response type; displaying content as text.\n

\n {parsedContent}\n
\n\n } else {\n // Give up\n bodyEl =

\n Unrecognized response type; unable to display.\n

\n }\n } else {\n // We don't know the contentType and there was no content returned\n bodyEl = null\n }\n\n return ( !bodyEl ? null :
\n
Response body
\n { bodyEl }\n
\n )\n }\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\nimport { Map, List } from \"immutable\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport createHtmlReadyId from \"core/utils/create-html-ready-id\"\n\nexport default class Parameters extends Component {\n\n constructor(props) {\n super(props)\n this.state = {\n callbackVisible: false,\n parametersVisible: true,\n }\n }\n\n static propTypes = {\n parameters: ImPropTypes.list.isRequired,\n operation: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n fn: PropTypes.object.isRequired,\n tryItOutEnabled: PropTypes.bool,\n allowTryItOut: PropTypes.bool,\n onTryoutClick: PropTypes.func,\n onResetClick: PropTypes.func,\n onCancelClick: PropTypes.func,\n onChangeKey: PropTypes.array,\n pathMethod: PropTypes.array.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specPath: ImPropTypes.list.isRequired,\n }\n\n\n static defaultProps = {\n onTryoutClick: Function.prototype,\n onCancelClick: Function.prototype,\n tryItOutEnabled: false,\n allowTryItOut: true,\n onChangeKey: [],\n specPath: [],\n }\n\n onChange = (param, value, isXml) => {\n let {\n specActions: { changeParamByIdentity },\n onChangeKey,\n } = this.props\n\n changeParamByIdentity(onChangeKey, param, value, isXml)\n }\n\n onChangeConsumesWrapper = (val) => {\n let {\n specActions: { changeConsumesValue },\n onChangeKey,\n } = this.props\n\n changeConsumesValue(onChangeKey, val)\n }\n\n toggleTab = (tab) => {\n if (tab === \"parameters\") {\n return this.setState({\n parametersVisible: true,\n callbackVisible: false,\n })\n } else if (tab === \"callbacks\") {\n return this.setState({\n callbackVisible: true,\n parametersVisible: false,\n })\n }\n }\n \n onChangeMediaType = ({ value, pathMethod }) => {\n let { specActions, oas3Selectors, oas3Actions } = this.props\n const userHasEditedBody = oas3Selectors.hasUserEditedBody(...pathMethod)\n const shouldRetainRequestBodyValue = oas3Selectors.shouldRetainRequestBodyValue(...pathMethod)\n oas3Actions.setRequestContentType({ value, pathMethod })\n oas3Actions.initRequestBodyValidateError({ pathMethod })\n if (!userHasEditedBody) {\n if(!shouldRetainRequestBodyValue) {\n oas3Actions.setRequestBodyValue({ value: undefined, pathMethod })\n }\n specActions.clearResponse(...pathMethod)\n specActions.clearRequest(...pathMethod)\n specActions.clearValidateParams(pathMethod)\n }\n }\n\n render() {\n\n let {\n onTryoutClick,\n onResetClick,\n parameters,\n allowTryItOut,\n tryItOutEnabled,\n specPath,\n fn,\n getComponent,\n getConfigs,\n specSelectors,\n specActions,\n pathMethod,\n oas3Actions,\n oas3Selectors,\n operation,\n } = this.props\n\n const ParameterRow = getComponent(\"parameterRow\")\n const TryItOutButton = getComponent(\"TryItOutButton\")\n const ContentType = getComponent(\"contentType\")\n const Callbacks = getComponent(\"Callbacks\", true)\n const RequestBody = getComponent(\"RequestBody\", true)\n\n const isExecute = tryItOutEnabled && allowTryItOut\n const isOAS3 = specSelectors.isOAS3()\n\n const regionId = createHtmlReadyId(`${pathMethod[1]}${pathMethod[0]}_requests`)\n const controlId = `${regionId}_select`\n\n const requestBody = operation.get(\"requestBody\")\n\n const groupedParametersArr = Object.values(parameters\n .reduce((acc, x) => {\n const key = x.get(\"in\")\n acc[key] ??= []\n acc[key].push(x)\n return acc\n }, {}))\n .reduce((acc, x) => acc.concat(x), [])\n\n const retainRequestBodyValueFlagForOperation = (f) => oas3Actions.setRetainRequestBodyValueFlag({ value: f, pathMethod })\n return (\n
\n
\n {isOAS3 ? (\n
\n
this.toggleTab(\"parameters\")}\n className={`tab-item ${this.state.parametersVisible && \"active\"}`}>\n

Parameters

\n
\n {operation.get(\"callbacks\") ?\n (\n
this.toggleTab(\"callbacks\")}\n className={`tab-item ${this.state.callbackVisible && \"active\"}`}>\n

Callbacks

\n
\n ) : null\n }\n
\n ) : (\n
\n

Parameters

\n
\n )}\n {allowTryItOut ? (\n onResetClick(pathMethod)}/>\n ) : null}\n
\n {this.state.parametersVisible ?
\n {!groupedParametersArr.length ?

No parameters

:\n
\n \n \n \n \n \n \n \n \n {\n groupedParametersArr.map((parameter, i) => (\n \n ))\n }\n \n
NameDescription
\n
\n }\n
: null}\n\n {this.state.callbackVisible ?
\n \n
: null}\n {\n isOAS3 && requestBody && this.state.parametersVisible &&\n
\n
\n

Request\n body

\n \n
\n
\n {\n this.props.oas3Actions.setActiveExamplesMember({\n name: key,\n pathMethod: this.props.pathMethod,\n contextType: \"requestBody\",\n contextName: \"requestBody\", // RBs are currently not stored per-mediaType\n })\n }\n }\n onChange={(value, path) => {\n if (path) {\n const lastValue = oas3Selectors.requestBodyValue(...pathMethod)\n const usableValue = Map.isMap(lastValue) ? lastValue : Map()\n return oas3Actions.setRequestBodyValue({\n pathMethod,\n value: usableValue.setIn(path, value),\n })\n }\n oas3Actions.setRequestBodyValue({ value, pathMethod })\n }}\n onChangeIncludeEmpty={(name, value) => {\n oas3Actions.setRequestBodyInclusion({\n pathMethod,\n value,\n name,\n })\n }}\n contentType={oas3Selectors.requestContentType(...pathMethod)} />\n
\n
\n }\n
\n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport const ParameterExt = ({ xKey, xVal }) => {\n return
{ xKey }: { String(xVal) }
\n}\nParameterExt.propTypes = {\n xKey: PropTypes.string,\n xVal: PropTypes.any\n}\n\nexport default ParameterExt\n","import React, { Component } from \"react\"\nimport cx from \"classnames\"\nimport PropTypes from \"prop-types\"\n\n\nconst noop = () => { }\n\nconst ParameterIncludeEmptyPropTypes = {\n isIncluded: PropTypes.bool.isRequired,\n isDisabled: PropTypes.bool.isRequired,\n isIncludedOptions: PropTypes.object,\n onChange: PropTypes.func.isRequired,\n}\n\nconst ParameterIncludeEmptyDefaultProps = {\n onChange: noop,\n isIncludedOptions: {},\n}\nexport default class ParameterIncludeEmpty extends Component {\n static propTypes = ParameterIncludeEmptyPropTypes\n static defaultProps = ParameterIncludeEmptyDefaultProps\n\n componentDidMount() {\n const { isIncludedOptions, onChange } = this.props\n const { shouldDispatchInit, defaultValue } = isIncludedOptions\n if (shouldDispatchInit) {\n onChange(defaultValue)\n }\n }\n\n onCheckboxChange = e => {\n const { onChange } = this.props\n onChange(e.target.checked)\n }\n\n render() {\n let { isIncluded, isDisabled } = this.props\n\n return (\n
\n \n
\n )\n }\n}\n","import React, { Component } from \"react\"\nimport { Map, List, fromJS } from \"immutable\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport win from \"core/window\"\nimport { getExtensions, getCommonExtensions, numberToString, stringify, isEmptyValue, immutableToJS } from \"core/utils\"\nimport getParameterSchema from \"core/utils/get-parameter-schema.js\"\n\nexport default class ParameterRow extends Component {\n static propTypes = {\n onChange: PropTypes.func.isRequired,\n param: PropTypes.object.isRequired,\n rawParam: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n fn: PropTypes.object.isRequired,\n isExecute: PropTypes.bool,\n onChangeConsumes: PropTypes.func.isRequired,\n specSelectors: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n pathMethod: PropTypes.array.isRequired,\n getConfigs: PropTypes.func.isRequired,\n specPath: ImPropTypes.list.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n }\n\n constructor(props, context) {\n super(props, context)\n\n this.setDefaultValue()\n }\n\n UNSAFE_componentWillReceiveProps(props) {\n let { specSelectors, pathMethod, rawParam } = props\n let isOAS3 = specSelectors.isOAS3()\n\n let parameterWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || new Map()\n // fallback, if the meta lookup fails\n parameterWithMeta = parameterWithMeta.isEmpty() ? rawParam : parameterWithMeta\n\n let enumValue\n\n if(isOAS3) {\n let { schema } = getParameterSchema(parameterWithMeta, { isOAS3 })\n enumValue = schema ? schema.get(\"enum\") : undefined\n } else {\n enumValue = parameterWithMeta ? parameterWithMeta.get(\"enum\") : undefined\n }\n let paramValue = parameterWithMeta ? parameterWithMeta.get(\"value\") : undefined\n\n let value\n\n if ( paramValue !== undefined ) {\n value = paramValue\n } else if ( rawParam.get(\"required\") && enumValue && enumValue.size ) {\n value = enumValue.first()\n }\n\n if ( value !== undefined && value !== paramValue ) {\n this.onChangeWrapper(numberToString(value))\n }\n // todo: could check if schema here; if not, do not call. impact?\n this.setDefaultValue()\n }\n\n onChangeWrapper = (value, isXml = false) => {\n let { onChange, rawParam } = this.props\n let valueForUpstream\n\n // Coerce empty strings and empty Immutable objects to null\n if(value === \"\" || (value && value.size === 0)) {\n valueForUpstream = null\n } else {\n valueForUpstream = value\n }\n\n return onChange(rawParam, valueForUpstream, isXml)\n }\n\n _onExampleSelect = (key, /* { isSyntheticChange } = {} */) => {\n this.props.oas3Actions.setActiveExamplesMember({\n name: key,\n pathMethod: this.props.pathMethod,\n contextType: \"parameters\",\n contextName: this.getParamKey()\n })\n }\n\n onChangeIncludeEmpty = (newValue) => {\n let { specActions, param, pathMethod } = this.props\n const paramName = param.get(\"name\")\n const paramIn = param.get(\"in\")\n return specActions.updateEmptyParamInclusion(pathMethod, paramName, paramIn, newValue)\n }\n\n setDefaultValue = () => {\n let { specSelectors, pathMethod, rawParam, oas3Selectors, fn } = this.props\n\n const paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()\n let { schema } = getParameterSchema(paramWithMeta, { isOAS3: specSelectors.isOAS3() })\n const parameterMediaType = paramWithMeta\n .get(\"content\", Map())\n .keySeq()\n .first()\n\n // getSampleSchema could return null\n const generatedSampleValue = schema ? fn.getSampleSchema(schema.toJS(), parameterMediaType, {\n\n includeWriteOnly: true\n }) : null\n\n if (!paramWithMeta || paramWithMeta.get(\"value\") !== undefined) {\n return\n }\n\n if( paramWithMeta.get(\"in\") !== \"body\" ) {\n let initialValue\n\n //// Find an initial value\n\n if (specSelectors.isSwagger2()) {\n initialValue =\n paramWithMeta.get(\"x-example\") !== undefined\n ? paramWithMeta.get(\"x-example\")\n : paramWithMeta.getIn([\"schema\", \"example\"]) !== undefined\n ? paramWithMeta.getIn([\"schema\", \"example\"])\n : (schema && schema.getIn([\"default\"]))\n } else if (specSelectors.isOAS3()) {\n schema = this.composeJsonSchema(schema)\n\n const currentExampleKey = oas3Selectors.activeExamplesMember(...pathMethod, \"parameters\", this.getParamKey())\n initialValue =\n paramWithMeta.getIn([\"examples\", currentExampleKey, \"value\"]) !== undefined\n ? paramWithMeta.getIn([\"examples\", currentExampleKey, \"value\"])\n : paramWithMeta.getIn([\"content\", parameterMediaType, \"example\"]) !== undefined\n ? paramWithMeta.getIn([\"content\", parameterMediaType, \"example\"])\n : paramWithMeta.get(\"example\") !== undefined\n ? paramWithMeta.get(\"example\")\n : (schema && schema.get(\"example\")) !== undefined\n ? (schema && schema.get(\"example\"))\n : (schema && schema.get(\"default\")) !== undefined\n ? (schema && schema.get(\"default\"))\n : paramWithMeta.get(\"default\") // ensures support for `parameterMacro`\n }\n\n //// Process the initial value\n\n if(initialValue !== undefined && !List.isList(initialValue)) {\n // Stringify if it isn't a List\n initialValue = stringify(initialValue)\n }\n\n //// Dispatch the initial value\n\n const type = fn.jsonSchema202012.foldType(immutableToJS(schema?.get(\"type\")))\n const itemType = fn.jsonSchema202012.foldType(immutableToJS(schema?.getIn([\"items\", \"type\"])))\n\n if(initialValue !== undefined) {\n this.onChangeWrapper(initialValue)\n } else if(\n type === \"object\"\n && generatedSampleValue\n && !paramWithMeta.get(\"examples\")\n ) {\n // Object parameters get special treatment.. if the user doesn't set any\n // default or example values, we'll provide initial values generated from\n // the schema.\n // However, if `examples` exist for the parameter, we won't do anything,\n // so that the appropriate `examples` logic can take over.\n this.onChangeWrapper(\n List.isList(generatedSampleValue) ? (\n generatedSampleValue\n ) : (\n stringify(generatedSampleValue)\n )\n )\n } \n else if (\n type === \"array\"\n && itemType === \"object\"\n && generatedSampleValue\n && !paramWithMeta.get(\"examples\")\n ) {\n this.onChangeWrapper(\n List.isList(generatedSampleValue) ? (\n generatedSampleValue\n ) : (\n List(JSON.parse(generatedSampleValue))\n )\n )\n }\n }\n }\n\n getParamKey() {\n const { param } = this.props\n\n if(!param) return null\n\n return `${param.get(\"name\")}-${param.get(\"in\")}`\n }\n\n composeJsonSchema(schema) {\n const { fn } = this.props\n const oneOf = schema.get(\"oneOf\")?.get(0)?.toJS()\n const anyOf = schema.get(\"anyOf\")?.get(0)?.toJS()\n return fromJS(fn.mergeJsonSchema(schema.toJS(), oneOf ?? anyOf ?? {}))\n }\n\n render() {\n let {param, rawParam, getComponent, getConfigs, isExecute, fn, onChangeConsumes, specSelectors, pathMethod, specPath, oas3Selectors} = this.props\n\n let isOAS3 = specSelectors.isOAS3()\n\n const { showExtensions, showCommonExtensions } = getConfigs()\n\n if(!param) {\n param = rawParam\n }\n\n if(!rawParam) return null\n\n // const onChangeWrapper = (value) => onChange(param, value)\n const JsonSchemaForm = getComponent(\"JsonSchemaForm\")\n const ParamBody = getComponent(\"ParamBody\")\n let inType = param.get(\"in\")\n let bodyParam = inType !== \"body\" ? null\n : \n\n const ModelExample = getComponent(\"modelExample\")\n const Markdown = getComponent(\"Markdown\", true)\n const ParameterExt = getComponent(\"ParameterExt\")\n const ParameterIncludeEmpty = getComponent(\"ParameterIncludeEmpty\")\n const ExamplesSelectValueRetainer = getComponent(\"ExamplesSelectValueRetainer\")\n const Example = getComponent(\"Example\")\n\n let { schema } = getParameterSchema(param, { isOAS3 })\n let paramWithMeta = specSelectors.parameterWithMetaByIdentity(pathMethod, rawParam) || Map()\n\n if (isOAS3) {\n schema = this.composeJsonSchema(schema)\n }\n \n let format = schema ? schema.get(\"format\") : null\n let isFormData = inType === \"formData\"\n let isFormDataSupported = \"FormData\" in win\n let required = param.get(\"required\")\n\n const typeLabel = fn.jsonSchema202012.getType(immutableToJS(schema))\n const type = fn.jsonSchema202012.foldType(immutableToJS(schema?.get(\"type\"))) \n const itemType = fn.jsonSchema202012.foldType(immutableToJS(schema?.getIn([\"items\", \"type\"]))) \n const isObject = !bodyParam && type === \"object\"\n const isArrayOfObjects = !bodyParam && itemType === \"object\"\n\n let value = paramWithMeta ? paramWithMeta.get(\"value\") : \"\"\n let commonExt = showCommonExtensions ? getCommonExtensions(schema) : null\n let extensions = showExtensions ? getExtensions(param) : null\n\n let paramItems // undefined\n let paramEnum // undefined\n let paramDefaultValue // undefined\n let paramExample // undefined\n let isDisplayParamEnum = false\n\n if ( param !== undefined && schema ) {\n paramItems = schema.get(\"items\")\n }\n\n if (paramItems !== undefined) {\n paramEnum = paramItems.get(\"enum\")\n paramDefaultValue = paramItems.get(\"default\")\n } else if (schema) {\n paramEnum = schema.get(\"enum\")\n }\n\n if ( paramEnum && paramEnum.size && paramEnum.size > 0) {\n isDisplayParamEnum = true\n }\n\n // Default and Example Value for readonly doc\n if ( param !== undefined ) {\n if (schema) {\n paramDefaultValue = schema.get(\"default\")\n }\n if (paramDefaultValue === undefined) {\n paramDefaultValue = param.get(\"default\")\n }\n paramExample = param.get(\"example\")\n if (paramExample === undefined) {\n paramExample = param.get(\"x-example\")\n }\n }\n\n const jsonSchemaForm = bodyParam ? null\n : \n\n return (\n \n \n
\n { param.get(\"name\") }\n { !required ? null :  * }\n
\n
\n { typeLabel }\n { format && (${format})}\n
\n
\n { isOAS3 && param.get(\"deprecated\") ? \"deprecated\": null }\n
\n
({ param.get(\"in\") })
\n \n\n \n { param.get(\"description\") ? : null }\n\n { (bodyParam || !isExecute) && isDisplayParamEnum ?\n Available values : \" + paramEnum.map(function(item) {\n return item\n }).toArray().map(String).join(\", \")}/>\n : null\n }\n\n { (bodyParam || !isExecute) && paramDefaultValue !== undefined ?\n Default value : \" + paramDefaultValue}/>\n : null\n }\n\n { (bodyParam || !isExecute) && paramExample !== undefined ?\n Example : \" + paramExample}/>\n : null\n }\n\n {(isFormData && !isFormDataSupported) &&
Error: your browser does not support FormData
}\n\n {\n isOAS3 && param.get(\"examples\") ? (\n
\n \n
\n ) : null\n }\n\n { (isObject || isArrayOfObjects) ? (\n \n ) : jsonSchemaForm \n }\n\n {\n bodyParam && schema ? \n : null\n }\n\n {\n !bodyParam && isExecute && param.get(\"allowEmptyValue\") ?\n \n : null\n }\n\n {\n isOAS3 && param.get(\"examples\") ? (\n \n ) : null\n }\n\n { !showCommonExtensions || !commonExt.size ? null : commonExt.entrySeq().map(([key, v]) => )}\n { !showExtensions || !extensions.size ? null : extensions.entrySeq().map(([key, v]) => )}\n\n \n\n \n )\n\n }\n\n}\n","import React, { Component } from \"react\"\nimport PropTypes from \"prop-types\"\n\nexport default class Execute extends Component {\n\n static propTypes = {\n specSelectors: PropTypes.object.isRequired,\n specActions: PropTypes.object.isRequired,\n operation: PropTypes.object.isRequired,\n path: PropTypes.string.isRequired,\n method: PropTypes.string.isRequired,\n oas3Selectors: PropTypes.object.isRequired,\n oas3Actions: PropTypes.object.isRequired,\n onExecute: PropTypes.func,\n disabled: PropTypes.bool\n }\n\n handleValidateParameters = () => {\n let { specSelectors, specActions, path, method } = this.props\n specActions.validateParams([path, method])\n return specSelectors.validateBeforeExecute([path, method])\n }\n\n handleValidateRequestBody = () => {\n let { path, method, specSelectors, oas3Selectors, oas3Actions } = this.props\n let validationErrors = {\n missingBodyValue: false,\n missingRequiredKeys: []\n }\n // context: reset errors, then (re)validate\n oas3Actions.clearRequestBodyValidateError({ path, method })\n let oas3RequiredRequestBodyContentType = specSelectors.getOAS3RequiredRequestBodyContentType([path, method])\n let oas3RequestBodyValue = oas3Selectors.requestBodyValue(path, method)\n let oas3ValidateBeforeExecuteSuccess = oas3Selectors.validateBeforeExecute([path, method])\n let oas3RequestContentType = oas3Selectors.requestContentType(path, method)\n\n if (!oas3ValidateBeforeExecuteSuccess) {\n validationErrors.missingBodyValue = true\n oas3Actions.setRequestBodyValidateError({ path, method, validationErrors })\n return false\n }\n if (!oas3RequiredRequestBodyContentType) {\n return true\n }\n let missingRequiredKeys = oas3Selectors.validateShallowRequired({\n oas3RequiredRequestBodyContentType,\n oas3RequestContentType,\n oas3RequestBodyValue\n })\n if (!missingRequiredKeys || missingRequiredKeys.length < 1) {\n return true\n }\n missingRequiredKeys.forEach((missingKey) => {\n validationErrors.missingRequiredKeys.push(missingKey)\n })\n oas3Actions.setRequestBodyValidateError({ path, method, validationErrors })\n return false\n }\n\n handleValidationResultPass = () => {\n let { specActions, operation, path, method } = this.props\n if (this.props.onExecute) {\n // loading spinner\n this.props.onExecute()\n }\n specActions.execute({ operation, path, method })\n }\n\n handleValidationResultFail = () => {\n let { specActions, path, method } = this.props\n // deferred by 40ms, to give element class change time to settle.\n specActions.clearValidateParams([path, method])\n setTimeout(() => {\n specActions.validateParams([path, method])\n }, 40)\n }\n\n handleValidationResult = (isPass) => {\n if (isPass) {\n this.handleValidationResultPass()\n } else {\n this.handleValidationResultFail()\n }\n }\n\n onClick = () => {\n let paramsResult = this.handleValidateParameters()\n let requestBodyResult = this.handleValidateRequestBody()\n let isPass = paramsResult && requestBodyResult\n this.handleValidationResult(isPass)\n }\n\n onChangeProducesWrapper = ( val ) => this.props.specActions.changeProducesValue([this.props.path, this.props.method], val)\n\n render(){\n const { disabled } = this.props\n return (\n \n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport Im from \"immutable\"\n\nconst propClass = \"header-example\"\n\nexport default class Headers extends React.Component {\n static propTypes = {\n headers: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired\n }\n\n render() {\n let { headers, getComponent } = this.props\n\n const Property = getComponent(\"Property\")\n const Markdown = getComponent(\"Markdown\", true)\n\n if ( !headers || !headers.size )\n return null\n\n return (\n
\n

Headers:

\n \n \n \n \n \n \n \n \n \n {\n headers.entrySeq().map( ([ key, header ]) => {\n if(!Im.Map.isMap(header)) {\n return null\n }\n\n const description = header.get(\"description\")\n const type = header.getIn([\"schema\"]) ? header.getIn([\"schema\", \"type\"]) : header.getIn([\"type\"])\n const schemaExample = header.getIn([\"schema\", \"example\"])\n\n return (\n \n \n \n )\n }).toArray()\n }\n \n
NameDescriptionType
{ key }{\n !description ? null : \n }{ type } { schemaExample ? : null }
\n
\n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport { List } from \"immutable\"\n\nexport default class Errors extends React.Component {\n\n static propTypes = {\n editorActions: PropTypes.object,\n errSelectors: PropTypes.object.isRequired,\n layoutSelectors: PropTypes.object.isRequired,\n layoutActions: PropTypes.object.isRequired,\n getComponent: PropTypes.func.isRequired,\n }\n\n render() {\n let { editorActions, errSelectors, layoutSelectors, layoutActions, getComponent } = this.props\n\n const Collapse = getComponent(\"Collapse\")\n\n if(editorActions && editorActions.jumpToLine) {\n var jumpToLine = editorActions.jumpToLine\n }\n\n let errors = errSelectors.allErrors()\n\n // all thrown errors, plus error-level everything else\n let allErrorsToDisplay = errors.filter(err => err.get(\"type\") === \"thrown\" ? true :err.get(\"level\") === \"error\")\n\n if(!allErrorsToDisplay || allErrorsToDisplay.count() < 1) {\n return null\n }\n\n let isVisible = layoutSelectors.isShown([\"errorPane\"], true)\n let toggleVisibility = () => layoutActions.show([\"errorPane\"], !isVisible)\n\n let sortedJSErrors = allErrorsToDisplay.sortBy(err => err.get(\"line\"))\n\n return (\n
\n        
\n

Errors

\n \n
\n \n
\n { sortedJSErrors.map((err, i) => {\n let type = err.get(\"type\")\n if(type === \"thrown\" || type === \"auth\") {\n return \n }\n if(type === \"spec\") {\n return \n }\n }) }\n
\n
\n
\n )\n }\n}\n\nconst ThrownErrorItem = ( { error, jumpToLine } ) => {\n if(!error) {\n return null\n }\n let errorLine = error.get(\"line\")\n\n return (\n
\n { !error ? null :\n
\n

{ (error.get(\"source\") && error.get(\"level\")) ?\n toTitleCase(error.get(\"source\")) + \" \" + error.get(\"level\") : \"\" }\n { error.get(\"path\") ? at {error.get(\"path\")}: null }

\n \n { error.get(\"message\") }\n \n
\n { errorLine && jumpToLine ? Jump to line { errorLine } : null }\n
\n
\n }\n
\n )\n }\n\nconst SpecErrorItem = ( { error, jumpToLine = null } ) => {\n let locationMessage = null\n\n if(error.get(\"path\")) {\n if(List.isList(error.get(\"path\"))) {\n locationMessage = at { error.get(\"path\").join(\".\") }\n } else {\n locationMessage = at { error.get(\"path\") }\n }\n } else if(error.get(\"line\") && !jumpToLine) {\n locationMessage = on line { error.get(\"line\") }\n }\n\n return (\n
\n { !error ? null :\n
\n

{ toTitleCase(error.get(\"source\")) + \" \" + error.get(\"level\") } { locationMessage }

\n { error.get(\"message\") }\n
\n { jumpToLine ? (\n Jump to line { error.get(\"line\") }\n ) : null }\n
\n
\n }\n
\n )\n }\n\nfunction toTitleCase(str) {\n return (str || \"\")\n .split(\" \")\n .map(substr => substr[0].toUpperCase() + substr.slice(1))\n .join(\" \")\n}\n\nThrownErrorItem.propTypes = {\n error: PropTypes.object.isRequired,\n jumpToLine: PropTypes.func\n}\n\nSpecErrorItem.propTypes = {\n error: PropTypes.object.isRequired,\n jumpToLine: PropTypes.func\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport ImPropTypes from \"react-immutable-proptypes\"\nimport { fromJS } from \"immutable\"\n\nconst noop = ()=>{}\n\nexport default class ContentType extends React.Component {\n\n static propTypes = {\n ariaControls: PropTypes.string,\n contentTypes: PropTypes.oneOfType([ImPropTypes.list, ImPropTypes.set, ImPropTypes.seq]),\n controlId: PropTypes.string,\n value: PropTypes.string,\n onChange: PropTypes.func,\n className: PropTypes.string,\n ariaLabel: PropTypes.string\n }\n\n static defaultProps = {\n onChange: noop,\n value: null,\n contentTypes: fromJS([\"application/json\"]),\n }\n\n componentDidMount() {\n // Needed to populate the form, initially\n if(this.props.contentTypes) {\n this.props.onChange(this.props.contentTypes.first())\n }\n }\n\n UNSAFE_componentWillReceiveProps(nextProps) {\n if(!nextProps.contentTypes || !nextProps.contentTypes.size) {\n return\n }\n\n if(!nextProps.contentTypes.includes(nextProps.value)) {\n nextProps.onChange(nextProps.contentTypes.first())\n }\n }\n\n onChangeWrapper = e => this.props.onChange(e.target.value)\n\n render() {\n let { ariaControls, ariaLabel, className, contentTypes, controlId, value } = this.props\n\n if ( !contentTypes || !contentTypes.size )\n return null\n\n return (\n
\n \n
\n )\n }\n}\n","import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nfunction xclass(...args) {\n return args.filter(a => !!a).join(\" \").trim()\n}\n\nexport class Container extends React.Component {\n render() {\n let { fullscreen, full, ...rest } = this.props\n // Normal element\n\n if(fullscreen)\n return
\n\n let containerClass = \"swagger-container\" + (full ? \"-full\" : \"\")\n return (\n
\n )\n }\n}\n\nContainer.propTypes = {\n fullscreen: PropTypes.bool,\n full: PropTypes.bool,\n className: PropTypes.string\n}\n\nconst DEVICES = {\n \"mobile\": \"\",\n \"tablet\": \"-tablet\",\n \"desktop\": \"-desktop\",\n \"large\": \"-hd\"\n}\n\nexport class Col extends React.Component {\n\n render() {\n const {\n hide,\n keepContents,\n /* we don't want these in the `rest` object that passes to the final component,\n since React now complains. So we extract them */\n /* eslint-disable no-unused-vars */\n mobile,\n tablet,\n desktop,\n large,\n /* eslint-enable no-unused-vars */\n ...rest\n } = this.props\n\n if(hide && !keepContents)\n return \n\n let classesAr = []\n\n for (let device in DEVICES) {\n if (!Object.prototype.hasOwnProperty.call(DEVICES, device)) {\n continue\n }\n let deviceClass = DEVICES[device]\n if(device in this.props) {\n let val = this.props[device]\n\n if(val < 1) {\n classesAr.push(\"none\" + deviceClass)\n continue\n }\n\n classesAr.push(\"block\" + deviceClass)\n classesAr.push(\"col-\" + val + deviceClass)\n }\n }\n\n if (hide) {\n classesAr.push(\"hidden\")\n }\n\n let classes = xclass(rest.className, ...classesAr)\n\n return (\n
\n )\n }\n\n}\n\nCol.propTypes = {\n hide: PropTypes.bool,\n keepContents: PropTypes.bool,\n mobile: PropTypes.number,\n tablet: PropTypes.number,\n desktop: PropTypes.number,\n large: PropTypes.number,\n className: PropTypes.string\n}\n\nexport class Row extends React.Component {\n\n render() {\n return
\n }\n\n}\n\nRow.propTypes = {\n className: PropTypes.string\n}\n\nexport class Button extends React.Component {\n\n static propTypes = {\n className: PropTypes.string\n }\n\n static defaultProps = {\n className: \"\"\n }\n\n render() {\n return