@@ -1,2 +1,7 @@
/.idea
-volume
+.DS_Store
+.yarn
+go-server-server-generated
+node_modules
+volume
+yarn-error.log
@@ -3,11 +3,8 @@
<module name="sqs_demo" />
<working_directory value="$PROJECT_DIR$" />
<parameters value="-profile=sqs-tutorial -url=https://sqs.eu-west-3.amazonaws.com" />
- <envs>
- <env name="AWS_PROFILE" value="sqs-tutorial" />
- </envs>
<kind value="PACKAGE" />
- <package value="code.osinet.fr/fgm/sqs_demo/cmd/consumer/" />
+ <package value="code.osinet.fr/fgm/sqs_demo/back/cmd/consumer/" />
<directory value="$PROJECT_DIR$" />
<filePath value="$PROJECT_DIR$/demo.go" />
<method v="2" />
@@ -0,0 +1,13 @@
+<component name="ProjectRunConfigurationManager">
+ <configuration default="false" name="Message Store" type="GoTestRunConfiguration" factoryName="Go Test">
+ <module name="sqs_demo" />
+ <working_directory value="$PROJECT_DIR$/back/services/redriver" />
+ <root_directory value="$PROJECT_DIR$" />
+ <kind value="PACKAGE" />
+ <package value="code.osinet.fr/fgm/sqs_demo/back/services/redriver" />
+ <directory value="$PROJECT_DIR$" />
+ <filePath value="$PROJECT_DIR$" />
+ <framework value="gotest" />
+ <method v="2" />
+ </configuration>
+</component>
@@ -0,0 +1,12 @@
+ <configuration default="false" name="Producer" type="GoApplicationRunConfiguration" factoryName="Go Application">
+ <working_directory value="$PROJECT_DIR$" />
+ <parameters value="-profile=sqs-tutorial" />
+ <package value="code.osinet.fr/fgm/sqs_demo/back/cmd/producer/" />
+ <filePath value="$PROJECT_DIR$/demo.go" />
@@ -0,0 +1,22 @@
+ <configuration default="false" name="Redriver" type="GoApplicationRunConfiguration" factoryName="Go Application">
+ <parameters value="-profile=sqs-tutorial -addr :81 -wait 3 -prefix test" />
+ <EXTENSION ID="net.ashald.envfile">
+ <option name="IS_ENABLED" value="false" />
+ <option name="IS_SUBST" value="false" />
+ <option name="IS_PATH_MACRO_SUPPORTED" value="false" />
+ <option name="IS_IGNORE_MISSING_FILES" value="false" />
+ <option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
+ <ENTRIES>
+ <ENTRY IS_ENABLED="true" PARSER="runconfig" IS_EXECUTABLE="false" />
+ </ENTRIES>
+ </EXTENSION>
+ <package value="code.osinet.fr/fgm/sqs_demo/back/cmd/redriver" />
@@ -0,0 +1,3 @@
+lint:
+ cd back; go vet ./...
+ cd back; staticcheck ./...
@@ -0,0 +1,477 @@
+openapi: 3.0.3
+info:
+ title: payment-payin-work-redriver API
+ version: 1.0.0
+servers:
+ - url: /api/payment-payin-work-redriver/v1
+paths:
+ /dlq:
+ get:
+ summary: SPA
+ responses:
+ "200":
+ description: SPA main screen
+ "500":
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/SPAError'
+ /dlq/info:
+ summary: List visible queues
+ operationId: dlq_info
+ parameters: [ ]
+ description: Information about the visible queues
+ type: array
+ items:
+ $ref: '#/components/schemas/QueueInfo'
+ x-content-type: application/json
+ security:
+ - OAuth2:
+ - lbc.payment.payment.read
+ /dlq/ui/{asset}:
+ summary: UI assets
+ parameters:
+ - name: asset
+ in: path
+ description: the relative path of the asset
+ required: true
+ style: simple
+ explode: false
+ type: string
+ example: favicon.ico
+ description: Asset
+ /dlq/{name}/delete:
+ post:
+ summary: Delete the items
+ operationId: dlq_delete
+ - name: name
+ description: the name of the queue from which to delete messages
+ requestBody:
+ description: a list of message ReceiptHandle values
+ "205":
+ description: "Items deleted, reset view"
+ /dlq/{name}/redrive:
+ summary: Redrive the selected items
+ operationId: dlq_redrive
+ description: the name of the DLQ from which to redrive messages
+ description: "a list of message ID/ReceiptHandle pairs. ID is needed to republish,\
+ \ ReceiptHandle to remove from DLQ. Process starts by ChangingVisility to\
+ \ protect messages, then SendMessage on original source queue, and on success,\
+ \ DeleteMessage for the original messages."
+ $ref: '#/components/schemas/name_redrive_body'
+ /dlq/{name}/purge:
+ summary: purge the whole queue
+ operationId: dlq_purge
+ description: the name of the queue to purge
+ description: "Success, reset views"
+ /dlq/{name}/release:
+ summary: Release the items for reception by resetting their VisibilityTimeout
+ to 0
+ operationId: dlq_release
+ description: the name of the queue to poll
+ /dlq/{name}/items:
+ summary: Get the first items ready to be received in a queue
+ operationId: dlq_items
+ - name: limit
+ in: query
+ description: the maximum number of items to return
+ required: false
+ style: form
+ explode: true
+ maximum: 10
+ type: integer
+ default: 10
+ description: A list of messages
+ $ref: '#/components/schemas/Message'
+components:
+ schemas:
+ QueueURL:
+ description: The absolute URL to the queue
+ example: https://sqs.amazonaws.com/eu-west-3/12345678901234/some-queue
+ QueueARN:
+ description: the Amazon resource name (ARN) of the queue.
+ example: arn:sqs:eu-west-3:12345678901234:some_queue
+ Message:
+ type: object
+ properties:
+ attributes:
+ $ref: '#/components/schemas/Message_attributes'
+ body:
+ description: not url-encoded
+ md5_of_body:
+ description: An MD5 digest of the non-URL-encoded message body string.
+ md5_of_message_attributes:
+ description: "An MD5 digest of the non-URL-encoded message attribute string.\
+ \ You can use this attribute to verify that Amazon SQS received the message\
+ \ correctly. Amazon SQS URL-decodes the message before creating the MD5\
+ \ digest. For information about MD5, see RFC1321 (https://www.ietf.org/rfc/rfc1321.txt)."
+ message_id:
+ receipt_handle:
+ description: "An identifier associated with the act of receiving the message.\
+ \ A new receipt handle is returned every time you receive a message. When\
+ \ deleting a message, you provide the last received receipt handle to\
+ \ delete the message."
+ example:
+ md5_of_message_attributes: md5_of_message_attributes
+ receipt_handle: receipt_handle
+ sequence_number: 5
+ approximate_first_receive_timestamp: 0
+ approximate_receive_count: 1
+ sent_timestamp: 1
+ sender_id: ABCDE1F2GH3I4JK5LMNOP:i-a123b456
+ message_id: message_id
+ body: body
+ md5_of_body: md5_of_body
+ SPAError:
+ message:
+ description: Free string describing the error
+ message: an error message
+ QueueInfo:
+ name:
+ description: "The queue name, for both human and machine use"
+ url:
+ $ref: '#/components/schemas/QueueURL'
+ $ref: '#/components/schemas/QueueInfo_attributes'
+ name: name
+ visibility_timeout: 2
+ last_modified_timestamp: 2
+ approximate_number_of_messages_delayed: 6
+ created_timestamp: 5
+ delay_seconds: 5
+ redrive_policy:
+ maxReceiveCount: 4
+ deadLetterTargetArn: arn:sqs:eu-west-3:12345678901234:some_queue
+ redrive_allow_policy:
+ source_queue_arns:
+ - null
+ redrive_permission: ""
+ message_retention_period: 9
+ maximum_message_size: 7
+ queue_arn: arn:sqs:eu-west-3:12345678901234:some_queue
+ approximate_number_of_messages_not_visible: 1
+ approximate_number_of_messages: 0
+ receive_message_wait_time_seconds: 3
+ url: https://sqs.amazonaws.com/eu-west-3/12345678901234/some-queue
+ name_redrive_body:
+ id:
+ Message_attributes:
+ # AWSTraceHeader
+ approximate_first_receive_timestamp:
+ approximate_receive_count:
+ minimum: 1
+ # MessageDeduplicationId
+ # MessageGroupId
+ sender_id:
+ description: IAM user or role
+ example: ABCDE1F2GH3I4JK5LMNOP:i-a123b456
+ sent_timestamp:
+ sequence_number:
+ QueueInfo_attributes_redrive_policy:
+ deadLetterTargetArn:
+ description: The Amazon Resource Name (ARN) of the dead-letter queue to
+ which Amazon SQS moves messages after the value of maxReceiveCount is
+ exceeded.
+ maxReceiveCount:
+ description: "The number of times a message is delivered to the source queue\
+ \ before being moved to the dead-letter queue. Default 10. When the ReceiveCount\
+ \ for a message exceeds the maxReceiveCount for a queue, Amazon SQS moves\
+ \ the message to the dead-letter-queue."
+ description: the parameters for the dead-letter queue functionality of the source
+ queue
+ deadLetterTargetArn: arn:sqs:eu-west-3:12345678901234:some_queue_dlq
+ QueueInfo_attributes_redrive_allow_policy:
+ redrive_permission:
+ description: The permission type that defines which source queues can specify
+ the current queue as the dead-letter queue.
+ enum:
+ - allowAll
+ - denyAll
+ - byQueue
+ $ref: '#/components/schemas/QueueARN'
+ description: the permissions for the dead-letter queue redrive permission and
+ which source queues can specify dead-letter queues.
+ QueueInfo_attributes:
+ approximate_number_of_messages:
+ description: he approximate number of messages available for retrieval from
+ the queue.
+ approximate_number_of_messages_delayed:
+ description: the approximate number of messages in the queue that are delayed
+ and not available for reading immediately. This can happen when the queue
+ is configured as a delay queue or when a message has been sent with a
+ delay parameter.
+ approximate_number_of_messages_not_visible:
+ description: the approximate number of messages that are in flight. Messages
+ are considered to be in flight if they have been sent to a client but
+ have not yet been deleted or have not yet reached the end of their visibility
+ window.
+ created_timestamp:
+ description: the time when the queue was created in seconds (epoch time).
+ delay_seconds:
+ description: the default delay on the queue in seconds.
+ last_modified_timestamp:
+ description: the time when the queue was last changed in seconds (epoch
+ time).
+ maximum_message_size:
+ description: the limit of how many bytes a message can contain before Amazon
+ SQS rejects it.
+ message_retention_period:
+ description: "the length of time, in seconds, for which Amazon SQS retains\
+ \ a message. When you change a queue's attributes, the change can take\
+ \ up to 60 seconds for most of the attributes to propagate throughout\
+ \ the Amazon SQS system. Changes made to the MessageRetentionPeriod attribute\
+ \ can take up to 15 minutes and will impact existing messages in the queue\
+ \ potentially causing them to be expired and deleted if the MessageRetentionPeriod\
+ \ is reduced below the age of existing messages."
+ # policy:
+ queue_arn:
+ receive_message_wait_time_seconds:
+ description: "the length of time, in seconds, for which the ReceiveMessage\
+ \ action waits for a message to arrive."
+ visibility_timeout:
+ description: seconds a message remains hidden after being received. Max
+ 43200.
+ $ref: '#/components/schemas/QueueInfo_attributes_redrive_policy'
+ $ref: '#/components/schemas/QueueInfo_attributes_redrive_allow_policy'
+ # KmsMesterKeyID:
+ # KmsDataKeyReusePeriodSeconds:
+ # SqsManagerSseEnabled:
+ # FifoQueue:
+ # DeduplicationScope
+ # FifoThroughputLimit
+
+ securitySchemes:
+ OAuth2:
+ type: oauth2
+ flows:
+ authorizationCode:
+ authorizationUrl: https://api.leboncoin.fr/api/oauth/v1/authorize
+ tokenUrl: https://api.leboncoin.fr/api/oauth/v1/token
+ scopes:
+ user: Get user rights
@@ -8,7 +8,7 @@ import (
"github.com/fgm/izidic"
- "code.osinet.fr/fgm/sqs_demo/services"
+ "code.osinet.fr/fgm/sqs_demo/back/services"
)
func main() {
@@ -1,14 +1,16 @@
package main
import (
- "context"
"io"
- "log"
"os"
+ "github.com/gin-gonic/gin"
- services2 "code.osinet.fr/fgm/sqs_demo/back/services"
+ "code.osinet.fr/fgm/sqs_demo/back/web"
+ "code.osinet.fr/fgm/sqs_demo/back/services/redriver"
@@ -16,31 +18,32 @@ func main() {
}
func main2(w io.Writer, name string, args []string) (exitCode byte) {
- ctx := context.Background()
dic := Resolve(w, name, args)
- lister := dic.MustService(services2.SvcLister).(func(ctx context.Context) string)
- qURL := lister(ctx)
-
- producer := dic.MustService(services2.SvcProducer).(func(ctx context.Context, qName string))
- producer(ctx, qURL)
- log.Printf("exiting cleanly")
+ h := dic.MustService(services.SvcHttp).(*gin.Engine)
+ addr := dic.MustParam(services.PAddr).(string)
+ if err := h.Run(addr); err != nil {
+ return 1
+ }
return 0
func Resolve(w io.Writer, name string, args []string) *izidic.Container {
dic := izidic.New()
- dic.Store(services2.PName, name)
- dic.Store(services2.PArgs, args)
- dic.Store(services2.PWriter, w)
- dic.Register(services2.SvcClient, services2.SQSClientService)
- dic.Register(services2.SvcFlags, services2.FlagsService)
- dic.Register(services2.SvcLister, services2.ListerService)
- dic.Register(services2.SvcLogger, services2.LoggerService)
- dic.Register(services2.SvcProducer, services2.ProducerService)
- dic.MustService(services2.SvcFlags) // Store generated params before freeze.
+ dic.Store(services.PName, name)
+ dic.Store(services.PArgs, args)
+ dic.Store(services.PWriter, w)
+ dic.Register(services.SvcClient, services.SQSClientService)
+ dic.Register(services.SvcFlags, services.FlagsService)
+ dic.Register(services.SvcLister, services.ListerService)
+ dic.Register(services.SvcLogger, services.LoggerService)
+ dic.Register(services.SvcMessageStore, redriver.MessageStoreService)
+ dic.Register(services.SvcProducer, services.ProducerService)
+ dic.Register(services.SvcRedriver, redriver.RedriverService)
+ dic.Register(services.SvcHttp, web.HttpService)
+ dic.Register(services.SvcRenderer, web.RendererService)
+ dic.MustService(services.SvcFlags) // Store generated params before freeze.
dic.Freeze()
return dic
@@ -1,26 +0,0 @@
-module code.osinet.fr/fgm/sqs_demo
-go 1.19
-require (
- github.com/aws/aws-sdk-go-v2 v1.17.3
- github.com/aws/aws-sdk-go-v2/config v1.18.7
- github.com/aws/aws-sdk-go-v2/service/sqs v1.19.17
- github.com/davecgh/go-spew v1.1.0
- github.com/fgm/izidic v0.0.2
- github.com/google/uuid v1.3.0
- gopkg.in/yaml.v2 v2.4.0
-)
- github.com/aws/aws-sdk-go-v2/credentials v1.13.7 // indirect
- github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 // indirect
- github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 // indirect
- github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 // indirect
- github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 // indirect
- github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 // indirect
- github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 // indirect
- github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 // indirect
- github.com/aws/aws-sdk-go-v2/service/sts v1.17.7 // indirect
- github.com/aws/smithy-go v1.13.5 // indirect
@@ -1,43 +0,0 @@
-github.com/aws/aws-sdk-go-v2 v1.17.3 h1:shN7NlnVzvDUgPQ+1rLMSxY8OWRNDRYtiqe0p/PgrhY=
-github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
-github.com/aws/aws-sdk-go-v2/config v1.18.7 h1:V94lTcix6jouwmAsgQMAEBozVAGJMFhVj+6/++xfe3E=
-github.com/aws/aws-sdk-go-v2/config v1.18.7/go.mod h1:OZYsyHFL5PB9UpyS78NElgKs11qI/B5KJau2XOJDXHA=
-github.com/aws/aws-sdk-go-v2/credentials v1.13.7 h1:qUUcNS5Z1092XBFT66IJM7mYkMwgZ8fcC8YDIbEwXck=
-github.com/aws/aws-sdk-go-v2/credentials v1.13.7/go.mod h1:AdCcbZXHQCjJh6NaH3pFaw8LUeBFn5+88BZGMVGuBT8=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 h1:j9wi1kQ8b+e0FBVHxCqCGo4kxDU175hoDHcWAi0sauU=
-github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21/go.mod h1:ugwW57Z5Z48bpvUyZuaPy4Kv+vEfJWnIrky7RmkBvJg=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 h1:I3cakv2Uy1vNmmhRQmFptYDxOvBnwCdNwyw63N0RaRU=
-github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27/go.mod h1:a1/UpzeyBBerajpnP5nGZa9mGzsBn5cOKxm6NWQsvoI=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 h1:5NbbMrIzmUn/TXFqAle6mgrH5m9cOvMLRGL7pnG8tRE=
-github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21/go.mod h1:+Gxn8jYn5k9ebfHEqlhrMirFjSW0v0C9fI+KN5vk2kE=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 h1:KeTxcGdNnQudb46oOl4d90f2I33DF/c6q3RnZAmvQdQ=
-github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28/go.mod h1:yRZVr/iT0AqyHeep00SZ4YfBAKojXz08w3XMBscdi0c=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 h1:5C6XgTViSb0bunmU57b3CT+MhxULqHH2721FVA+/kDM=
-github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21/go.mod h1:lRToEJsn+DRA9lW4O9L9+/3hjTkUzlzyzHqn8MTds5k=
-github.com/aws/aws-sdk-go-v2/service/sqs v1.19.17 h1:bTr3F70BsgeJZW5QU0O4pVapJbgXuuiaaX9vQQfJAp8=
-github.com/aws/aws-sdk-go-v2/service/sqs v1.19.17/go.mod h1:jQhN5f4p3PALMNlUtfb/0wGIFlV7vGtJlPDVfxfNfPY=
-github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 h1:gItLq3zBYyRDPmqAClgzTH8PBjDQGeyptYGHIwtYYNA=
-github.com/aws/aws-sdk-go-v2/service/sso v1.11.28/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 h1:KCacyVSs/wlcPGx37hcbT3IGYO8P8Jx+TgSDhAXtQMY=
-github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11/go.mod h1:TZSH7xLO7+phDtViY/KUp9WGCJMQkLJ/VpgkTFd5gh8=
-github.com/aws/aws-sdk-go-v2/service/sts v1.17.7 h1:9Mtq1KM6nD8/+HStvWcvYnixJ5N85DX+P+OY3kI3W2k=
-github.com/aws/aws-sdk-go-v2/service/sts v1.17.7/go.mod h1:+lGbb3+1ugwKrNTWcf2RT05Xmp543B06zDFTwiTLp7I=
-github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
-github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/fgm/izidic v0.0.2 h1:xpIr9sEVE2xVMlUPu7zcQhumxPB0mjW2/j8Cm598sMw=
-github.com/fgm/izidic v0.0.2/go.mod h1:HSUQlWnf88mpvaovrffBQnjqug3WLjTisLZl5g2RaEc=
-github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
-github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
-github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@@ -135,7 +135,7 @@ func receiveMessage(ctx context.Context, w io.Writer, client *sqs.Client, qURL s
msg, err := client.ReceiveMessage(ctx, &rmi)
if err != nil {
- log.Fatalf("failed receiving from queue %s: %v", err)
+ log.Fatalf("failed receiving from queue %s: %v", qURL, err)
spew.Fdump(w, msg.Messages)
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/sqs"
- "gopkg.in/yaml.v2"
+ "gopkg.in/yaml.v3"
func ListerService(dic *izidic.Container) (any, error) {
@@ -0,0 +1,146 @@
+package redriver
+import (
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "net/url"
+ "strconv"
+ "strings"
+ "github.com/aws/aws-sdk-go-v2/aws"
+ "github.com/aws/aws-sdk-go-v2/aws/arn"
+ "github.com/aws/aws-sdk-go-v2/service/sqs/types"
+)
+func ARNFromURL(qURL string) (arn.ARN, error) {
+ a := arn.ARN{}
+ u, err := url.Parse(qURL)
+ if err != nil {
+ return a, fmt.Errorf("queue URL %q is not a valid URL: %w", qURL, err)
+ path := u.EscapedPath()
+ pathParts := strings.Split(strings.Trim(path, "/"), "/")
+ if len(pathParts) != 2 {
+ return a, fmt.Errorf("queue path %q does not have exactly 2 parts", path)
+ hostParts := strings.Split(u.Host, ".")
+ if len(hostParts) != 4 { // <service>.<region>.amazonaws.com
+ return a, fmt.Errorf("queue host %q does not have exactly 4 parts", u.Host)
+ a = arn.ARN{
+ Partition: "aws",
+ Service: hostParts[0],
+ Region: hostParts[1],
+ AccountID: pathParts[0],
+ Resource: pathParts[1],
+ return a, nil
+}
+func NameFromURL(qURL string) (name string, err error) {
+ return "", fmt.Errorf("queue URL %q is not a valid URL: %w", qURL, err)
+ parts := strings.Split(strings.Trim(path, "/"), "/")
+ if len(parts) != 2 {
+ return "", fmt.Errorf("queue path %q does not have exactly 2 parts", path)
+ return parts[1], nil
+func NameFromARN(qARN arn.ARN) string {
+ return qARN.Resource
+func NameFromARNString(qARN string) (name string, err error) {
+ a, err := arn.Parse(qARN)
+ return "", fmt.Errorf("queue \"ARN\" %q is not an valid ARN", qARN)
+ return NameFromARN(a), nil
+func URLFromARNString(qARN string) (name string, err error) {
+ return URLFromARN(a)
+func URLFromARN(qARN arn.ARN) (name string, err error) {
+ path, err := url.JoinPath("/", qARN.AccountID, qARN.Resource)
+ return "", fmt.Errorf("incorrect queue ARN: %w", err)
+ u := url.URL{
+ Scheme: "https",
+ Host: fmt.Sprintf("%s.%s.%s", qARN.Service, qARN.Region, "amazonaws.com"),
+ Path: path,
+ return u.String(), nil
+// MessageAttributeValuesFromJSONable attempts to convert a plain JSON to Message.MessageAttributes field value.
+//
+// FIXME we should not be storing those maps in JSONable form anyway, because that loses information.
+func MessageAttributeValuesFromJSONable(in map[string]any) (map[string]types.MessageAttributeValue, error) {
+ m := make(map[string]types.MessageAttributeValue, len(in))
+ for k, v := range in {
+ mav := types.MessageAttributeValue{StringValue: aws.String(fmt.Sprint(v))}
+ switch v.(type) {
+ case int:
+ mav.DataType = aws.String("Number")
+ case string:
+ mav.DataType = aws.String("String")
+ default:
+ return nil, fmt.Errorf("message attributes contain a non-convertible value: %T", v)
+ m[k] = mav
+ return m, nil
+func JSONableFromMessageAttributeValues(m map[string]types.MessageAttributeValue) (map[string]any, error) {
+ j := make(map[string]any)
+ for name, attr := range m {
+ if attr.DataType == nil {
+ return nil, errors.New("empty DataType on message attribute value")
+ switch t := *attr.DataType; t {
+ case "String":
+ if attr.StringValue == nil {
+ return nil, fmt.Errorf("nil string value on string field %q", name)
+ j[name] = *attr.StringValue
+ case "Number":
+ return nil, fmt.Errorf("nil string value on number field %q", name)
+ n, err := strconv.Atoi(*attr.StringValue)
+ return nil, fmt.Errorf("invalid numeric string on number field %q", name)
+ j[name] = n
+ case "Binary":
+ if attr.BinaryValue == nil {
+ j[name] = nil
+ continue
+ in := attr.BinaryValue
+ out := make([]byte, base64.StdEncoding.DecodedLen(len(in)))
+ _, err := base64.StdEncoding.Decode(out, in)
+ return nil, fmt.Errorf("failed decoding binary field %q: %w", name, err)
+ j[name] = out
+ return nil, fmt.Errorf("unimplemented DataType %q", t)
+ return j, nil
@@ -0,0 +1,123 @@
+ "log"
+ "sync"
+ "time"
+ "github.com/fgm/izidic"
+// Ensure messageStore implements interface MessageStore.
+var _ MessageStore = &messageStore{}
+// MessageStore is a Message local cache with TTL.
+type MessageStore interface {
+ Set(m Message) error
+ Get(receiptHandle string) (Message, bool)
+ Clear(receiptHandle string)
+ Flush()
+ Close()
+type messageStoreEntry struct {
+ value Message
+ stored time.Time
+type messageStore struct {
+ ttl time.Duration
+ sync.Mutex
+ sync.Map
+ done chan struct{}
+func (ms *messageStore) Close() {
+ ms.done <- struct{}{}
+func (ms *messageStore) Set(m Message) error {
+ if m.ReceiptHandle == "" {
+ return errors.New("cannot store a message with a nil ReceiptHandle")
+ mse := messageStoreEntry{
+ value: m,
+ stored: time.Now(),
+ ms.Store(m.ReceiptHandle, mse)
+ return nil
+func (ms *messageStore) Get(receiptHandle string) (Message, bool) {
+ v, ok := ms.Load(receiptHandle)
+ if !ok {
+ return Message{}, false
+ m, ok := v.(messageStoreEntry)
+ return m.value, true
+func (ms *messageStore) Clear(receiptHandle string) {
+ ms.Map.Delete(receiptHandle)
+func (ms *messageStore) Flush() {
+ ms.Lock()
+ ms.Map = sync.Map{}
+ ms.Unlock()
+func (ms *messageStore) expire() {
+ start := time.Now()
+ ms.Range(func(k, v any) bool {
+ log.Printf("expiring")
+ rh, ok := k.(string)
+ log.Printf("map contains a non-string key: %T", k)
+ mse, ok := v.(messageStoreEntry)
+ log.Printf("map contains a non-Messsage entry: %T", v)
+ return false
+ if start.Sub(mse.stored) > ms.ttl {
+ log.Printf("clearing message %s", rh)
+ ms.Clear(rh)
+ return true
+ })
+func (ms *messageStore) scheduleExpiries() {
+ t := time.NewTicker(ms.ttl / 2)
+ live := true
+ for live {
+ select {
+ case <-ms.done:
+ live = false
+ case <-t.C:
+ ms.expire()
+func newMessageStore(ttl time.Duration) *messageStore {
+ ms := messageStore{
+ ttl: ttl,
+ Map: sync.Map{},
+ done: make(chan struct{}),
+ go ms.scheduleExpiries()
+ return &ms
+func MessageStoreService(dic *izidic.Container) (any, error) {
+ ttl := dic.MustParam(services.PTTL).(time.Duration)
+ return newMessageStore(ttl), nil
@@ -0,0 +1,111 @@
+ "testing"
+func TestMessageStore_TTL(t *testing.T) {
+ const (
+ rh = "foo"
+ ttl = 1 * time.Millisecond
+ )
+ m1 := Message{ReceiptHandle: rh}
+ actual := newMessageStore(ttl)
+ _, ok := actual.Get(rh)
+ if ok {
+ t.Errorf("message found, but not yet stored")
+ actual.Set(m1)
+ m2, ok := actual.Get(rh)
+ t.Errorf("message not found, but expected to find it")
+ if m2.ReceiptHandle != rh {
+ t.Errorf("message RH is %q but key is %q", m2.ReceiptHandle, rh)
+ time.Sleep(3 * ttl)
+ _, ok = actual.Get(rh)
+ t.Errorf("message found, but expected it to be expired")
+func TestMessageStore_Flush(t *testing.T) {
+ len := func(ms *messageStore) int {
+ count := 0
+ ms.Range(func(_, _ any) bool {
+ count++
+ return count
+ const limit = 10
+ ms := newMessageStore(1 * time.Minute)
+ defer ms.Close()
+ for i := 0; i < limit; i++ {
+ ms.Set(Message{ReceiptHandle: strconv.Itoa(i)})
+ if actualLen := len(ms); actualLen != limit {
+ t.Fatalf("expected %d items, got %d", len(ms), actualLen)
+ ms.Flush()
+ if actualLen := len(ms); actualLen != 0 {
+ t.Fatalf("expected no items, got %d", actualLen)
+func TestMessageStore_Set(t *testing.T) {
+ for _, test := range [...]struct {
+ name string
+ input string
+ expectErr bool
+ }{
+ {"valid RH", "foo", false},
+ {"empty RH", "", true},
+ } {
+ t.Run(test.name, func(t *testing.T) {
+ ms := newMessageStore(time.Minute)
+ actual := ms.Set(Message{ReceiptHandle: test.input})
+ if actual != nil != test.expectErr {
+ t.Errorf("expected error %t, but got error %t", test.expectErr, actual)
+func TestMessageStore_Get_sad(t *testing.T) {
+ ms.Map.Store("foo", "sad")
+ _, ok := ms.Get("foo")
+ t.Fatalf("expected failure, but got success")
+func TestMessageStore_expire(t *testing.T) {
+ const ttl = 1 * time.Millisecond
+ ms := newMessageStore(ttl)
+ w := &strings.Builder{}
+ log.SetOutput(w)
+ ms.Map.Store(13, Message{ReceiptHandle: "13"})
+ ms.Map.Store("13", "vendredi")
+ ms.Close()
+ s := w.String()
+ if !strings.Contains(s, "map contains a non-string key") {
+ t.Errorf("expected expiry to have logged a key warning, but found none")
+ if !strings.Contains(s, "map contains a non-Messsage entry") {
+ t.Errorf("expected expiry to have logged a data warning, but found none")
@@ -0,0 +1,560 @@
+ "context"
+ "encoding/json"
+ "io"
+ "github.com/aws/aws-sdk-go-v2/service/sqs"
+const (
+ // BatchMax defines the maximum number of message usable in a batch operation, including redriving.
+ BatchMax = 10
+ // MessageSystemAttributeNameDeadLetterQueueSourceArn is an undocumented
+ // types.Message attribute, used by the SQS console to support redrive.
+ MessageSystemAttributeNameDeadLetterQueueSourceArn types.MessageSystemAttributeName = "DeadLetterQueueSourceArn"
+var (
+ ErrBatchTooBig = fmt.Errorf("operation requested on more than %d items", BatchMax)
+type ItemsKeys struct {
+ MessageID string
+ ReceiptHandle string
+type QueueRedrivePolicies struct {
+ *QueueInfoAttributesRedrivePolicy
+ *QueueInfoAttributesRedriveAllowPolicy
+// Redriver is a redrive-oriented facade in front of the sqs.Client API.
+type Redriver interface {
+ ListQueues(ctx context.Context, prefix string) (QueueUrls []string, err error)
+ GetRedrivePolicies(ctx context.Context, qName string) (*QueueRedrivePolicies, error)
+ GetQueueInfo(ctx context.Context, qName string) (*QueueInfo, error)
+ GetQueueItems(ctx context.Context, qName string) ([]Message, error)
+ DeleteItems(ctx context.Context, qName string, itemsIDs []ItemsKeys) error
+ Purge(ctx context.Context, qName string) error
+ RedriveItems(ctx context.Context, qName string, messages []Message) error
+type redriver struct {
+ VTO int32
+ Wait int32
+ io.Writer
+ *sqs.Client
+func (r *redriver) ListQueues(ctx context.Context, prefix string) (QueueUrls []string, err error) {
+ // TODO implement pagination for the day we need more than 1000 queues to be reported.
+ lqi := &sqs.ListQueuesInput{
+ MaxResults: aws.Int32(1000),
+ NextToken: nil,
+ QueueNamePrefix: aws.String(prefix),
+ lqo, err := r.Client.ListQueues(ctx, lqi)
+ return nil, fmt.Errorf("listing queues: %w", err)
+ return lqo.QueueUrls, nil
+func (*redriver) parseQueueInfoRedrivePolicies(qName string, qao sqs.GetQueueAttributesOutput) (*QueueRedrivePolicies, error) {
+ var qrp QueueRedrivePolicies
+ srp := qao.Attributes[string(types.QueueAttributeNameRedrivePolicy)]
+ if srp != "" {
+ rp := QueueInfoAttributesRedrivePolicy{}
+ if err := json.Unmarshal([]byte(srp), &rp); err != nil {
+ return nil, fmt.Errorf(
+ "failed parsing redrive policy for queue %q %w", qName, err)
+ if _, err := URLFromARNString(rp.DeadLetterTargetARN); err != nil {
+ "failed converting queue %q ARN to URL: %w", qName, err)
+ qrp.QueueInfoAttributesRedrivePolicy = &rp
+ srap := qao.Attributes[string(types.QueueAttributeNameRedriveAllowPolicy)]
+ if srap != "" {
+ rap := QueueInfoAttributesRedriveAllowPolicy{}
+ if err := json.Unmarshal([]byte(srap), &rap); err != nil {
+ "failed parsing redrive allow policy for queue %q %w", qName, err)
+ for _, src := range rap.SourceQueueARNs {
+ if _, err := URLFromARNString(src); err != nil {
+ qrp.QueueInfoAttributesRedriveAllowPolicy = &rap
+ return &qrp, nil
+func (r *redriver) GetRedrivePolicies(ctx context.Context, qName string) (policies *QueueRedrivePolicies, err error) {
+ qui := &sqs.GetQueueUrlInput{QueueName: &qName}
+ qu, err := r.GetQueueUrl(ctx, qui)
+ return nil, fmt.Errorf("failed getting URL for queue %q: %w", qName, err)
+ qai := &sqs.GetQueueAttributesInput{
+ QueueUrl: qu.QueueUrl,
+ AttributeNames: []types.QueueAttributeName{
+ types.QueueAttributeNameRedrivePolicy,
+ types.QueueAttributeNameRedriveAllowPolicy,
+ },
+ qao, err := r.Client.GetQueueAttributes(ctx, qai)
+ return nil, fmt.Errorf("failed getting DLQ policies for queue %q: %w", qName, err)
+ if qao == nil {
+ return nil, fmt.Errorf("redrive policy info for queue %q is empty", qName)
+ qrp, err := r.parseQueueInfoRedrivePolicies(qName, *qao)
+ return nil, fmt.Errorf("failed parsing redrive policy for queue %q: %w", qName, err)
+ if qrp == nil {
+ return nil, nil // Queue has no DLQ: this is not an error
+ return qrp, nil
+func (r *redriver) GetQueueInfo(ctx context.Context, qName string) (*QueueInfo, error) {
+ if qu.QueueUrl == nil {
+ return nil, fmt.Errorf("URL for queue %q is empty", qName)
+ AttributeNames: []types.QueueAttributeName{types.QueueAttributeNameAll},
+ return nil, fmt.Errorf("failed getting all attributes for queue %q: %w", qName, err)
+ return nil, fmt.Errorf("no attributes returned for queue %q", qName)
+ qi, err := r.parseQueueInfoAttributes(qName, qu, qao)
+ return nil, err
+ return qi, err
+func (r *redriver) parseQueueInfoAttributes(qName string, qu *sqs.GetQueueUrlOutput, qao *sqs.GetQueueAttributesOutput) (*QueueInfo, error) {
+ var (
+ qi *QueueInfo
+ errCount int
+ anom, anomd, anomnv int64
+ created, changed int64 // timestamps
+ delay, max, retention, vto, wait int64
+ u := *qu.QueueUrl
+ a, err := ARNFromURL(u)
+ return nil, fmt.Errorf("ARN for queue %q cannot be parsed from URL %q: %w", qName, u, err)
+ for _, field := range []struct {
+ name types.QueueAttributeName
+ value *int64
+ sDef string
+ def int64
+ {types.QueueAttributeNameApproximateNumberOfMessages, &anom, "-1", -1},
+ {types.QueueAttributeNameApproximateNumberOfMessagesDelayed, &anomd, "-1", -1},
+ {types.QueueAttributeNameApproximateNumberOfMessagesNotVisible, &anomnv, "-1", -1},
+ {types.QueueAttributeNameCreatedTimestamp, &created, "0", 0},
+ {types.QueueAttributeNameDelaySeconds, &delay, "-1", -1},
+ {types.QueueAttributeNameLastModifiedTimestamp, &changed, "0", 0},
+ {types.QueueAttributeNameMaximumMessageSize, &max, "262144", 1 << (8 + 10)}, // 256ko
+ {types.QueueAttributeNameMessageRetentionPeriod, &max, "1209600", 14 * 24 * 60 * 60}, // 2 weeks
+ {types.QueueAttributeNameReceiveMessageWaitTimeSeconds, &wait, "0", 0}, // short polling
+ {types.QueueAttributeNameVisibilityTimeout, &vto, "0", 0}, // short polling
+ s, ok := qao.Attributes[string(field.name)]
+ errCount++
+ s = field.sDef
+ n, err := strconv.Atoi(s)
+ *field.value = int64(n)
+ qi = &QueueInfo{
+ Name: qName,
+ URL: u,
+ Attributes: &QueueInfoAttributes{
+ ApproximateNumberOfMessages: anom,
+ ApproximateNumberOfMessagesDelayed: anomd,
+ ApproximateNumberOfMessagesNotVisible: anomnv,
+ CreatedTimestamp: created,
+ DelaySeconds: delay,
+ LastModifiedTimestamp: changed,
+ MaximumMessageSize: max,
+ MessageRetentionPeriod: retention,
+ QueueARN: a.String(),
+ ReceiveMessageWaitTimeSeconds: wait,
+ VisibilityTimeout: vto,
+ RedrivePolicy: qrp.QueueInfoAttributesRedrivePolicy,
+ RedriveAllowPolicy: qrp.QueueInfoAttributesRedriveAllowPolicy,
+ return qi, nil
+func (*redriver) parseReceivedAttributes(msg types.Message) (*SystemMessageAttributes, error) {
+ afrt, arc, sent int64
+ err error
+ name types.MessageSystemAttributeName
+ {types.MessageSystemAttributeNameApproximateFirstReceiveTimestamp, &afrt, "0", 0},
+ {types.MessageSystemAttributeNameApproximateReceiveCount, &arc, "0", 0},
+ {types.MessageSystemAttributeNameSentTimestamp, &sent, "0", 0},
+ s, ok := msg.Attributes[string(field.name)]
+ ma := SystemMessageAttributes{
+ ApproximateFirstReceiveTimestamp: afrt,
+ ApproximateReceiveCount: arc,
+ DeadLetterQueueSourceARN: msg.Attributes[string(MessageSystemAttributeNameDeadLetterQueueSourceArn)],
+ SenderId: msg.Attributes[string(types.MessageSystemAttributeNameSenderId)],
+ SentTimestamp: sent,
+ if errCount > 0 {
+ err = fmt.Errorf("encounted %d errors parsing system message attributes", errCount)
+ return &ma, nil
+func (r *redriver) GetQueueItems(ctx context.Context, qName string) ([]Message, error) {
+ rmi := sqs.ReceiveMessageInput{
+ MaxNumberOfMessages: 10,
+ MessageAttributeNames: []string{".*"},
+ VisibilityTimeout: 0,
+ WaitTimeSeconds: r.Wait,
+ t0 := time.Now()
+ rmo, err := r.Client.ReceiveMessage(ctx, &rmi)
+ d := time.Since(t0)
+ return nil, fmt.Errorf("after %v, failed receiving messages for queue %q: %w",
+ d, qName, err)
+ ms := make([]Message, 0, len(rmo.Messages))
+ for _, m := range rmo.Messages {
+ ma, err := r.parseReceivedAttributes(m)
+ value any
+ {"body", m.Body},
+ {"md5 of body", m.MD5OfBody},
+ {"md5 of message attributes", m.MD5OfMessageAttributes},
+ {"message ID", m.MessageId},
+ {"receipt handle", m.ReceiptHandle},
+ if field.value == nil {
+ return nil, fmt.Errorf("missing field %s on message", field.name)
+ j, err := JSONableFromMessageAttributeValues(m.MessageAttributes)
+ return nil, fmt.Errorf("failed decoding message boby: %w", err)
+ m2 := Message{
+ Attributes: ma,
+ MessageAttributes: j,
+ for _, pair := range []struct{ dest, src *string }{
+ {&m2.Body, m.Body},
+ {&m2.Md5OfBody, m.MD5OfBody},
+ {&m2.Md5OfMessageAttributes, m.MD5OfMessageAttributes},
+ {&m2.MessageId, m.MessageId},
+ {&m2.ReceiptHandle, m.ReceiptHandle},
+ if pair.src != nil {
+ *pair.dest = *pair.src
+ ms = append(ms, m2)
+ return ms, err
+func (r *redriver) DeleteItems(ctx context.Context, qName string, keys []ItemsKeys) error {
+ return fmt.Errorf("failed getting URL for queue %q: %w", qName, err)
+ entries := make([]types.DeleteMessageBatchRequestEntry, len(keys))
+ for i := 0; i < len(keys); i++ {
+ entries[i] = types.DeleteMessageBatchRequestEntry{
+ Id: aws.String(keys[i].MessageID),
+ ReceiptHandle: aws.String(keys[i].ReceiptHandle),
+ dmi := sqs.DeleteMessageBatchInput{
+ Entries: entries,
+ dmo, err := r.DeleteMessageBatch(ctx, &dmi)
+ return fmt.Errorf("failed deleting %d items from queue %q: %v",
+ len(keys), qName, err)
+ if len(dmo.Failed) > 0 {
+ errs := make([]string, len(dmo.Failed))
+ for i, bree := range dmo.Failed {
+ source := "aws"
+ if bree.SenderFault {
+ source = "redriver"
+ errs[i] = fmt.Sprintf("ID: %s / Failure: %s = %q / Source: %s",
+ *bree.Id, *bree.Message, *bree.Code, source)
+ return fmt.Errorf("failed deleting %d items out of %d from queue %q: %s",
+ len(dmo.Failed), len(keys), qName, strings.Join(errs, "\n"))
+ log.Println(dmo)
+func (r *redriver) Purge(ctx context.Context, qName string) error {
+ pqi := sqs.PurgeQueueInput{QueueUrl: qu.QueueUrl}
+ _, err = r.PurgeQueue(ctx, &pqi)
+ return fmt.Errorf("failed purging queue %q: %w",
+ qName, err)
+// RedriveItems redrives the selected message back to their respective source queue,
+// removing them from the DLQ once they have been sent.
+// Since a queue can act as a DLQ for more than one source queue, the messages
+// sends are grouped by source queue.
+func (r *redriver) RedriveItems(ctx context.Context, dlqName string, messages []Message) error {
+ sqURLs := make(map[string][]Message, 1) // In most cases, only a single queue will be used.
+ for _, message := range messages {
+ sARN := message.Attributes.DeadLetterQueueSourceARN
+ sURL, err := URLFromARNString(sARN)
+ return fmt.Errorf("failed resolving source ARN %q to URL: %v", sARN, err)
+ sqURLs[sURL] = append(sqURLs[sURL], message)
+ for qURL, messages := range sqURLs {
+ if err := r.redriveQueueMessages(ctx, dlqName, qURL, messages); err != nil {
+ return err
+// redriveQueueMessages handles message redriving for messages in a single queue.
+func (r *redriver) redriveQueueMessages(ctx context.Context, dlqName string, qURL string, messages []Message) error {
+ if len(messages) > BatchMax {
+ return ErrBatchTooBig
+ qui := &sqs.GetQueueUrlInput{QueueName: &dlqName}
+ dlqURL, err := r.GetQueueUrl(ctx, qui)
+ if err != nil || dlqURL == nil {
+ return fmt.Errorf("failed getting URL for queue %q: %w", dlqName, err)
+ // Hide messages to prevent other consumers from seeing them and generating duplicates.
+ fatal, nontafal := r.hideQueueMessages(ctx, "", *dlqURL.QueueUrl, messages)
+ if fatal != nil {
+ return fmt.Errorf("failed hiding messages during redrive towards queue %q: %w", qURL, fatal)
+ if nontafal != nil {
+ log.Printf("Redrive nonfatal error hiding messages on queue %q: %v", dlqName, nontafal)
+ // Send the messages back to their source queue.
+ if err := r.resendQueueMessages(ctx, qURL, messages); err != nil {
+ return fmt.Errorf("failed sending messages back to queue %q: %w", qURL, err)
+ // Delete them from the DLQ.
+ keys := make([]ItemsKeys, len(messages))
+ for i, m := range messages {
+ keys[i] = m.Keys()
+ if err := r.DeleteItems(ctx, dlqName, keys); err != nil {
+ return fmt.Errorf("failed deleting messages already redriven from DLQ %s to queue %q: beware of duplicates: %w",
+ dlqName, qURL, err)
+func (r *redriver) resendQueueMessages(ctx context.Context, qURL string, messages []Message) error {
+ smbre := make([]types.SendMessageBatchRequestEntry, len(messages))
+ m.MessageAttributes["previous-message-id"] = m.MessageId
+ mav, err := MessageAttributeValuesFromJSONable(m.MessageAttributes)
+ return fmt.Errorf("failed converting message attributes for message %s on queue %q: %v",
+ m.MessageId, qURL, err)
+ smbre[i] = types.SendMessageBatchRequestEntry{
+ Id: aws.String(strconv.Itoa(i)),
+ MessageBody: &m.Body,
+ MessageAttributes: mav,
+ smbi := sqs.SendMessageBatchInput{
+ Entries: smbre,
+ QueueUrl: &qURL,
+ smbo, err := r.SendMessageBatch(ctx, &smbi)
+ return fmt.Errorf("failed sending messages to queue %q: %v",
+ qURL, err)
+ if len(smbo.Failed) == 0 {
+ errs := make([]error, len(smbo.Failed))
+ for _, err := range smbo.Failed {
+ msg := fmt.Sprintf("ID: %s, Code: %s, Message: %s", *err.Id, *err.Code, *err.Message)
+ if err.SenderFault {
+ msg += " (sender fault)"
+ errs = append(errs, errors.New(msg))
+ return fmt.Errorf("partial redrive: failed re-sending %d/%d messages, %v",
+ len(smbo.Failed), len(smbi.Entries), errs)
+func (r *redriver) hideQueueMessages(ctx context.Context, dlqName string, qURL string, messages []Message) (fatal, nonfatal error) {
+ cmvbre := make([]types.ChangeMessageVisibilityBatchRequestEntry, len(messages))
+ cmvbre[i] = types.ChangeMessageVisibilityBatchRequestEntry{
+ ReceiptHandle: aws.String(m.ReceiptHandle),
+ VisibilityTimeout: r.VTO,
+ cmvbi := sqs.ChangeMessageVisibilityBatchInput{
+ Entries: cmvbre,
+ QueueUrl: aws.String(qURL),
+ cmvbo, err := r.ChangeMessageVisibilityBatch(ctx, &cmvbi)
+ return fmt.Errorf("failed hiding request on DLQ %q: %w", dlqName, err), nil
+ switch len(cmvbo.Failed) {
+ case len(cmvbi.Entries):
+ // No message made it: abort.
+ return fmt.Errorf("failed hiding all %d messages on DLQ %q", len(cmvbi.Entries), dlqName), nil
+ case 0:
+ return nil, nil // All well
+ errs := make([]error, len(cmvbo.Failed))
+ for _, err := range cmvbo.Failed {
+ // Some message made it, crossing fingers.
+ return nil, fmt.Errorf("failed hiding %d/%d messages, %v",
+ len(cmvbo.Failed), len(cmvbi.Entries), errs)
+func RedriverService(dic *izidic.Container) (any, error) {
+ cli := dic.MustService(services.SvcClient).(*sqs.Client)
+ w := dic.MustParam(services.PWriter).(io.Writer)
+ vto := dic.MustParam(services.PVTO).(time.Duration)
+ wait := int32(dic.MustParam(services.PWait).(int))
+ return &redriver{
+ Client: cli,
+ VTO: int32(vto.Seconds()),
+ Wait: wait,
+ Writer: w,
+ }, nil
@@ -1,28 +1,24 @@
-package services
+//go:build futuresqs
-import (
- "io"
+// The redriver_future.go file contains types and code related to the yet-unpublished
+// DLQ redrive APIs.
+// See https://github.com/aws/aws-sdk-go-v2/issues/1991 for details.
- "github.com/aws/aws-sdk-go-v2/service/sqs"
- "github.com/fgm/izidic"
+// Redriver defines the behavior of DLQ redriverV2 services.
+type RedriverV2 interface {
+ // CreateMoveTask()
-type CreateMoveTaskOutput struct {
- Status *string // Running
- SourceARN *string
- ApproximateNumberOfMessagesMoved int
-}
+ // ListMoveTasks() []any
-type Redriver interface {
- CreateMoveTask()
- ListMoveTasks() []any
+ ListDLQs() map[string]string
-type redriver struct {
+type redriverV2 struct {
-func (r redriver) CreateMoveTask() {
+func (r redriverV2) CreateMoveTask() {
// TODO implement me
// u := url.URL{
// Scheme: "https",
@@ -44,15 +40,13 @@ func (r redriver) CreateMoveTask() {
*/
-func (r redriver) ListMoveTasks() []any {
+func (r redriverV2) ListMoveTasks() []any {
panic("implement me")
-func RedriverService(dic *izidic.Container) (any, error) {
- cli := dic.MustService(SvcClient).(*sqs.Client)
- w := dic.MustParam(PWriter).(io.Writer)
- return func(ctx context.Context, qName string) {
- senderHandler(ctx, w, cli, qName)
- }, nil
+type CreateMoveTaskOutput struct {
+ Status *string // Running
+ SourceARN *string
+ ApproximateNumberOfMessagesMoved int
@@ -5,44 +5,76 @@ import (
"fmt"
"log"
const (
+ // Flags
+ PAddr = "addr"
+ PPrefix = "prefix"
+ PProfile = "profile"
+ PQName = "queue-name"
+ PRegion = "region"
+ PCSRFSecret = "csrf-secret"
+ PStoreSecret = "store-secret"
+ PTTL = "ttl"
+ PURL = "url"
+ PVTO = "vto"
+ PWait = "wait"
+ // Non-flags
PArgs = "args"
PHandler = "handler"
PName = "name"
- PProfile = "profile"
- PQName = "queue-name"
- PRegion = "region"
- PURL = "url"
PWriter = "writer"
- SvcClient = "sqs"
- SvcConsumer = "consumeMessage"
- SvcFlags = "flags"
- SvcLister = "lister"
- SvcLogger = "logger"
- SvcProducer = "producer"
- SvcReceiver = "receiver"
- SvcRedriver = "redriver"
+ // Services
+ SvcClient = "sqs"
+ SvcConsumer = "consume-message"
+ SvcFlags = "flags"
+ SvcHttp = "http"
+ SvcLister = "lister"
+ SvcLogger = "logger"
+ SvcMessageStore = "message-store"
+ SvcProducer = "producer"
+ SvcReceiver = "receiver"
+ SvcRedriver = "redriver"
+ SvcRenderer = "renderer"
func FlagsService(dic *izidic.Container) (any, error) {
fs := flag.NewFlagSet(dic.MustParam(PName).(string), flag.ContinueOnError)
+ addr := fs.String(PAddr, ":8080", "The IP address on which to listen")
profile := fs.String(PProfile, "test-profile", "The AWS profile")
region := fs.String(PRegion, "eu-west-3", "The AWS region to connect to")
qName := fs.String(PQName, "dummy-queue", "The queue name")
+ csrfSecret := fs.String(PCSRFSecret, "csrfSecret", "The CSRF secret")
+ prefix := fs.String(PPrefix, "", "The queue prefix to filter the main list")
+ storeSecret := fs.String(PStoreSecret, "storeSecret", "The session store secret")
sqsURL := fs.String(PURL, "http://localhost:4566", "The SQS endpoint URL")
+ ttl := fs.Duration(PTTL, 10*time.Minute, "The message store TTL")
+ vto := fs.Duration(PVTO, 10*time.Minute, "The redrive visibility timeout")
+ wait := fs.Int(PWait, 3, "The maximum number of seconds to wait when receiving messages")
if err := fs.Parse(dic.MustParam(PArgs).([]string)); err != nil {
return nil, fmt.Errorf("cannot obtain CLI args")
+ // Durations are signed, and Duration.Round rounds down, but we want positive durations, rounded up.
+ *vto = (vto.Abs() + 500*time.Millisecond).Round(time.Second)
+ dic.Store(PAddr, *addr)
dic.Store(PProfile, *profile)
+ dic.Store(PQName, *qName)
dic.Store(PRegion, *region)
+ dic.Store(PCSRFSecret, []byte(*csrfSecret))
+ dic.Store(PPrefix, *prefix)
+ dic.Store(PStoreSecret, []byte(*storeSecret))
+ dic.Store(PTTL, *ttl)
dic.Store(PURL, *sqsURL)
- dic.Store(PQName, *qName)
+ dic.Store(PVTO, *vto)
+ dic.Store(PWait, *wait)
return fs, nil
@@ -0,0 +1,203 @@
+package web
+ "net/http"
+ "regexp"
+ "github.com/gin-contrib/sessions"
+ "github.com/google/uuid"
+ csrf "github.com/utrack/gin-csrf"
+ "golang.org/x/exp/maps"
+type (
+ // Level is an information level: danger | warning.
+ Level string
+ // QueueOp is an allowed operation or reserved error: delete|redrive|invalid.
+ QueueOp string
+ LevelDanger Level = "danger"
+ LevelInfo Level = "info"
+ LevelWarning Level = "warning"
+ OpDelete QueueOp = "delete"
+ OpInvalid QueueOp = "invalid"
+ OpPurge QueueOp = "purge"
+ OpRedrive QueueOp = "redrive"
+var confirms = map[QueueOp]struct {
+ question, description string
+ confirm string
+ Level // danger|warning
+}{
+ OpDelete: {
+ confirm: "Clear",
+ description: "These messages cannot be recovered after that step",
+ question: "Do you confirm this deletion request?",
+ Level: LevelDanger,
+ OpInvalid: {
+ description: "The operation requested is invalid",
+ Level: LevelInfo,
+ OpPurge: {
+ confirm: "Purge",
+ description: "All the messages in the letter queue will be lost after that step",
+ question: "Do you confirm this purge request?",
+ OpRedrive: {
+ confirm: "Redrive",
+ description: "These messages will be in their source queue after that step",
+ question: "Do you confirm this redriving request?",
+ Level: LevelWarning,
+// getOp extracts the operation from the unsafe form data, returning a safe value.
+func getOp(u *url.URL) QueueOp {
+ q := u.Query()
+ found := 0
+ var res QueueOp
+ for key := range q {
+ switch op := QueueOp(strings.ToLower(key)); op {
+ case OpDelete, OpPurge, OpRedrive:
+ res = op
+ found++
+ if found != 1 {
+ return OpInvalid
+ return res
+type IDValidator func(in string) bool
+// validateUint is an IDValidator for unsigned integer strings.
+func validateUint(in string) bool {
+ _, err := strconv.Atoi(in)
+ return err == nil
+// validateUUID is an IDValidator for UUID strings
+func validateUUID(in string) bool {
+ _, err := uuid.Parse(in)
+// validateARN is an IDValidator for ARN strings
+func validateARN(in string) bool {
+ return arn.IsARN(in)
+func parseIDs(v url.Values, isValid IDValidator) []string {
+ const prefix = "id-"
+ var ids []string
+ for k, vs := range v {
+ // Weed out bad keys, bad value lengths, and non-matching checkbox statuses.
+ if pos := strings.Index(k, prefix); pos == -1 || len(vs) != 1 || vs[0] != "on" {
+ b, a, found := strings.Cut(k, prefix)
+ if b == "" && found && isValid(a) {
+ ids = append(ids, a)
+ return ids
+func parseMessages(v url.Values, ids []string) []redriver.Message {
+ m := make(map[int]redriver.Message, len(ids))
+ keyRx := regexp.MustCompile("^(id|mid|rh)-(.+)")
+ mOK := make(map[int]bool, len(ids))
+ ms := keyRx.FindStringSubmatch(k)
+ if len(ms) != 3 || len(vs) != 1 {
+ v := vs[0]
+ id, err := strconv.Atoi(ms[2])
+ message := m[id]
+ switch ms[1] {
+ case "id":
+ if v == "on" {
+ mOK[id] = true
+ case "rh":
+ message.ReceiptHandle = v
+ case "mid":
+ message.MessageId = v
+ m[id] = message
+ // Only return selected messages.
+ res := make(map[int]redriver.Message, len(mOK))
+ for id := range mOK {
+ res[id] = m[id]
+ return maps.Values(res)
+func makeConfirmHandler() gin.HandlerFunc {
+ return func(c *gin.Context) {
+ qName := c.Param("name")
+ u := c.Request.URL
+ sess := sessions.Default(c)
+ flashes := sess.Flashes()
+ defer func() {
+ _ = sess.Save()
+ }()
+ op := getOp(u)
+ ids := parseIDs(u.Query(), validateUint)
+ var messages []redriver.Message
+ if len(ids) == 0 && op != OpPurge {
+ op = OpInvalid
+ messages = nil
+ } else {
+ messages = parseMessages(u.Query(), ids)
+ token := csrf.GetToken(c)
+ data := confirms[op] // op is guaranteed to be valid by getOp().
+ h := gin.H{
+ "action": "/queue/" + qName + "/" + string(op),
+ "cancel": "Cancel",
+ "cancelURL": "/queue/" + qName,
+ "confirm": data.confirm,
+ "csrf": token,
+ "description": data.description,
+ "flashes": flashes,
+ "level": data.Level,
+ "messages": messages,
+ "question": data.question,
+ if qName == "" {
+ h["cancelURL"] = "/"
+ if op == OpInvalid {
+ h["messages"] = nil
+ c.HTML(http.StatusOK, "confirm", h)
@@ -0,0 +1,30 @@
+func Test__getOp(t *testing.T) {
+ empty := []string{""}
+ input url.Values
+ expected QueueOp
+ {"delete", url.Values{"delete": empty}, OpDelete},
+ {"redrive", url.Values{"redrive": empty}, OpRedrive},
+ {"none", url.Values{"whatever": empty}, OpInvalid},
+ {"both", url.Values{"delete": nil, "redrive": nil}, OpInvalid},
+ var u = url.URL{
+ RawQuery: test.input.Encode(),
+ actual := getOp(&u)
+ if actual != test.expected {
+ t.Errorf("got %s but expected %s", actual, test.expected)
@@ -0,0 +1,65 @@
+func makeDeleteHandler(rd redriver.Redriver) gin.HandlerFunc {
+ ctx := c.Request.Context()
+ redirect := "/queue/" + qName
+ // Do not consume sess.Flashes(): this is a redirect-only handler, and they would be lost.
+ c.Redirect(http.StatusSeeOther, redirect)
+ req := c.Request
+ if err := req.ParseForm(); err != nil {
+ log.Printf("Failed to parse deletion confirm form for queue %s: %v",
+ sess.AddFlash(fmt.Sprintf("Failed to parsed deletion confirmm form for queue %s",
+ qName))
+ return
+ ids := parseIDs(req.Form, validateUint)
+ if len(ids) == 0 {
+ flash := fmt.Sprintf("Got no message to delete from queue %q", qName)
+ log.Print(flash)
+ sess.AddFlash(flash)
+ messages = parseMessages(req.Form, ids)
+ keys := make([]redriver.ItemsKeys, len(messages))
+ for i, msg := range messages {
+ keys[i] = msg.Keys()
+ err := rd.DeleteItems(ctx, qName, keys)
+ latency := time.Since(t0)
+ log.Printf("failed deleting %d items from queue %q: %v",
+ sess.AddFlash(fmt.Sprintf("Failed deleting %d items from queue %q",
+ len(keys), qName))
+ sess.AddFlash(fmt.Sprintf("Deleted %d items from queue %s in %v", len(keys),
+ qName, latency))
@@ -0,0 +1,242 @@
+// classifyPolicies distributes queue policies across five bins:
+// - wild: policies describing a DLQ with an allowAll policy
+// - closed: policies describing a DLQ with an denyAll policy
+// - byQueue: policies describing a DLQ with an byQueue policy
+// - sources: policies describing a queue with a DLQ
+// - notSources: policies describing a queue without a DLQ
+// Note that dead-letter queues may also be source queues, for multi-level processing, and vice-versa.
+// For easier reading, DLQs are not listed as part of the non-source list.
+func classifyPolicies(policies map[string]*redriver.QueueRedrivePolicies) (byQueue, wild, closed, sources map[string]redriver.QueueRedrivePolicies, notSources []string) {
+ DLQs := make(map[string]redriver.QueueRedrivePolicies)
+ sources = make(map[string]redriver.QueueRedrivePolicies)
+ wild = make(map[string]redriver.QueueRedrivePolicies)
+ closed = make(map[string]redriver.QueueRedrivePolicies)
+ byQueue = make(map[string]redriver.QueueRedrivePolicies)
+ nsTemp := make([]string, 0, len(notSources))
+ for qURL, qrp := range policies {
+ if qrp.QueueInfoAttributesRedriveAllowPolicy != nil {
+ DLQs[qURL] = *qrp
+ if qrp.QueueInfoAttributesRedrivePolicy != nil {
+ sources[qURL] = *qrp
+ nsTemp = append(nsTemp, qURL)
+ for _, qURL := range nsTemp {
+ if _, ok := DLQs[qURL]; !ok {
+ notSources = append(notSources, qURL)
+ for qURL, qrp := range DLQs {
+ rap := qrp.QueueInfoAttributesRedriveAllowPolicy
+ switch rap.RedrivePermission {
+ case "allowAll":
+ wild[qURL] = qrp
+ case "denyAll":
+ closed[qURL] = qrp
+ case "byQueue":
+ byQueue[qURL] = qrp
+func policiesByURL(c *gin.Context, qURLs []string, rd redriver.Redriver, ctx context.Context) map[string]*redriver.QueueRedrivePolicies {
+ qMap := make(map[string]*redriver.QueueRedrivePolicies, len(qURLs))
+ for _, qURL := range qURLs {
+ name, err := redriver.NameFromURL(qURL)
+ log.Println(http.StatusInternalServerError)
+ c.HTML(http.StatusInternalServerError, "500", nil)
+ qrp, err := rd.GetRedrivePolicies(ctx, name)
+ if err != nil || qrp == nil {
+ log.Println(err)
+ qMap[qURL] = qrp
+ return qMap
+type link struct{ URL, Text string }
+// queueRow provides templates with a representation for all kinds of table cells
+type queueCell struct {
+ Message string
+ Link *link
+ Links [][]link
+type QueueRow [2]queueCell
+func makeHomeHandler(rd redriver.Redriver, prefix string) gin.HandlerFunc {
+ defer func() { _ = sess.Save() }()
+ qURLs, err := rd.ListQueues(ctx, prefix)
+ log.Printf("failed listing queues: %v", err)
+ policies := policiesByURL(c, qURLs, rd, ctx)
+ byQueue, wild, closed, sources, notSources := classifyPolicies(policies)
+ // log.Printf("DLQs: %#v\nSRCs: %#v\nNoSRCs: %#v\n", maps.Keys(DLQs), maps.Keys(SRCs), NoSRCs)
+ rows := make([]QueueRow, 0, len(byQueue)+3) // wild, closed, notSources: sources appear within a byQueue row
+ rows, err = prepareSingleLeft(rows, wild, "All source queues allowed")
+ log.Printf("failed preparing wild DLQs: %v", err)
+ rows, err = prepareSingleLeft(rows, closed, "No source queue allowed")
+ log.Printf("failed preparing closed DLQs: %v", err)
+ rows, err = prepareSingleRight(rows, notSources, "No associated DLQ")
+ log.Printf("failed converting isolated queues: %v", err)
+ rows, err = prepareBindings(rows, byQueue, sources)
+ log.Printf("failed preparing queue bindings: %v", err)
+ c.HTML(http.StatusOK, "home", gin.H{
+ "latency": latency,
+ "prefix": prefix,
+ "rows": rows,
+func prepareSingleLeft(rows []QueueRow, list map[string]redriver.QueueRedrivePolicies, msg string) ([]QueueRow, error) {
+ if len(list) > 0 {
+ row := QueueRow{
+ {Message: msg},
+ {Links: make([][]link, 1)},
+ row[1].Links[0] = make([]link, 0, len(list))
+ for qURL := range list {
+ row[1].Links[0] = append(row[1].Links[0], link{URL: "/queue/" + name, Text: name})
+ rows = append(rows, row)
+ return rows, nil
+func prepareSingleRight(rows []QueueRow, list []string, msg string) ([]QueueRow, error) {
+ row[0].Links[0] = make([]link, 0, len(list))
+ for _, qURL := range list {
+ row[0].Links[0] = append(row[0].Links[0], link{URL: "/queue/" + name, Text: name})
+func prepareBinding(dlqURL string, DLQrp redriver.QueueRedrivePolicies, sources map[string]redriver.QueueRedrivePolicies) (*QueueRow, error) {
+ var left, right queueCell
+ dlqName, err := redriver.NameFromURL(dlqURL)
+ return nil, fmt.Errorf("failed parsing DLQ URL %q: %w", dlqURL, err)
+ right = queueCell{Link: &link{URL: "/queue/" + dlqName, Text: dlqName}}
+ a, err := redriver.ARNFromURL(dlqURL)
+ return nil, fmt.Errorf("failed converting URL %q to ARN: %w", dlqURL, err)
+ dlqARN := a.String()
+ var bound, unbound, elsewhere []link
+ for _, sourceARN := range DLQrp.SourceQueueARNs {
+ sa, err := arn.Parse(sourceARN)
+ return nil, fmt.Errorf("failed parsing %q as an ARN: %w", sourceARN, err)
+ sourceURL, err := redriver.URLFromARN(sa)
+ return nil, fmt.Errorf("failed converting ARN %q for URL: %w", sa.String(), err)
+ sourceName := sa.Resource
+ sourceQRP, ok := sources[sourceURL]
+ link := link{
+ URL: "/queue/" + sourceName,
+ Text: sourceName,
+ switch sourceQRP.DeadLetterTargetARN {
+ case "":
+ unbound = append(unbound, link)
+ case dlqARN:
+ bound = append(bound, link)
+ elsewhere = append(elsewhere, link)
+ left.Links = [][]link{bound, elsewhere, unbound}
+ return &QueueRow{left, right}, nil
+func prepareBindings(rows []QueueRow, DLQs, sources map[string]redriver.QueueRedrivePolicies) ([]QueueRow, error) {
+ for dlqURL, DLQrp := range DLQs {
+ row, err := prepareBinding(dlqURL, DLQrp, sources)
+ if err != nil || row == nil {
+ return nil, fmt.Errorf("failed binding DLQ %s: %w", dlqURL, err)
+ rows = append(rows, *row)
@@ -0,0 +1,43 @@
+func makePurgeHandler(rd redriver.Redriver) gin.HandlerFunc {
+ err := rd.Purge(ctx, qName)
+ log.Printf("failed purging queue %q: %v", qName, err)
+ sess.AddFlash(fmt.Sprintf("Failed purging queue %q", qName))
+ sess.AddFlash(fmt.Sprintf("Purged submitted for queue %s in %v", qName,
+ latency))
+ sess.AddFlash("The actual purge process takes up to 60 seconds, " +
+ "so not be surprised if some messages are still visible on the queue messsages list." +
+ "We recommend waiting for 60 seconds to refresh, regardless of your queue's size.")
@@ -0,0 +1,52 @@
+func makeQueueHandler(rd redriver.Redriver, ms redriver.MessageStore) gin.HandlerFunc {
+ qi, err := rd.GetQueueInfo(ctx, qName)
+ infoLatency := time.Since(t0)
+ log.Printf("failed getting info for queue %q: %v", qName, err)
+ items, err := rd.GetQueueItems(ctx, qName)
+ log.Printf("failed getting items for queue %q: %v", qName, err)
+ for _, item := range items {
+ ms.Set(item)
+ itemsLatency := time.Since(t0) - infoLatency
+ c.HTML(http.StatusOK, "queue-get", gin.H{
+ "info": qi,
+ "isDLQ": qi.IsDLQ(),
+ "items": items,
+ "latency": map[string]time.Duration{
+ "info": infoLatency,
+ "items": itemsLatency,
@@ -0,0 +1,78 @@
+func makeRedriveHandler(rd redriver.Redriver, ms redriver.MessageStore) gin.HandlerFunc {
+ ctx := req.Context()
+ messages []redriver.Message
+ done bool
+ if messages, done = messagesFromRequest(req, qName, sess, ms); done {
+ var flash string
+ if err := rd.RedriveItems(ctx, qName, messages); err != nil {
+ flash = fmt.Sprintf("failed redriving selected messages on queue %q: %v",
+ flash = fmt.Sprintf("%d messages redriven to queue %q."+
+ `They now carry new MessageIDs and their past MessageID will be visible in the "previous-message-id" attribute`,
+ len(messages), qName)
+func messagesFromRequest(req *http.Request, qName string, sess sessions.Session, ms redriver.MessageStore) ([]redriver.Message, bool) {
+ return nil, true
+ for i, message := range messages {
+ m, ok := ms.Get(message.ReceiptHandle)
+ sess.AddFlash(fmt.Sprintf("Failed retrieving message with ID %s and receipt handle %s from queue %q. Aborting redrive.",
+ message.MessageId, message.ReceiptHandle, qName))
+ messages[i] = m
+ return messages, false
@@ -0,0 +1,118 @@
+ _ "embed"
+ "html/template"
+ "path/filepath"
+ "github.com/Masterminds/sprig/v3"
+ "github.com/davecgh/go-spew/spew"
+ "github.com/gin-contrib/sessions/cookie"
+ "github.com/utrack/gin-csrf"
+ "code.osinet.fr/fgm/sqs_demo/front"
+func SetupRoutes(rd redriver.Redriver, ms redriver.MessageStore, renderer *template.Template, storeSecret, csrfSecret []byte, prefix string) *gin.Engine {
+ const assetsPrefix = "/assets/"
+ r := gin.Default()
+ r.SetHTMLTemplate(renderer)
+ _ = r.SetTrustedProxies(nil)
+ store := cookie.NewStore(storeSecret)
+ r.Use(sessions.Sessions("defaultsession", store))
+ mw := csrf.Middleware(csrf.Options{
+ Secret: string(csrfSecret),
+ ErrorFunc: func(c *gin.Context) {
+ c.String(http.StatusBadRequest, "CSRF token does not match")
+ c.Abort()
+ TokenGetter: nil,
+ // Done
+ r.StaticFS(assetsPrefix, PrefixFileSystem(assetsPrefix, http.FS(front.Assets)))
+ r.GET("/queue", gin.WrapH(http.RedirectHandler("/", http.StatusMovedPermanently)))
+ r.GET("/queue/:name/confirm", mw, makeConfirmHandler()) // Needs mw to set token.
+ // Back done, front WIP
+ r.GET("/queue/:name", makeQueueHandler(rd, ms))
+ // JSON done
+ r.GET("/", makeHomeHandler(rd, prefix))
+ // TODO
+ r.POST("/queue/:name/delete", mw, makeDeleteHandler(rd)) // Needs mw to check token.
+ r.POST("/queue/:name/purge", mw, makePurgeHandler(rd)) // Needs mw to check token.
+ r.POST("/queue/:name/redrive", mw, makeRedriveHandler(rd, ms)) // Needs mw to check token.
+ return r
+func HttpService(dic *izidic.Container) (any, error) {
+ csrfSecret := dic.MustParam(services.PCSRFSecret).([]byte)
+ prefix := dic.MustParam(services.PPrefix).(string)
+ storeSecret := dic.MustParam(services.PStoreSecret).([]byte)
+ rd := dic.MustService(services.SvcRedriver).(redriver.Redriver)
+ re := dic.MustService(services.SvcRenderer).(*template.Template)
+ ms := dic.MustService(services.SvcMessageStore).(redriver.MessageStore)
+ return SetupRoutes(rd, ms, re, storeSecret, csrfSecret, prefix), nil
+func RendererService(_ *izidic.Container) (any, error) {
+ var err error
+ renderer := template.New("redriver").Funcs(template.FuncMap{
+ "dump": func(args ...any) template.HTML {
+ return "<pre>" + template.HTML(spew.Sdump(args...)) + "</pre>\n"
+ "timestamp": func(ts int64) time.Time {
+ return time.Unix(ts, 0)
+ "nameFromARN": redriver.NameFromARNString,
+ }).Funcs(sprig.FuncMap())
+ for _, tpl := range []struct {
+ value string
+ {"confirm", front.Confirm},
+ {"flashes", front.Flashes},
+ {"home", front.Home},
+ {"queue-get", front.QueueGet}, // Includes queue-item.
+ {"500", front.Err500},
+ renderer, err = renderer.New(tpl.name).Parse(tpl.value)
+ return nil, fmt.Errorf("failed parsing %q template: %w", tpl.name, err)
+ return renderer, nil
+// PrefixFileSystem converts a http.FileSystem by serving it requests prefixed
+// by the passed prefix, allowing rooted static directories matching the name
+// of an embed.FS.
+func PrefixFileSystem(prefix string, ifs http.FileSystem) http.FileSystem {
+ return &prefixedFS{
+ prefix: prefix,
+ ifs: ifs,
+type prefixedFS struct {
+ prefix string
+ ifs http.FileSystem
+// Open implements http.FileSystem
+func (pfs *prefixedFS) Open(name string) (http.File, error) {
+ return pfs.ifs.Open(filepath.Join(pfs.prefix, name))
@@ -0,0 +1,11783 @@
+@charset "UTF-8";
+/*!
+ * Bootstrap v5.3.0-alpha1 (https://getbootstrap.com/)
+ * Copyright 2011-2022 The Bootstrap Authors
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */
+:root,
+[data-bs-theme=light] {
+ --bs-blue: #0d6efd;
+ --bs-indigo: #6610f2;
+ --bs-purple: #6f42c1;
+ --bs-pink: #d63384;
+ --bs-red: #dc3545;
+ --bs-orange: #fd7e14;
+ --bs-yellow: #ffc107;
+ --bs-green: #198754;
+ --bs-teal: #20c997;
+ --bs-cyan: #0dcaf0;
+ --bs-black: #000;
+ --bs-white: #fff;
+ --bs-gray: #6c757d;
+ --bs-gray-dark: #343a40;
+ --bs-gray-100: #f8f9fa;
+ --bs-gray-200: #e9ecef;
+ --bs-gray-300: #dee2e6;
+ --bs-gray-400: #ced4da;
+ --bs-gray-500: #adb5bd;
+ --bs-gray-600: #6c757d;
+ --bs-gray-700: #495057;
+ --bs-gray-800: #343a40;
+ --bs-gray-900: #212529;
+ --bs-primary: #0d6efd;
+ --bs-secondary: #6c757d;
+ --bs-success: #198754;
+ --bs-info: #0dcaf0;
+ --bs-warning: #ffc107;
+ --bs-danger: #dc3545;
+ --bs-light: #f8f9fa;
+ --bs-dark: #212529;
+ --bs-primary-rgb: 13, 110, 253;
+ --bs-secondary-rgb: 108, 117, 125;
+ --bs-success-rgb: 25, 135, 84;
+ --bs-info-rgb: 13, 202, 240;
+ --bs-warning-rgb: 255, 193, 7;
+ --bs-danger-rgb: 220, 53, 69;
+ --bs-light-rgb: 248, 249, 250;
+ --bs-dark-rgb: 33, 37, 41;
+ --bs-primary-text: #0a58ca;
+ --bs-secondary-text: #6c757d;
+ --bs-success-text: #146c43;
+ --bs-info-text: #087990;
+ --bs-warning-text: #997404;
+ --bs-danger-text: #b02a37;
+ --bs-light-text: #6c757d;
+ --bs-dark-text: #495057;
+ --bs-primary-bg-subtle: #cfe2ff;
+ --bs-secondary-bg-subtle: #f8f9fa;
+ --bs-success-bg-subtle: #d1e7dd;
+ --bs-info-bg-subtle: #cff4fc;
+ --bs-warning-bg-subtle: #fff3cd;
+ --bs-danger-bg-subtle: #f8d7da;
+ --bs-light-bg-subtle: #fcfcfd;
+ --bs-dark-bg-subtle: #ced4da;
+ --bs-primary-border-subtle: #9ec5fe;
+ --bs-secondary-border-subtle: #e9ecef;
+ --bs-success-border-subtle: #a3cfbb;
+ --bs-info-border-subtle: #9eeaf9;
+ --bs-warning-border-subtle: #ffe69c;
+ --bs-danger-border-subtle: #f1aeb5;
+ --bs-light-border-subtle: #e9ecef;
+ --bs-dark-border-subtle: #adb5bd;
+ --bs-white-rgb: 255, 255, 255;
+ --bs-black-rgb: 0, 0, 0;
+ --bs-body-color-rgb: 33, 37, 41;
+ --bs-body-bg-rgb: 255, 255, 255;
+ --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+ --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
+ --bs-body-font-family: var(--bs-font-sans-serif);
+ --bs-body-font-size: 1rem;
+ --bs-body-font-weight: 400;
+ --bs-body-line-height: 1.5;
+ --bs-body-color: #212529;
+ --bs-emphasis-color: #000;
+ --bs-emphasis-color-rgb: 0, 0, 0;
+ --bs-secondary-color: rgba(33, 37, 41, 0.75);
+ --bs-secondary-color-rgb: 33, 37, 41;
+ --bs-secondary-bg: #e9ecef;
+ --bs-secondary-bg-rgb: 233, 236, 239;
+ --bs-tertiary-color: rgba(33, 37, 41, 0.5);
+ --bs-tertiary-color-rgb: 33, 37, 41;
+ --bs-tertiary-bg: #f8f9fa;
+ --bs-tertiary-bg-rgb: 248, 249, 250;
+ --bs-body-bg: #fff;
+ --bs-link-color: #0d6efd;
+ --bs-link-color-rgb: 13, 110, 253;
+ --bs-link-decoration: underline;
+ --bs-link-hover-color: #0a58ca;
+ --bs-link-hover-color-rgb: 10, 88, 202;
+ --bs-code-color: #d63384;
+ --bs-highlight-bg: #fff3cd;
+ --bs-border-width: 1px;
+ --bs-border-style: solid;
+ --bs-border-color: #dee2e6;
+ --bs-border-color-translucent: rgba(0, 0, 0, 0.175);
+ --bs-border-radius: 0.375rem;
+ --bs-border-radius-sm: 0.25rem;
+ --bs-border-radius-lg: 0.5rem;
+ --bs-border-radius-xl: 1rem;
+ --bs-border-radius-2xl: 2rem;
+ --bs-border-radius-pill: 50rem;
+ --bs-box-shadow: 0 0.5rem 1rem rgba(var(--bs-body-color-rgb), 0.15);
+ --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(var(--bs-body-color-rgb), 0.075);
+ --bs-box-shadow-lg: 0 1rem 3rem rgba(var(--bs-body-color-rgb), 0.175);
+ --bs-box-shadow-inset: inset 0 1px 2px rgba(var(--bs-body-color-rgb), 0.075);
+ --bs-form-control-bg: var(--bs-body-bg);
+ --bs-form-control-disabled-bg: var(--bs-secondary-bg);
+ --bs-breakpoint-xs: 0;
+ --bs-breakpoint-sm: 576px;
+ --bs-breakpoint-md: 768px;
+ --bs-breakpoint-lg: 992px;
+ --bs-breakpoint-xl: 1200px;
+ --bs-breakpoint-xxl: 1400px;
+[data-bs-theme=dark] {
+ --bs-body-color: #adb5bd;
+ --bs-body-color-rgb: 173, 181, 189;
+ --bs-body-bg: #212529;
+ --bs-body-bg-rgb: 33, 37, 41;
+ --bs-emphasis-color: #f8f9fa;
+ --bs-emphasis-color-rgb: 248, 249, 250;
+ --bs-secondary-color: rgba(173, 181, 189, 0.75);
+ --bs-secondary-color-rgb: 173, 181, 189;
+ --bs-secondary-bg: #343a40;
+ --bs-secondary-bg-rgb: 52, 58, 64;
+ --bs-tertiary-color: rgba(173, 181, 189, 0.5);
+ --bs-tertiary-color-rgb: 173, 181, 189;
+ --bs-tertiary-bg: #2b3035;
+ --bs-tertiary-bg-rgb: 43, 48, 53;
+ --bs-emphasis-color: #fff;
+ --bs-primary-text: #6ea8fe;
+ --bs-secondary-text: #dee2e6;
+ --bs-success-text: #75b798;
+ --bs-info-text: #6edff6;
+ --bs-warning-text: #ffda6a;
+ --bs-danger-text: #ea868f;
+ --bs-light-text: #f8f9fa;
+ --bs-dark-text: #dee2e6;
+ --bs-primary-bg-subtle: #031633;
+ --bs-secondary-bg-subtle: #212529;
+ --bs-success-bg-subtle: #051b11;
+ --bs-info-bg-subtle: #032830;
+ --bs-warning-bg-subtle: #332701;
+ --bs-danger-bg-subtle: #2c0b0e;
+ --bs-light-bg-subtle: #343a40;
+ --bs-dark-bg-subtle: #1a1d20;
+ --bs-primary-border-subtle: #084298;
+ --bs-secondary-border-subtle: #495057;
+ --bs-success-border-subtle: #0f5132;
+ --bs-info-border-subtle: #055160;
+ --bs-warning-border-subtle: #664d03;
+ --bs-danger-border-subtle: #842029;
+ --bs-light-border-subtle: #495057;
+ --bs-dark-border-subtle: #343a40;
+ --bs-heading-color: #fff;
+ --bs-link-color: #6ea8fe;
+ --bs-link-hover-color: #9ec5fe;
+ --bs-link-color-rgb: 110, 168, 254;
+ --bs-link-hover-color-rgb: 158, 197, 254;
+ --bs-code-color: #e685b5;
+ --bs-border-color: #495057;
+ --bs-border-color-translucent: rgba(255, 255, 255, 0.15);
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+@media (prefers-reduced-motion: no-preference) {
+ :root {
+ scroll-behavior: smooth;
+body {
+ margin: 0;
+ font-family: var(--bs-body-font-family);
+ font-size: var(--bs-body-font-size);
+ font-weight: var(--bs-body-font-weight);
+ line-height: var(--bs-body-line-height);
+ color: var(--bs-body-color);
+ text-align: var(--bs-body-text-align);
+ background-color: var(--bs-body-bg);
+ -webkit-text-size-adjust: 100%;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+hr {
+ margin: 1rem 0;
+ color: inherit;
+ border: 0;
+ border-top: var(--bs-border-width) solid;
+ opacity: 0.25;
+h6, .h6, h5, .h5, h4, .h4, h3, .h3, h2, .h2, h1, .h1 {
+ margin-top: 0;
+ margin-bottom: 0.5rem;
+ font-weight: 500;
+ line-height: 1.2;
+ color: var(--bs-heading-color, inherit);
+h1, .h1 {
+ font-size: calc(1.375rem + 1.5vw);
+@media (min-width: 1200px) {
+ h1, .h1 {
+ font-size: 2.5rem;
+h2, .h2 {
+ font-size: calc(1.325rem + 0.9vw);
+ h2, .h2 {
+ font-size: 2rem;
+h3, .h3 {
+ font-size: calc(1.3rem + 0.6vw);
+ h3, .h3 {
+ font-size: 1.75rem;
+h4, .h4 {
+ font-size: calc(1.275rem + 0.3vw);
+ h4, .h4 {
+ font-size: 1.5rem;
+h5, .h5 {
+ font-size: 1.25rem;
+h6, .h6 {
+ font-size: 1rem;
+p {
+ margin-bottom: 1rem;
+abbr[title] {
+ -webkit-text-decoration: underline dotted;
+ text-decoration: underline dotted;
+ cursor: help;
+ -webkit-text-decoration-skip-ink: none;
+ text-decoration-skip-ink: none;
+address {
+ font-style: normal;
+ line-height: inherit;
+ol,
+ul {
+ padding-left: 2rem;
+ul,
+dl {
+ol ol,
+ul ul,
+ol ul,
+ul ol {
+ margin-bottom: 0;
+dt {
+ font-weight: 700;
+dd {
+ margin-left: 0;
+blockquote {
+ margin: 0 0 1rem;
+b,
+strong {
+ font-weight: bolder;
+small, .small {
+ font-size: 0.875em;
+mark, .mark {
+ padding: 0.1875em;
+ background-color: var(--bs-highlight-bg);
+sub,
+sup {
+ position: relative;
+ font-size: 0.75em;
+ line-height: 0;
+ vertical-align: baseline;
+sub {
+ bottom: -0.25em;
+ top: -0.5em;
+a {
+ color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
+ text-decoration: underline;
+a:hover {
+ --bs-link-color-rgb: var(--bs-link-hover-color-rgb);
+a:not([href]):not([class]), a:not([href]):not([class]):hover {
+ text-decoration: none;
+pre,
+code,
+kbd,
+samp {
+ font-family: var(--bs-font-monospace);
+ font-size: 1em;
+pre {
+ display: block;
+ overflow: auto;
+pre code {
+ font-size: inherit;
+ word-break: normal;
+code {
+ color: var(--bs-code-color);
+ word-wrap: break-word;
+a > code {
+kbd {
+ padding: 0.1875rem 0.375rem;
+ color: var(--bs-body-bg);
+ background-color: var(--bs-body-color);
+ border-radius: 0.25rem;
+kbd kbd {
+ padding: 0;
+figure {
+img,
+svg {
+ vertical-align: middle;
+table {
+ caption-side: bottom;
+ border-collapse: collapse;
+caption {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ color: var(--bs-secondary-color);
+ text-align: left;
+th {
+ text-align: inherit;
+ text-align: -webkit-match-parent;
+thead,
+tbody,
+tfoot,
+tr,
+td,
+ border-color: inherit;
+ border-style: solid;
+ border-width: 0;
+label {
+ display: inline-block;
+button {
+ border-radius: 0;
+button:focus:not(:focus-visible) {
+ outline: 0;
+input,
+button,
+select,
+optgroup,
+textarea {
+ font-family: inherit;
+select {
+ text-transform: none;
+[role=button] {
+ cursor: pointer;
+ word-wrap: normal;
+select:disabled {
+ opacity: 1;
+[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
+ display: none !important;
+[type=button],
+[type=reset],
+[type=submit] {
+ -webkit-appearance: button;
+button:not(:disabled),
+[type=button]:not(:disabled),
+[type=reset]:not(:disabled),
+[type=submit]:not(:disabled) {
+::-moz-focus-inner {
+ border-style: none;
+ resize: vertical;
+fieldset {
+ min-width: 0;
+legend {
+ float: left;
+ width: 100%;
+ legend {
+legend + * {
+ clear: left;
+::-webkit-datetime-edit-fields-wrapper,
+::-webkit-datetime-edit-text,
+::-webkit-datetime-edit-minute,
+::-webkit-datetime-edit-hour-field,
+::-webkit-datetime-edit-day-field,
+::-webkit-datetime-edit-month-field,
+::-webkit-datetime-edit-year-field {
+::-webkit-inner-spin-button {
+ height: auto;
+[type=search] {
+ outline-offset: -2px;
+ -webkit-appearance: textfield;
+/* rtl:raw:
+[type="tel"],
+[type="url"],
+[type="email"],
+[type="number"] {
+ direction: ltr;
+*/
+::-webkit-search-decoration {
+ -webkit-appearance: none;
+::-webkit-color-swatch-wrapper {
+::-webkit-file-upload-button {
+ font: inherit;
+::file-selector-button {
+output {
+iframe {
+summary {
+ display: list-item;
+progress {
+[hidden] {
+.lead {
+ font-weight: 300;
+.display-1 {
+ font-size: calc(1.625rem + 4.5vw);
+ .display-1 {
+ font-size: 5rem;
+.display-2 {
+ font-size: calc(1.575rem + 3.9vw);
+ .display-2 {
+ font-size: 4.5rem;
+.display-3 {
+ font-size: calc(1.525rem + 3.3vw);
+ .display-3 {
+ font-size: 4rem;
+.display-4 {
+ font-size: calc(1.475rem + 2.7vw);
+ .display-4 {
+ font-size: 3.5rem;
+.display-5 {
+ font-size: calc(1.425rem + 2.1vw);
+ .display-5 {
+ font-size: 3rem;
+.display-6 {
+ .display-6 {
+.list-unstyled {
+ padding-left: 0;
+ list-style: none;
+.list-inline {
+.list-inline-item {
+.list-inline-item:not(:last-child) {
+ margin-right: 0.5rem;
+.initialism {
+ text-transform: uppercase;
+.blockquote {
+.blockquote > :last-child {
+.blockquote-footer {
+ margin-top: -1rem;
+ color: #6c757d;
+.blockquote-footer::before {
+ content: "— ";
+.img-fluid {
+ max-width: 100%;
+.img-thumbnail {
+ padding: 0.25rem;
+ border: var(--bs-border-width) solid var(--bs-border-color);
+ border-radius: var(--bs-border-radius);
+.figure {
+.figure-img {
+ line-height: 1;
+.figure-caption {
+.container,
+.container-fluid,
+.container-xxl,
+.container-xl,
+.container-lg,
+.container-md,
+.container-sm {
+ --bs-gutter-x: 1.5rem;
+ --bs-gutter-y: 0;
+ padding-right: calc(var(--bs-gutter-x) * 0.5);
+ padding-left: calc(var(--bs-gutter-x) * 0.5);
+ margin-right: auto;
+ margin-left: auto;
+@media (min-width: 576px) {
+ .container-sm, .container {
+ max-width: 540px;
+@media (min-width: 768px) {
+ .container-md, .container-sm, .container {
+ max-width: 720px;
+@media (min-width: 992px) {
+ .container-lg, .container-md, .container-sm, .container {
+ max-width: 960px;
+ .container-xl, .container-lg, .container-md, .container-sm, .container {
+ max-width: 1140px;
+@media (min-width: 1400px) {
+ .container-xxl, .container-xl, .container-lg, .container-md, .container-sm, .container {
+ max-width: 1320px;
+.row {
+ display: flex;
+ flex-wrap: wrap;
+ margin-top: calc(-1 * var(--bs-gutter-y));
+ margin-right: calc(-0.5 * var(--bs-gutter-x));
+ margin-left: calc(-0.5 * var(--bs-gutter-x));
+.row > * {
+ flex-shrink: 0;
+ margin-top: var(--bs-gutter-y);
+.col {
+ flex: 1 0 0%;
+.row-cols-auto > * {
+ flex: 0 0 auto;
+ width: auto;
+.row-cols-1 > * {
+.row-cols-2 > * {
+ width: 50%;
+.row-cols-3 > * {
+ width: 33.3333333333%;
+.row-cols-4 > * {
+ width: 25%;
+.row-cols-5 > * {
+ width: 20%;
+.row-cols-6 > * {
+ width: 16.6666666667%;
+.col-auto {
+.col-1 {
+ width: 8.33333333%;
+.col-2 {
+ width: 16.66666667%;
+.col-3 {
+.col-4 {
+ width: 33.33333333%;
+.col-5 {
+ width: 41.66666667%;
+.col-6 {
+.col-7 {
+ width: 58.33333333%;
+.col-8 {
+ width: 66.66666667%;
+.col-9 {
+ width: 75%;
+.col-10 {
+ width: 83.33333333%;
+.col-11 {
+ width: 91.66666667%;
+.col-12 {
+.offset-1 {
+ margin-left: 8.33333333%;
+.offset-2 {
+ margin-left: 16.66666667%;
+.offset-3 {
+ margin-left: 25%;
+.offset-4 {
+ margin-left: 33.33333333%;
+.offset-5 {
+ margin-left: 41.66666667%;
+.offset-6 {
+ margin-left: 50%;
+.offset-7 {
+ margin-left: 58.33333333%;
+.offset-8 {
+ margin-left: 66.66666667%;
+.offset-9 {
+ margin-left: 75%;
+.offset-10 {
+ margin-left: 83.33333333%;
+.offset-11 {
+ margin-left: 91.66666667%;
+.g-0,
+.gx-0 {
+ --bs-gutter-x: 0;
+.gy-0 {
+.g-1,
+.gx-1 {
+ --bs-gutter-x: 0.25rem;
+.gy-1 {
+ --bs-gutter-y: 0.25rem;
+.g-2,
+.gx-2 {
+ --bs-gutter-x: 0.5rem;
+.gy-2 {
+ --bs-gutter-y: 0.5rem;
+.g-3,
+.gx-3 {
+ --bs-gutter-x: 1rem;
+.gy-3 {
+ --bs-gutter-y: 1rem;
+.g-4,
+.gx-4 {
+.gy-4 {
+ --bs-gutter-y: 1.5rem;
+.g-5,
+.gx-5 {
+ --bs-gutter-x: 3rem;
+.gy-5 {
+ --bs-gutter-y: 3rem;
+ .col-sm {
+ .row-cols-sm-auto > * {
+ .row-cols-sm-1 > * {
+ .row-cols-sm-2 > * {
+ .row-cols-sm-3 > * {
+ .row-cols-sm-4 > * {
+ .row-cols-sm-5 > * {
+ .row-cols-sm-6 > * {
+ .col-sm-auto {
+ .col-sm-1 {
+ .col-sm-2 {
+ .col-sm-3 {
+ .col-sm-4 {
+ .col-sm-5 {
+ .col-sm-6 {
+ .col-sm-7 {
+ .col-sm-8 {
+ .col-sm-9 {
+ .col-sm-10 {
+ .col-sm-11 {
+ .col-sm-12 {
+ .offset-sm-0 {
+ .offset-sm-1 {
+ .offset-sm-2 {
+ .offset-sm-3 {
+ .offset-sm-4 {
+ .offset-sm-5 {
+ .offset-sm-6 {
+ .offset-sm-7 {
+ .offset-sm-8 {
+ .offset-sm-9 {
+ .offset-sm-10 {
+ .offset-sm-11 {
+ .g-sm-0,
+ .gx-sm-0 {
+ .gy-sm-0 {
+ .g-sm-1,
+ .gx-sm-1 {
+ .gy-sm-1 {
+ .g-sm-2,
+ .gx-sm-2 {
+ .gy-sm-2 {
+ .g-sm-3,
+ .gx-sm-3 {
+ .gy-sm-3 {
+ .g-sm-4,
+ .gx-sm-4 {
+ .gy-sm-4 {
+ .g-sm-5,
+ .gx-sm-5 {
+ .gy-sm-5 {
+ .col-md {
+ .row-cols-md-auto > * {
+ .row-cols-md-1 > * {
+ .row-cols-md-2 > * {
+ .row-cols-md-3 > * {
+ .row-cols-md-4 > * {
+ .row-cols-md-5 > * {
+ .row-cols-md-6 > * {
+ .col-md-auto {
+ .col-md-1 {
+ .col-md-2 {
+ .col-md-3 {
+ .col-md-4 {
+ .col-md-5 {
+ .col-md-6 {
+ .col-md-7 {
+ .col-md-8 {
+ .col-md-9 {
+ .col-md-10 {
+ .col-md-11 {
+ .col-md-12 {
+ .offset-md-0 {
+ .offset-md-1 {
+ .offset-md-2 {
+ .offset-md-3 {
+ .offset-md-4 {
+ .offset-md-5 {
+ .offset-md-6 {
+ .offset-md-7 {
+ .offset-md-8 {
+ .offset-md-9 {
+ .offset-md-10 {
+ .offset-md-11 {
+ .g-md-0,
+ .gx-md-0 {
+ .gy-md-0 {
+ .g-md-1,
+ .gx-md-1 {
+ .gy-md-1 {
+ .g-md-2,
+ .gx-md-2 {
+ .gy-md-2 {
+ .g-md-3,
+ .gx-md-3 {
+ .gy-md-3 {
+ .g-md-4,
+ .gx-md-4 {
+ .gy-md-4 {
+ .g-md-5,
+ .gx-md-5 {
+ .gy-md-5 {
+ .col-lg {
+ .row-cols-lg-auto > * {
+ .row-cols-lg-1 > * {
+ .row-cols-lg-2 > * {
+ .row-cols-lg-3 > * {
+ .row-cols-lg-4 > * {
+ .row-cols-lg-5 > * {
+ .row-cols-lg-6 > * {
+ .col-lg-auto {
+ .col-lg-1 {
+ .col-lg-2 {
+ .col-lg-3 {
+ .col-lg-4 {
+ .col-lg-5 {
+ .col-lg-6 {
+ .col-lg-7 {
+ .col-lg-8 {
+ .col-lg-9 {
+ .col-lg-10 {
+ .col-lg-11 {
+ .col-lg-12 {
+ .offset-lg-0 {
+ .offset-lg-1 {
+ .offset-lg-2 {
+ .offset-lg-3 {
+ .offset-lg-4 {
+ .offset-lg-5 {
+ .offset-lg-6 {
+ .offset-lg-7 {
+ .offset-lg-8 {
+ .offset-lg-9 {
+ .offset-lg-10 {
+ .offset-lg-11 {
+ .g-lg-0,
+ .gx-lg-0 {
+ .gy-lg-0 {
+ .g-lg-1,
+ .gx-lg-1 {
+ .gy-lg-1 {
+ .g-lg-2,
+ .gx-lg-2 {
+ .gy-lg-2 {
+ .g-lg-3,
+ .gx-lg-3 {
+ .gy-lg-3 {
+ .g-lg-4,
+ .gx-lg-4 {
+ .gy-lg-4 {
+ .g-lg-5,
+ .gx-lg-5 {
+ .gy-lg-5 {
+ .col-xl {
+ .row-cols-xl-auto > * {
+ .row-cols-xl-1 > * {
+ .row-cols-xl-2 > * {
+ .row-cols-xl-3 > * {
+ .row-cols-xl-4 > * {
+ .row-cols-xl-5 > * {
+ .row-cols-xl-6 > * {
+ .col-xl-auto {
+ .col-xl-1 {
+ .col-xl-2 {
+ .col-xl-3 {
+ .col-xl-4 {
+ .col-xl-5 {
+ .col-xl-6 {
+ .col-xl-7 {
+ .col-xl-8 {
+ .col-xl-9 {
+ .col-xl-10 {
+ .col-xl-11 {
+ .col-xl-12 {
+ .offset-xl-0 {
+ .offset-xl-1 {
+ .offset-xl-2 {
+ .offset-xl-3 {
+ .offset-xl-4 {
+ .offset-xl-5 {
+ .offset-xl-6 {
+ .offset-xl-7 {
+ .offset-xl-8 {
+ .offset-xl-9 {
+ .offset-xl-10 {
+ .offset-xl-11 {
+ .g-xl-0,
+ .gx-xl-0 {
+ .gy-xl-0 {
+ .g-xl-1,
+ .gx-xl-1 {
+ .gy-xl-1 {
+ .g-xl-2,
+ .gx-xl-2 {
+ .gy-xl-2 {
+ .g-xl-3,
+ .gx-xl-3 {
+ .gy-xl-3 {
+ .g-xl-4,
+ .gx-xl-4 {
+ .gy-xl-4 {
+ .g-xl-5,
+ .gx-xl-5 {
+ .gy-xl-5 {
+ .col-xxl {
+ .row-cols-xxl-auto > * {
+ .row-cols-xxl-1 > * {
+ .row-cols-xxl-2 > * {
+ .row-cols-xxl-3 > * {
+ .row-cols-xxl-4 > * {
+ .row-cols-xxl-5 > * {
+ .row-cols-xxl-6 > * {
+ .col-xxl-auto {
+ .col-xxl-1 {
+ .col-xxl-2 {
+ .col-xxl-3 {
+ .col-xxl-4 {
+ .col-xxl-5 {
+ .col-xxl-6 {
+ .col-xxl-7 {
+ .col-xxl-8 {
+ .col-xxl-9 {
+ .col-xxl-10 {
+ .col-xxl-11 {
+ .col-xxl-12 {
+ .offset-xxl-0 {
+ .offset-xxl-1 {
+ .offset-xxl-2 {
+ .offset-xxl-3 {
+ .offset-xxl-4 {
+ .offset-xxl-5 {
+ .offset-xxl-6 {
+ .offset-xxl-7 {
+ .offset-xxl-8 {
+ .offset-xxl-9 {
+ .offset-xxl-10 {
+ .offset-xxl-11 {
+ .g-xxl-0,
+ .gx-xxl-0 {
+ .gy-xxl-0 {
+ .g-xxl-1,
+ .gx-xxl-1 {
+ .gy-xxl-1 {
+ .g-xxl-2,
+ .gx-xxl-2 {
+ .gy-xxl-2 {
+ .g-xxl-3,
+ .gx-xxl-3 {
+ .gy-xxl-3 {
+ .g-xxl-4,
+ .gx-xxl-4 {
+ .gy-xxl-4 {
+ .g-xxl-5,
+ .gx-xxl-5 {
+ .gy-xxl-5 {
+.table {
+ --bs-table-color: var(--bs-body-color);
+ --bs-table-bg: transparent;
+ --bs-table-border-color: var(--bs-border-color);
+ --bs-table-accent-bg: transparent;
+ --bs-table-striped-color: var(--bs-body-color);
+ --bs-table-striped-bg: rgba(0, 0, 0, 0.05);
+ --bs-table-active-color: var(--bs-body-color);
+ --bs-table-active-bg: rgba(0, 0, 0, 0.1);
+ --bs-table-hover-color: var(--bs-body-color);
+ --bs-table-hover-bg: rgba(0, 0, 0, 0.075);
+ color: var(--bs-table-color);
+ vertical-align: top;
+ border-color: var(--bs-table-border-color);
+.table > :not(caption) > * > * {
+ padding: 0.5rem 0.5rem;
+ background-color: var(--bs-table-bg);
+ border-bottom-width: var(--bs-border-width);
+ box-shadow: inset 0 0 0 9999px var(--bs-table-accent-bg);
+.table > tbody {
+ vertical-align: inherit;
+.table > thead {
+ vertical-align: bottom;
+.table-group-divider {
+ border-top: calc(var(--bs-border-width) * 2) solid currentcolor;
+.caption-top {
+ caption-side: top;
+.table-sm > :not(caption) > * > * {
+ padding: 0.25rem 0.25rem;
+.table-bordered > :not(caption) > * {
+ border-width: var(--bs-border-width) 0;
+.table-bordered > :not(caption) > * > * {
+ border-width: 0 var(--bs-border-width);
+.table-borderless > :not(caption) > * > * {
+ border-bottom-width: 0;
+.table-borderless > :not(:first-child) {
+ border-top-width: 0;
+.table-striped > tbody > tr:nth-of-type(odd) > * {
+ --bs-table-accent-bg: var(--bs-table-striped-bg);
+ color: var(--bs-table-striped-color);
+.table-striped-columns > :not(caption) > tr > :nth-child(even) {
+.table-active {
+ --bs-table-accent-bg: var(--bs-table-active-bg);
+ color: var(--bs-table-active-color);
+.table-hover > tbody > tr:hover > * {
+ --bs-table-accent-bg: var(--bs-table-hover-bg);
+ color: var(--bs-table-hover-color);
+.table-primary {
+ --bs-table-color: #000;
+ --bs-table-bg: #cfe2ff;
+ --bs-table-border-color: #bacbe6;
+ --bs-table-striped-bg: #c5d7f2;
+ --bs-table-striped-color: #000;
+ --bs-table-active-bg: #bacbe6;
+ --bs-table-active-color: #000;
+ --bs-table-hover-bg: #bfd1ec;
+ --bs-table-hover-color: #000;
+.table-secondary {
+ --bs-table-bg: #e2e3e5;
+ --bs-table-border-color: #cbccce;
+ --bs-table-striped-bg: #d7d8da;
+ --bs-table-active-bg: #cbccce;
+ --bs-table-hover-bg: #d1d2d4;
+.table-success {
+ --bs-table-bg: #d1e7dd;
+ --bs-table-border-color: #bcd0c7;
+ --bs-table-striped-bg: #c7dbd2;
+ --bs-table-active-bg: #bcd0c7;
+ --bs-table-hover-bg: #c1d6cc;
+.table-info {
+ --bs-table-bg: #cff4fc;
+ --bs-table-border-color: #badce3;
+ --bs-table-striped-bg: #c5e8ef;
+ --bs-table-active-bg: #badce3;
+ --bs-table-hover-bg: #bfe2e9;
+.table-warning {
+ --bs-table-bg: #fff3cd;
+ --bs-table-border-color: #e6dbb9;
+ --bs-table-striped-bg: #f2e7c3;
+ --bs-table-active-bg: #e6dbb9;
+ --bs-table-hover-bg: #ece1be;
+.table-danger {
+ --bs-table-bg: #f8d7da;
+ --bs-table-border-color: #dfc2c4;
+ --bs-table-striped-bg: #eccccf;
+ --bs-table-active-bg: #dfc2c4;
+ --bs-table-hover-bg: #e5c7ca;
+.table-light {
+ --bs-table-bg: #f8f9fa;
+ --bs-table-border-color: #dfe0e1;
+ --bs-table-striped-bg: #ecedee;
+ --bs-table-active-bg: #dfe0e1;
+ --bs-table-hover-bg: #e5e6e7;
+.table-dark {
+ --bs-table-color: #fff;
+ --bs-table-bg: #212529;
+ --bs-table-border-color: #373b3e;
+ --bs-table-striped-bg: #2c3034;
+ --bs-table-striped-color: #fff;
+ --bs-table-active-bg: #373b3e;
+ --bs-table-active-color: #fff;
+ --bs-table-hover-bg: #323539;
+ --bs-table-hover-color: #fff;
+.table-responsive {
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+@media (max-width: 575.98px) {
+ .table-responsive-sm {
+@media (max-width: 767.98px) {
+ .table-responsive-md {
+@media (max-width: 991.98px) {
+ .table-responsive-lg {
+@media (max-width: 1199.98px) {
+ .table-responsive-xl {
+@media (max-width: 1399.98px) {
+ .table-responsive-xxl {
+.form-label {
+.col-form-label {
+ padding-top: calc(0.375rem + var(--bs-border-width));
+ padding-bottom: calc(0.375rem + var(--bs-border-width));
+ line-height: 1.5;
+.col-form-label-lg {
+ padding-top: calc(0.5rem + var(--bs-border-width));
+ padding-bottom: calc(0.5rem + var(--bs-border-width));
+.col-form-label-sm {
+ padding-top: calc(0.25rem + var(--bs-border-width));
+ padding-bottom: calc(0.25rem + var(--bs-border-width));
+ font-size: 0.875rem;
+.form-text {
+ margin-top: 0.25rem;
+.form-control {
+ padding: 0.375rem 0.75rem;
+ font-weight: 400;
+ background-color: var(--bs-form-control-bg);
+ background-clip: padding-box;
+ -moz-appearance: none;
+ appearance: none;
+ border-radius: 0.375rem;
+ transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+@media (prefers-reduced-motion: reduce) {
+ .form-control {
+ transition: none;
+.form-control[type=file] {
+ overflow: hidden;
+.form-control[type=file]:not(:disabled):not([readonly]) {
+.form-control:focus {
+ border-color: #86b7fe;
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+.form-control::-webkit-date-and-time-value {
+ height: 1.5em;
+.form-control::-webkit-datetime-edit {
+.form-control::-moz-placeholder {
+.form-control::placeholder {
+.form-control:disabled {
+ background-color: var(--bs-form-control-disabled-bg);
+.form-control::-webkit-file-upload-button {
+ margin: -0.375rem -0.75rem;
+ -webkit-margin-end: 0.75rem;
+ margin-inline-end: 0.75rem;
+ background-color: var(--bs-tertiary-bg);
+ pointer-events: none;
+ border-inline-end-width: var(--bs-border-width);
+ -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+ transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+.form-control::file-selector-button {
+ .form-control::-webkit-file-upload-button {
+ -webkit-transition: none;
+ .form-control::file-selector-button {
+.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button {
+ background-color: var(--bs-secondary-bg);
+.form-control:hover:not(:disabled):not([readonly])::file-selector-button {
+.form-control-plaintext {
+ padding: 0.375rem 0;
+ background-color: transparent;
+ border: solid transparent;
+.form-control-plaintext:focus {
+.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg {
+ padding-right: 0;
+.form-control-sm {
+ min-height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2));
+ padding: 0.25rem 0.5rem;
+.form-control-sm::-webkit-file-upload-button {
+ margin: -0.25rem -0.5rem;
+ -webkit-margin-end: 0.5rem;
+ margin-inline-end: 0.5rem;
+.form-control-sm::file-selector-button {
+.form-control-lg {
+ min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));
+ padding: 0.5rem 1rem;
+ border-radius: 0.5rem;
+.form-control-lg::-webkit-file-upload-button {
+ margin: -0.5rem -1rem;
+ -webkit-margin-end: 1rem;
+ margin-inline-end: 1rem;
+.form-control-lg::file-selector-button {
+textarea.form-control {
+ min-height: calc(1.5em + 0.75rem + calc(var(--bs-border-width) * 2));
+textarea.form-control-sm {
+textarea.form-control-lg {
+.form-control-color {
+ width: 3rem;
+ height: calc(1.5em + 0.75rem + calc(var(--bs-border-width) * 2));
+ padding: 0.375rem;
+.form-control-color:not(:disabled):not([readonly]) {
+.form-control-color::-moz-color-swatch {
+ border: 0 !important;
+.form-control-color::-webkit-color-swatch {
+.form-control-color.form-control-sm {
+ height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2));
+.form-control-color.form-control-lg {
+ height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2));
+.form-select {
+ --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
+ padding: 0.375rem 2.25rem 0.375rem 0.75rem;
+ -moz-padding-start: calc(0.75rem - 3px);
+ background-image: var(--bs-form-select-bg-img), var(--bs-form-select-bg-icon, none);
+ background-repeat: no-repeat;
+ background-position: right 0.75rem center;
+ background-size: 16px 12px;
+ .form-select {
+.form-select:focus {
+.form-select[multiple], .form-select[size]:not([size="1"]) {
+ padding-right: 0.75rem;
+ background-image: none;
+.form-select:disabled {
+.form-select:-moz-focusring {
+ color: transparent;
+ text-shadow: 0 0 0 var(--bs-body-color);
+.form-select-sm {
+ padding-top: 0.25rem;
+ padding-bottom: 0.25rem;
+ padding-left: 0.5rem;
+.form-select-lg {
+ padding-left: 1rem;
+[data-bs-theme=dark] .form-select {
+ --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23adb5bd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
+.form-check {
+ min-height: 1.5rem;
+ padding-left: 1.5em;
+ margin-bottom: 0.125rem;
+.form-check .form-check-input {
+ margin-left: -1.5em;
+.form-check-reverse {
+ padding-right: 1.5em;
+ text-align: right;
+.form-check-reverse .form-check-input {
+ float: right;
+ margin-right: -1.5em;
+.form-check-input {
+ --bs-form-check-bg: var(--bs-form-control-bg);
+ width: 1em;
+ height: 1em;
+ margin-top: 0.25em;
+ background-color: var(--bs-form-check-bg);
+ background-image: var(--bs-form-check-bg-image);
+ background-position: center;
+ background-size: contain;
+ -webkit-print-color-adjust: exact;
+ color-adjust: exact;
+ print-color-adjust: exact;
+.form-check-input[type=checkbox] {
+ border-radius: 0.25em;
+.form-check-input[type=radio] {
+ border-radius: 50%;
+.form-check-input:active {
+ filter: brightness(90%);
+.form-check-input:focus {
+.form-check-input:checked {
+ background-color: #0d6efd;
+ border-color: #0d6efd;
+.form-check-input:checked[type=checkbox] {
+ --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
+.form-check-input:checked[type=radio] {
+ --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e");
+.form-check-input[type=checkbox]:indeterminate {
+ --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");
+.form-check-input:disabled {
+ filter: none;
+ opacity: 0.5;
+.form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label {
+ cursor: default;
+.form-switch {
+ padding-left: 2.5em;
+.form-switch .form-check-input {
+ --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");
+ width: 2em;
+ margin-left: -2.5em;
+ background-image: var(--bs-form-switch-bg);
+ background-position: left center;
+ border-radius: 2em;
+ transition: background-position 0.15s ease-in-out;
+ .form-switch .form-check-input {
+.form-switch .form-check-input:focus {
+ --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e");
+.form-switch .form-check-input:checked {
+ background-position: right center;
+ --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
+.form-switch.form-check-reverse {
+ padding-right: 2.5em;
+.form-switch.form-check-reverse .form-check-input {
+ margin-right: -2.5em;
+.form-check-inline {
+ margin-right: 1rem;
+.btn-check {
+ position: absolute;
+ clip: rect(0, 0, 0, 0);
+.btn-check[disabled] + .btn, .btn-check:disabled + .btn {
+ opacity: 0.65;
+[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus) {
+ --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e");
+.form-range {
+ height: 1.5rem;
+.form-range:focus {
+.form-range:focus::-webkit-slider-thumb {
+ box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+.form-range:focus::-moz-range-thumb {
+.form-range::-moz-focus-outer {
+.form-range::-webkit-slider-thumb {
+ width: 1rem;
+ height: 1rem;
+ margin-top: -0.25rem;
+ border-radius: 1rem;
+ -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+ transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+ .form-range::-webkit-slider-thumb {
+.form-range::-webkit-slider-thumb:active {
+ background-color: #b6d4fe;
+.form-range::-webkit-slider-runnable-track {
+ height: 0.5rem;
+ border-color: transparent;
+.form-range::-moz-range-thumb {
+ -moz-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+ .form-range::-moz-range-thumb {
+ -moz-transition: none;
+.form-range::-moz-range-thumb:active {
+.form-range::-moz-range-track {
+.form-range:disabled {
+.form-range:disabled::-webkit-slider-thumb {
+ background-color: var(--bs-secondary-color);
+.form-range:disabled::-moz-range-thumb {
+.form-floating {
+.form-floating::before:not(.form-control:disabled) {
+ top: var(--bs-border-width);
+ left: var(--bs-border-width);
+ width: calc(100% - (calc(calc(0.375em + 0.1875rem) + calc(0.75em + 0.375rem))));
+ height: 1.875em;
+ content: "";
+.form-floating > .form-control,
+.form-floating > .form-control-plaintext,
+.form-floating > .form-select {
+ height: calc(3.5rem + calc(var(--bs-border-width) * 2));
+ line-height: 1.25;
+.form-floating > label {
+ top: 0;
+ left: 0;
+ height: 100%;
+ padding: 1rem 0.75rem;
+ text-align: start;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ border: var(--bs-border-width) solid transparent;
+ transform-origin: 0 0;
+ transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out;
+ .form-floating > label {
+.form-floating > .form-control-plaintext {
+.form-floating > .form-control::-moz-placeholder, .form-floating > .form-control-plaintext::-moz-placeholder {
+.form-floating > .form-control::placeholder,
+.form-floating > .form-control-plaintext::placeholder {
+.form-floating > .form-control:not(:-moz-placeholder-shown), .form-floating > .form-control-plaintext:not(:-moz-placeholder-shown) {
+ padding-top: 1.625rem;
+ padding-bottom: 0.625rem;
+.form-floating > .form-control:focus, .form-floating > .form-control:not(:placeholder-shown),
+.form-floating > .form-control-plaintext:focus,
+.form-floating > .form-control-plaintext:not(:placeholder-shown) {
+.form-floating > .form-control:-webkit-autofill,
+.form-floating > .form-control-plaintext:-webkit-autofill {
+.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label {
+ transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem);
+.form-floating > .form-control:focus ~ label,
+.form-floating > .form-control:not(:placeholder-shown) ~ label,
+.form-floating > .form-control-plaintext ~ label,
+.form-floating > .form-select ~ label {
+.form-floating > .form-control:-webkit-autofill ~ label {
+.form-floating > .form-control-plaintext ~ label {
+.form-floating > .form-control:disabled ~ label {
+.input-group {
+ align-items: stretch;
+.input-group > .form-control,
+.input-group > .form-select,
+.input-group > .form-floating {
+ flex: 1 1 auto;
+ width: 1%;
+.input-group > .form-control:focus,
+.input-group > .form-select:focus,
+.input-group > .form-floating:focus-within {
+ z-index: 5;
+.input-group .btn {
+ z-index: 2;
+.input-group .btn:focus {
+.input-group-text {
+ align-items: center;
+ text-align: center;
+.input-group-lg > .form-control,
+.input-group-lg > .form-select,
+.input-group-lg > .input-group-text,
+.input-group-lg > .btn {
+.input-group-sm > .form-control,
+.input-group-sm > .form-select,
+.input-group-sm > .input-group-text,
+.input-group-sm > .btn {
+.input-group-sm > .form-select {
+ padding-right: 3rem;
+.input-group:not(.has-validation) > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),
+.input-group:not(.has-validation) > .dropdown-toggle:nth-last-child(n+3),
+.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-control,
+.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-select {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+.input-group.has-validation > :nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating),
+.input-group.has-validation > .dropdown-toggle:nth-last-child(n+4),
+.input-group.has-validation > .form-floating:nth-last-child(n+3) > .form-control,
+.input-group.has-validation > .form-floating:nth-last-child(n+3) > .form-select {
+.input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) {
+ margin-left: calc(var(--bs-border-width) * -1);
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+.input-group > .form-floating:not(:first-child) > .form-control,
+.input-group > .form-floating:not(:first-child) > .form-select {
+.valid-feedback {
+ display: none;
+ color: var(--bs-success-text);
+.valid-tooltip {
+ top: 100%;
+ margin-top: 0.1rem;
+ color: #fff;
+ background-color: var(--bs-success);
+.was-validated :valid ~ .valid-feedback,
+.was-validated :valid ~ .valid-tooltip,
+.is-valid ~ .valid-feedback,
+.is-valid ~ .valid-tooltip {
+.was-validated .form-control:valid, .form-control.is-valid {
+ border-color: var(--bs-success);
+ padding-right: calc(1.5em + 0.75rem);
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
+ background-position: right calc(0.375em + 0.1875rem) center;
+ background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
+.was-validated .form-control:valid:focus, .form-control.is-valid:focus {
+ box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25);
+.was-validated textarea.form-control:valid, textarea.form-control.is-valid {
+ background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);
+.was-validated .form-select:valid, .form-select.is-valid {
+.was-validated .form-select:valid:not([multiple]):not([size]), .was-validated .form-select:valid:not([multiple])[size="1"], .form-select.is-valid:not([multiple]):not([size]), .form-select.is-valid:not([multiple])[size="1"] {
+ --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
+ padding-right: 4.125rem;
+ background-position: right 0.75rem center, center right 2.25rem;
+ background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
+.was-validated .form-select:valid:focus, .form-select.is-valid:focus {
+.was-validated .form-control-color:valid, .form-control-color.is-valid {
+ width: calc(3rem + calc(1.5em + 0.75rem));
+.was-validated .form-check-input:valid, .form-check-input.is-valid {
+.was-validated .form-check-input:valid:checked, .form-check-input.is-valid:checked {
+ background-color: var(--bs-success-text);
+.was-validated .form-check-input:valid:focus, .form-check-input.is-valid:focus {
+.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {
+.form-check-inline .form-check-input ~ .valid-feedback {
+ margin-left: 0.5em;
+.was-validated .input-group > .form-control:not(:focus):valid, .input-group > .form-control:not(:focus).is-valid,
+.was-validated .input-group > .form-select:not(:focus):valid,
+.input-group > .form-select:not(:focus).is-valid,
+.was-validated .input-group > .form-floating:not(:focus-within):valid,
+.input-group > .form-floating:not(:focus-within).is-valid {
+ z-index: 3;
+.invalid-feedback {
+ color: var(--bs-danger-text);
+.invalid-tooltip {
+ background-color: var(--bs-danger);
+.was-validated :invalid ~ .invalid-feedback,
+.was-validated :invalid ~ .invalid-tooltip,
+.is-invalid ~ .invalid-feedback,
+.is-invalid ~ .invalid-tooltip {
+.was-validated .form-control:invalid, .form-control.is-invalid {
+ border-color: var(--bs-danger);
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
+.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {
+ box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25);
+.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {
+.was-validated .form-select:invalid, .form-select.is-invalid {
+.was-validated .form-select:invalid:not([multiple]):not([size]), .was-validated .form-select:invalid:not([multiple])[size="1"], .form-select.is-invalid:not([multiple]):not([size]), .form-select.is-invalid:not([multiple])[size="1"] {
+ --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
+.was-validated .form-select:invalid:focus, .form-select.is-invalid:focus {
+.was-validated .form-control-color:invalid, .form-control-color.is-invalid {
+.was-validated .form-check-input:invalid, .form-check-input.is-invalid {
+.was-validated .form-check-input:invalid:checked, .form-check-input.is-invalid:checked {
+ background-color: var(--bs-danger-text);
+.was-validated .form-check-input:invalid:focus, .form-check-input.is-invalid:focus {
+.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {
+.form-check-inline .form-check-input ~ .invalid-feedback {
+.was-validated .input-group > .form-control:not(:focus):invalid, .input-group > .form-control:not(:focus).is-invalid,
+.was-validated .input-group > .form-select:not(:focus):invalid,
+.input-group > .form-select:not(:focus).is-invalid,
+.was-validated .input-group > .form-floating:not(:focus-within):invalid,
+.input-group > .form-floating:not(:focus-within).is-invalid {
+ z-index: 4;
+.btn {
+ --bs-btn-padding-x: 0.75rem;
+ --bs-btn-padding-y: 0.375rem;
+ --bs-btn-font-family: ;
+ --bs-btn-font-size: 1rem;
+ --bs-btn-font-weight: 400;
+ --bs-btn-line-height: 1.5;
+ --bs-btn-color: #212529;
+ --bs-btn-bg: transparent;
+ --bs-btn-border-width: var(--bs-border-width);
+ --bs-btn-border-color: transparent;
+ --bs-btn-border-radius: 0.375rem;
+ --bs-btn-hover-border-color: transparent;
+ --bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
+ --bs-btn-disabled-opacity: 0.65;
+ --bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5);
+ padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x);
+ font-family: var(--bs-btn-font-family);
+ font-size: var(--bs-btn-font-size);
+ font-weight: var(--bs-btn-font-weight);
+ line-height: var(--bs-btn-line-height);
+ color: var(--bs-btn-color);
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ border: var(--bs-btn-border-width) solid var(--bs-btn-border-color);
+ border-radius: var(--bs-btn-border-radius);
+ background-color: var(--bs-btn-bg);
+ .btn {
+.btn:hover {
+ color: var(--bs-btn-hover-color);
+ background-color: var(--bs-btn-hover-bg);
+ border-color: var(--bs-btn-hover-border-color);
+.btn-check + .btn:hover {
+ border-color: var(--bs-btn-border-color);
+.btn:focus-visible {
+ box-shadow: var(--bs-btn-focus-box-shadow);
+.btn-check:focus-visible + .btn {
+.btn-check:checked + .btn, :not(.btn-check) + .btn:active, .btn:first-child:active, .btn.active, .btn.show {
+ color: var(--bs-btn-active-color);
+ background-color: var(--bs-btn-active-bg);
+ border-color: var(--bs-btn-active-border-color);
+.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible {
+.btn:disabled, .btn.disabled, fieldset:disabled .btn {
+ color: var(--bs-btn-disabled-color);
+ background-color: var(--bs-btn-disabled-bg);
+ border-color: var(--bs-btn-disabled-border-color);
+ opacity: var(--bs-btn-disabled-opacity);
+.btn-primary {
+ --bs-btn-color: #fff;
+ --bs-btn-bg: #0d6efd;
+ --bs-btn-border-color: #0d6efd;
+ --bs-btn-hover-color: #fff;
+ --bs-btn-hover-bg: #0b5ed7;
+ --bs-btn-hover-border-color: #0a58ca;
+ --bs-btn-focus-shadow-rgb: 49, 132, 253;
+ --bs-btn-active-color: #fff;
+ --bs-btn-active-bg: #0a58ca;
+ --bs-btn-active-border-color: #0a53be;
+ --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+ --bs-btn-disabled-color: #fff;
+ --bs-btn-disabled-bg: #0d6efd;
+ --bs-btn-disabled-border-color: #0d6efd;
+.btn-secondary {
+ --bs-btn-bg: #6c757d;
+ --bs-btn-border-color: #6c757d;
+ --bs-btn-hover-bg: #5c636a;
+ --bs-btn-hover-border-color: #565e64;
+ --bs-btn-focus-shadow-rgb: 130, 138, 145;
+ --bs-btn-active-bg: #565e64;
+ --bs-btn-active-border-color: #51585e;
+ --bs-btn-disabled-bg: #6c757d;
+ --bs-btn-disabled-border-color: #6c757d;
+.btn-success {
+ --bs-btn-bg: #198754;
+ --bs-btn-border-color: #198754;
+ --bs-btn-hover-bg: #157347;
+ --bs-btn-hover-border-color: #146c43;
+ --bs-btn-focus-shadow-rgb: 60, 153, 110;
+ --bs-btn-active-bg: #146c43;
+ --bs-btn-active-border-color: #13653f;
+ --bs-btn-disabled-bg: #198754;
+ --bs-btn-disabled-border-color: #198754;
+.btn-info {
+ --bs-btn-color: #000;
+ --bs-btn-bg: #0dcaf0;
+ --bs-btn-border-color: #0dcaf0;
+ --bs-btn-hover-color: #000;
+ --bs-btn-hover-bg: #31d2f2;
+ --bs-btn-hover-border-color: #25cff2;
+ --bs-btn-focus-shadow-rgb: 11, 172, 204;
+ --bs-btn-active-color: #000;
+ --bs-btn-active-bg: #3dd5f3;
+ --bs-btn-active-border-color: #25cff2;
+ --bs-btn-disabled-color: #000;
+ --bs-btn-disabled-bg: #0dcaf0;
+ --bs-btn-disabled-border-color: #0dcaf0;
+.btn-warning {
+ --bs-btn-bg: #ffc107;
+ --bs-btn-border-color: #ffc107;
+ --bs-btn-hover-bg: #ffca2c;
+ --bs-btn-hover-border-color: #ffc720;
+ --bs-btn-focus-shadow-rgb: 217, 164, 6;
+ --bs-btn-active-bg: #ffcd39;
+ --bs-btn-active-border-color: #ffc720;
+ --bs-btn-disabled-bg: #ffc107;
+ --bs-btn-disabled-border-color: #ffc107;
+.btn-danger {
+ --bs-btn-bg: #dc3545;
+ --bs-btn-border-color: #dc3545;
+ --bs-btn-hover-bg: #bb2d3b;
+ --bs-btn-hover-border-color: #b02a37;
+ --bs-btn-focus-shadow-rgb: 225, 83, 97;
+ --bs-btn-active-bg: #b02a37;
+ --bs-btn-active-border-color: #a52834;
+ --bs-btn-disabled-bg: #dc3545;
+ --bs-btn-disabled-border-color: #dc3545;
+.btn-light {
+ --bs-btn-bg: #f8f9fa;
+ --bs-btn-border-color: #f8f9fa;
+ --bs-btn-hover-bg: #d3d4d5;
+ --bs-btn-hover-border-color: #c6c7c8;
+ --bs-btn-focus-shadow-rgb: 211, 212, 213;
+ --bs-btn-active-bg: #c6c7c8;
+ --bs-btn-active-border-color: #babbbc;
+ --bs-btn-disabled-bg: #f8f9fa;
+ --bs-btn-disabled-border-color: #f8f9fa;
+.btn-dark {
+ --bs-btn-bg: #212529;
+ --bs-btn-border-color: #212529;
+ --bs-btn-hover-bg: #424649;
+ --bs-btn-hover-border-color: #373b3e;
+ --bs-btn-focus-shadow-rgb: 66, 70, 73;
+ --bs-btn-active-bg: #4d5154;
+ --bs-btn-active-border-color: #373b3e;
+ --bs-btn-disabled-bg: #212529;
+ --bs-btn-disabled-border-color: #212529;
+.btn-outline-primary {
+ --bs-btn-color: #0d6efd;
+ --bs-btn-hover-bg: #0d6efd;
+ --bs-btn-hover-border-color: #0d6efd;
+ --bs-btn-focus-shadow-rgb: 13, 110, 253;
+ --bs-btn-active-bg: #0d6efd;
+ --bs-btn-active-border-color: #0d6efd;
+ --bs-btn-disabled-color: #0d6efd;
+ --bs-btn-disabled-bg: transparent;
+ --bs-gradient: none;
+.btn-outline-secondary {
+ --bs-btn-color: #6c757d;
+ --bs-btn-hover-bg: #6c757d;
+ --bs-btn-hover-border-color: #6c757d;
+ --bs-btn-focus-shadow-rgb: 108, 117, 125;
+ --bs-btn-active-bg: #6c757d;
+ --bs-btn-active-border-color: #6c757d;
+ --bs-btn-disabled-color: #6c757d;
+.btn-outline-success {
+ --bs-btn-color: #198754;
+ --bs-btn-hover-bg: #198754;
+ --bs-btn-hover-border-color: #198754;
+ --bs-btn-focus-shadow-rgb: 25, 135, 84;
+ --bs-btn-active-bg: #198754;
+ --bs-btn-active-border-color: #198754;
+ --bs-btn-disabled-color: #198754;
+.btn-outline-info {
+ --bs-btn-color: #0dcaf0;
+ --bs-btn-hover-bg: #0dcaf0;
+ --bs-btn-hover-border-color: #0dcaf0;
+ --bs-btn-focus-shadow-rgb: 13, 202, 240;
+ --bs-btn-active-bg: #0dcaf0;
+ --bs-btn-active-border-color: #0dcaf0;
+ --bs-btn-disabled-color: #0dcaf0;
+.btn-outline-warning {
+ --bs-btn-color: #ffc107;
+ --bs-btn-hover-bg: #ffc107;
+ --bs-btn-hover-border-color: #ffc107;
+ --bs-btn-focus-shadow-rgb: 255, 193, 7;
+ --bs-btn-active-bg: #ffc107;
+ --bs-btn-active-border-color: #ffc107;
+ --bs-btn-disabled-color: #ffc107;
+.btn-outline-danger {
+ --bs-btn-color: #dc3545;
+ --bs-btn-hover-bg: #dc3545;
+ --bs-btn-hover-border-color: #dc3545;
+ --bs-btn-focus-shadow-rgb: 220, 53, 69;
+ --bs-btn-active-bg: #dc3545;
+ --bs-btn-active-border-color: #dc3545;
+ --bs-btn-disabled-color: #dc3545;
+.btn-outline-light {
+ --bs-btn-color: #f8f9fa;
+ --bs-btn-hover-bg: #f8f9fa;
+ --bs-btn-hover-border-color: #f8f9fa;
+ --bs-btn-focus-shadow-rgb: 248, 249, 250;
+ --bs-btn-active-bg: #f8f9fa;
+ --bs-btn-active-border-color: #f8f9fa;
+ --bs-btn-disabled-color: #f8f9fa;
+.btn-outline-dark {
+ --bs-btn-hover-bg: #212529;
+ --bs-btn-hover-border-color: #212529;
+ --bs-btn-focus-shadow-rgb: 33, 37, 41;
+ --bs-btn-active-bg: #212529;
+ --bs-btn-active-border-color: #212529;
+ --bs-btn-disabled-color: #212529;
+.btn-link {
+ --bs-btn-color: var(--bs-link-color);
+ --bs-btn-hover-color: var(--bs-link-hover-color);
+ --bs-btn-active-color: var(--bs-link-hover-color);
+ --bs-btn-active-border-color: transparent;
+ --bs-btn-disabled-border-color: transparent;
+ --bs-btn-box-shadow: none;
+.btn-link:focus-visible {
+.btn-link:hover {
+.btn-lg, .btn-group-lg > .btn {
+ --bs-btn-padding-y: 0.5rem;
+ --bs-btn-padding-x: 1rem;
+ --bs-btn-font-size: 1.25rem;
+ --bs-btn-border-radius: 0.5rem;
+.btn-sm, .btn-group-sm > .btn {
+ --bs-btn-padding-y: 0.25rem;
+ --bs-btn-padding-x: 0.5rem;
+ --bs-btn-font-size: 0.875rem;
+ --bs-btn-border-radius: 0.25rem;
+.fade {
+ transition: opacity 0.15s linear;
+ .fade {
+.fade:not(.show) {
+ opacity: 0;
+.collapse:not(.show) {
+.collapsing {
+ height: 0;
+ transition: height 0.35s ease;
+ .collapsing {
+.collapsing.collapse-horizontal {
+ width: 0;
+ transition: width 0.35s ease;
+ .collapsing.collapse-horizontal {
+.dropup,
+.dropend,
+.dropdown,
+.dropstart,
+.dropup-center,
+.dropdown-center {
+.dropdown-toggle {
+.dropdown-toggle::after {
+ margin-left: 0.255em;
+ vertical-align: 0.255em;
+ border-top: 0.3em solid;
+ border-right: 0.3em solid transparent;
+ border-bottom: 0;
+ border-left: 0.3em solid transparent;
+.dropdown-toggle:empty::after {
+.dropdown-menu {
+ --bs-dropdown-zindex: 1000;
+ --bs-dropdown-min-width: 10rem;
+ --bs-dropdown-padding-x: 0;
+ --bs-dropdown-padding-y: 0.5rem;
+ --bs-dropdown-spacer: 0.125rem;
+ --bs-dropdown-font-size: 1rem;
+ --bs-dropdown-color: var(--bs-body-color);
+ --bs-dropdown-bg: var(--bs-body-bg);
+ --bs-dropdown-border-color: var(--bs-border-color-translucent);
+ --bs-dropdown-border-radius: 0.375rem;
+ --bs-dropdown-border-width: var(--bs-border-width);
+ --bs-dropdown-inner-border-radius: calc(0.375rem - var(--bs-border-width));
+ --bs-dropdown-divider-bg: var(--bs-border-color-translucent);
+ --bs-dropdown-divider-margin-y: 0.5rem;
+ --bs-dropdown-box-shadow: 0 0.5rem 1rem rgba(var(--bs-body-color-rgb), 0.15);
+ --bs-dropdown-link-color: var(--bs-body-color);
+ --bs-dropdown-link-hover-color: var(--bs-body-color);
+ --bs-dropdown-link-hover-bg: var(--bs-tertiary-bg);
+ --bs-dropdown-link-active-color: #fff;
+ --bs-dropdown-link-active-bg: #0d6efd;
+ --bs-dropdown-link-disabled-color: #adb5bd;
+ --bs-dropdown-item-padding-x: 1rem;
+ --bs-dropdown-item-padding-y: 0.25rem;
+ --bs-dropdown-header-color: #6c757d;
+ --bs-dropdown-header-padding-x: 1rem;
+ --bs-dropdown-header-padding-y: 0.5rem;
+ z-index: var(--bs-dropdown-zindex);
+ min-width: var(--bs-dropdown-min-width);
+ padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x);
+ font-size: var(--bs-dropdown-font-size);
+ color: var(--bs-dropdown-color);
+ background-color: var(--bs-dropdown-bg);
+ border: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);
+ border-radius: var(--bs-dropdown-border-radius);
+.dropdown-menu[data-bs-popper] {
+ margin-top: var(--bs-dropdown-spacer);
+.dropdown-menu-start {
+ --bs-position: start;
+.dropdown-menu-start[data-bs-popper] {
+ right: auto;
+.dropdown-menu-end {
+ --bs-position: end;
+.dropdown-menu-end[data-bs-popper] {
+ right: 0;
+ left: auto;
+ .dropdown-menu-sm-start {
+ .dropdown-menu-sm-start[data-bs-popper] {
+ .dropdown-menu-sm-end {
+ .dropdown-menu-sm-end[data-bs-popper] {
+ .dropdown-menu-md-start {
+ .dropdown-menu-md-start[data-bs-popper] {
+ .dropdown-menu-md-end {
+ .dropdown-menu-md-end[data-bs-popper] {
+ .dropdown-menu-lg-start {
+ .dropdown-menu-lg-start[data-bs-popper] {
+ .dropdown-menu-lg-end {
+ .dropdown-menu-lg-end[data-bs-popper] {
+ .dropdown-menu-xl-start {
+ .dropdown-menu-xl-start[data-bs-popper] {
+ .dropdown-menu-xl-end {
+ .dropdown-menu-xl-end[data-bs-popper] {
+ .dropdown-menu-xxl-start {
+ .dropdown-menu-xxl-start[data-bs-popper] {
+ .dropdown-menu-xxl-end {
+ .dropdown-menu-xxl-end[data-bs-popper] {
+.dropup .dropdown-menu[data-bs-popper] {
+ top: auto;
+ bottom: 100%;
+ margin-bottom: var(--bs-dropdown-spacer);
+.dropup .dropdown-toggle::after {
+ border-top: 0;
+ border-bottom: 0.3em solid;
+.dropup .dropdown-toggle:empty::after {
+.dropend .dropdown-menu[data-bs-popper] {
+ left: 100%;
+ margin-left: var(--bs-dropdown-spacer);
+.dropend .dropdown-toggle::after {
+ border-top: 0.3em solid transparent;
+ border-right: 0;
+ border-bottom: 0.3em solid transparent;
+ border-left: 0.3em solid;
+.dropend .dropdown-toggle:empty::after {
+ vertical-align: 0;
+.dropstart .dropdown-menu[data-bs-popper] {
+ right: 100%;
+ margin-right: var(--bs-dropdown-spacer);
+.dropstart .dropdown-toggle::after {
+.dropstart .dropdown-toggle::before {
+ margin-right: 0.255em;
+ border-right: 0.3em solid;
+.dropstart .dropdown-toggle:empty::after {
+.dropdown-divider {
+ margin: var(--bs-dropdown-divider-margin-y) 0;
+ border-top: 1px solid var(--bs-dropdown-divider-bg);
+.dropdown-item {
+ padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x);
+ clear: both;
+ color: var(--bs-dropdown-link-color);
+ border-radius: var(--bs-dropdown-item-border-radius, 0);
+.dropdown-item:hover, .dropdown-item:focus {
+ color: var(--bs-dropdown-link-hover-color);
+ background-color: var(--bs-dropdown-link-hover-bg);
+.dropdown-item.active, .dropdown-item:active {
+ color: var(--bs-dropdown-link-active-color);
+ background-color: var(--bs-dropdown-link-active-bg);
+.dropdown-item.disabled, .dropdown-item:disabled {
+ color: var(--bs-dropdown-link-disabled-color);
+.dropdown-menu.show {
+.dropdown-header {
+ padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x);
+ color: var(--bs-dropdown-header-color);
+.dropdown-item-text {
+.dropdown-menu-dark {
+ --bs-dropdown-color: #dee2e6;
+ --bs-dropdown-bg: #343a40;
+ --bs-dropdown-box-shadow: ;
+ --bs-dropdown-link-color: #dee2e6;
+ --bs-dropdown-link-hover-color: #fff;
+ --bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15);
+ --bs-dropdown-header-color: #adb5bd;
+.btn-group,
+.btn-group-vertical {
+ display: inline-flex;
+.btn-group > .btn,
+.btn-group-vertical > .btn {
+.btn-group > .btn-check:checked + .btn,
+.btn-group > .btn-check:focus + .btn,
+.btn-group > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group > .btn:active,
+.btn-group > .btn.active,
+.btn-group-vertical > .btn-check:checked + .btn,
+.btn-group-vertical > .btn-check:focus + .btn,
+.btn-group-vertical > .btn:hover,
+.btn-group-vertical > .btn:focus,
+.btn-group-vertical > .btn:active,
+.btn-group-vertical > .btn.active {
+ z-index: 1;
+.btn-toolbar {
+ justify-content: flex-start;
+.btn-toolbar .input-group {
+.btn-group {
+.btn-group > :not(.btn-check:first-child) + .btn,
+.btn-group > .btn-group:not(:first-child) {
+.btn-group > .btn:not(:last-child):not(.dropdown-toggle),
+.btn-group > .btn.dropdown-toggle-split:first-child,
+.btn-group > .btn-group:not(:last-child) > .btn {
+.btn-group > .btn:nth-child(n+3),
+.btn-group > :not(.btn-check) + .btn,
+.btn-group > .btn-group:not(:first-child) > .btn {
+.dropdown-toggle-split {
+ padding-right: 0.5625rem;
+ padding-left: 0.5625rem;
+.dropdown-toggle-split::after, .dropup .dropdown-toggle-split::after, .dropend .dropdown-toggle-split::after {
+.dropstart .dropdown-toggle-split::before {
+ margin-right: 0;
+.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {
+ padding-right: 0.375rem;
+ padding-left: 0.375rem;
+.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {
+ padding-left: 0.75rem;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: center;
+.btn-group-vertical > .btn,
+.btn-group-vertical > .btn-group {
+.btn-group-vertical > .btn:not(:first-child),
+.btn-group-vertical > .btn-group:not(:first-child) {
+ margin-top: calc(var(--bs-border-width) * -1);
+.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),
+.btn-group-vertical > .btn-group:not(:last-child) > .btn {
+.btn-group-vertical > .btn ~ .btn,
+.btn-group-vertical > .btn-group:not(:first-child) > .btn {
+.nav {
+ --bs-nav-link-padding-x: 1rem;
+ --bs-nav-link-padding-y: 0.5rem;
+ --bs-nav-link-font-weight: ;
+ --bs-nav-link-color: var(--bs-link-color);
+ --bs-nav-link-hover-color: var(--bs-link-hover-color);
+ --bs-nav-link-disabled-color: var(--bs-secondary-color);
+.nav-link {
+ padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x);
+ font-size: var(--bs-nav-link-font-size);
+ font-weight: var(--bs-nav-link-font-weight);
+ color: var(--bs-nav-link-color);
+ transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
+ .nav-link {
+.nav-link:hover, .nav-link:focus {
+ color: var(--bs-nav-link-hover-color);
+.nav-link.disabled {
+ color: var(--bs-nav-link-disabled-color);
+.nav-tabs {
+ --bs-nav-tabs-border-width: var(--bs-border-width);
+ --bs-nav-tabs-border-color: var(--bs-border-color);
+ --bs-nav-tabs-border-radius: var(--bs-border-radius);
+ --bs-nav-tabs-link-hover-border-color: var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color);
+ --bs-nav-tabs-link-active-color: var(--bs-emphasis-color);
+ --bs-nav-tabs-link-active-bg: var(--bs-body-bg);
+ --bs-nav-tabs-link-active-border-color: var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg);
+ border-bottom: var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color);
+.nav-tabs .nav-link {
+ margin-bottom: calc(-1 * var(--bs-nav-tabs-border-width));
+ background: none;
+ border: var(--bs-nav-tabs-border-width) solid transparent;
+ border-top-left-radius: var(--bs-nav-tabs-border-radius);
+ border-top-right-radius: var(--bs-nav-tabs-border-radius);
+.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {
+ isolation: isolate;
+ border-color: var(--bs-nav-tabs-link-hover-border-color);
+.nav-tabs .nav-link.disabled, .nav-tabs .nav-link:disabled {
+.nav-tabs .nav-link.active,
+.nav-tabs .nav-item.show .nav-link {
+ color: var(--bs-nav-tabs-link-active-color);
+ background-color: var(--bs-nav-tabs-link-active-bg);
+ border-color: var(--bs-nav-tabs-link-active-border-color);
+.nav-tabs .dropdown-menu {
+ margin-top: calc(-1 * var(--bs-nav-tabs-border-width));
+.nav-pills {
+ --bs-nav-pills-border-radius: 0.375rem;
+ --bs-nav-pills-link-active-color: #fff;
+ --bs-nav-pills-link-active-bg: #0d6efd;
+.nav-pills .nav-link {
+ border-radius: var(--bs-nav-pills-border-radius);
+.nav-pills .nav-link:disabled {
+.nav-pills .nav-link.active,
+.nav-pills .show > .nav-link {
+ color: var(--bs-nav-pills-link-active-color);
+ background-color: var(--bs-nav-pills-link-active-bg);
+.nav-fill > .nav-link,
+.nav-fill .nav-item {
+.nav-justified > .nav-link,
+.nav-justified .nav-item {
+ flex-basis: 0;
+ flex-grow: 1;
+.nav-fill .nav-item .nav-link,
+.nav-justified .nav-item .nav-link {
+.tab-content > .tab-pane {
+.tab-content > .active {
+.navbar {
+ --bs-navbar-padding-x: 0;
+ --bs-navbar-padding-y: 0.5rem;
+ --bs-navbar-color: rgba(var(--bs-emphasis-color-rgb), 0.65);
+ --bs-navbar-hover-color: rgba(var(--bs-emphasis-color-rgb), 0.8);
+ --bs-navbar-disabled-color: rgba(var(--bs-emphasis-color-rgb), 0.3);
+ --bs-navbar-active-color: rgba(var(--bs-emphasis-color-rgb), 1);
+ --bs-navbar-brand-padding-y: 0.3125rem;
+ --bs-navbar-brand-margin-end: 1rem;
+ --bs-navbar-brand-font-size: 1.25rem;
+ --bs-navbar-brand-color: rgba(var(--bs-emphasis-color-rgb), 1);
+ --bs-navbar-brand-hover-color: rgba(var(--bs-emphasis-color-rgb), 1);
+ --bs-navbar-nav-link-padding-x: 0.5rem;
+ --bs-navbar-toggler-padding-y: 0.25rem;
+ --bs-navbar-toggler-padding-x: 0.75rem;
+ --bs-navbar-toggler-font-size: 1.25rem;
+ --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
+ --bs-navbar-toggler-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15);
+ --bs-navbar-toggler-border-radius: 0.375rem;
+ --bs-navbar-toggler-focus-width: 0.25rem;
+ --bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out;
+ justify-content: space-between;
+ padding: var(--bs-navbar-padding-y) var(--bs-navbar-padding-x);
+.navbar > .container,
+.navbar > .container-fluid,
+.navbar > .container-sm,
+.navbar > .container-md,
+.navbar > .container-lg,
+.navbar > .container-xl,
+.navbar > .container-xxl {
+ flex-wrap: inherit;
+.navbar-brand {
+ padding-top: var(--bs-navbar-brand-padding-y);
+ padding-bottom: var(--bs-navbar-brand-padding-y);
+ margin-right: var(--bs-navbar-brand-margin-end);
+ font-size: var(--bs-navbar-brand-font-size);
+ color: var(--bs-navbar-brand-color);
+.navbar-brand:hover, .navbar-brand:focus {
+ color: var(--bs-navbar-brand-hover-color);
+.navbar-nav {
+ --bs-nav-link-padding-x: 0;
+ --bs-nav-link-color: var(--bs-navbar-color);
+ --bs-nav-link-hover-color: var(--bs-navbar-hover-color);
+ --bs-nav-link-disabled-color: var(--bs-navbar-disabled-color);
+.navbar-nav .show > .nav-link,
+.navbar-nav .nav-link.active {
+ color: var(--bs-navbar-active-color);
+.navbar-nav .dropdown-menu {
+ position: static;
+.navbar-text {
+ color: var(--bs-navbar-color);
+.navbar-text a,
+.navbar-text a:hover,
+.navbar-text a:focus {
+.navbar-collapse {
+ flex-basis: 100%;
+.navbar-toggler {
+ padding: var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x);
+ font-size: var(--bs-navbar-toggler-font-size);
+ border: var(--bs-border-width) solid var(--bs-navbar-toggler-border-color);
+ border-radius: var(--bs-navbar-toggler-border-radius);
+ transition: var(--bs-navbar-toggler-transition);
+ .navbar-toggler {
+.navbar-toggler:hover {
+.navbar-toggler:focus {
+ box-shadow: 0 0 0 var(--bs-navbar-toggler-focus-width);
+.navbar-toggler-icon {
+ width: 1.5em;
+ background-image: var(--bs-navbar-toggler-icon-bg);
+ background-size: 100%;
+.navbar-nav-scroll {
+ max-height: var(--bs-scroll-height, 75vh);
+ overflow-y: auto;
+ .navbar-expand-sm {
+ flex-wrap: nowrap;
+ .navbar-expand-sm .navbar-nav {
+ flex-direction: row;
+ .navbar-expand-sm .navbar-nav .dropdown-menu {
+ .navbar-expand-sm .navbar-nav .nav-link {
+ padding-right: var(--bs-navbar-nav-link-padding-x);
+ padding-left: var(--bs-navbar-nav-link-padding-x);
+ .navbar-expand-sm .navbar-nav-scroll {
+ overflow: visible;
+ .navbar-expand-sm .navbar-collapse {
+ display: flex !important;
+ flex-basis: auto;
+ .navbar-expand-sm .navbar-toggler {
+ .navbar-expand-sm .offcanvas {
+ z-index: auto;
+ width: auto !important;
+ height: auto !important;
+ visibility: visible !important;
+ background-color: transparent !important;
+ transform: none !important;
+ .navbar-expand-sm .offcanvas .offcanvas-header {
+ .navbar-expand-sm .offcanvas .offcanvas-body {
+ flex-grow: 0;
+ overflow-y: visible;
+ .navbar-expand-md {
+ .navbar-expand-md .navbar-nav {
+ .navbar-expand-md .navbar-nav .dropdown-menu {
+ .navbar-expand-md .navbar-nav .nav-link {
+ .navbar-expand-md .navbar-nav-scroll {
+ .navbar-expand-md .navbar-collapse {
+ .navbar-expand-md .navbar-toggler {
+ .navbar-expand-md .offcanvas {
+ .navbar-expand-md .offcanvas .offcanvas-header {
+ .navbar-expand-md .offcanvas .offcanvas-body {
+ .navbar-expand-lg {
+ .navbar-expand-lg .navbar-nav {
+ .navbar-expand-lg .navbar-nav .dropdown-menu {
+ .navbar-expand-lg .navbar-nav .nav-link {
+ .navbar-expand-lg .navbar-nav-scroll {
+ .navbar-expand-lg .navbar-collapse {
+ .navbar-expand-lg .navbar-toggler {
+ .navbar-expand-lg .offcanvas {
+ .navbar-expand-lg .offcanvas .offcanvas-header {
+ .navbar-expand-lg .offcanvas .offcanvas-body {
+ .navbar-expand-xl {
+ .navbar-expand-xl .navbar-nav {
+ .navbar-expand-xl .navbar-nav .dropdown-menu {
+ .navbar-expand-xl .navbar-nav .nav-link {
+ .navbar-expand-xl .navbar-nav-scroll {
+ .navbar-expand-xl .navbar-collapse {
+ .navbar-expand-xl .navbar-toggler {
+ .navbar-expand-xl .offcanvas {
+ .navbar-expand-xl .offcanvas .offcanvas-header {
+ .navbar-expand-xl .offcanvas .offcanvas-body {
+ .navbar-expand-xxl {
+ .navbar-expand-xxl .navbar-nav {
+ .navbar-expand-xxl .navbar-nav .dropdown-menu {
+ .navbar-expand-xxl .navbar-nav .nav-link {
+ .navbar-expand-xxl .navbar-nav-scroll {
+ .navbar-expand-xxl .navbar-collapse {
+ .navbar-expand-xxl .navbar-toggler {
+ .navbar-expand-xxl .offcanvas {
+ .navbar-expand-xxl .offcanvas .offcanvas-header {
+ .navbar-expand-xxl .offcanvas .offcanvas-body {
+.navbar-expand {
+.navbar-expand .navbar-nav {
+.navbar-expand .navbar-nav .dropdown-menu {
+.navbar-expand .navbar-nav .nav-link {
+.navbar-expand .navbar-nav-scroll {
+.navbar-expand .navbar-collapse {
+.navbar-expand .navbar-toggler {
+.navbar-expand .offcanvas {
+.navbar-expand .offcanvas .offcanvas-header {
+.navbar-expand .offcanvas .offcanvas-body {
+.navbar-dark {
+ --bs-navbar-color: rgba(255, 255, 255, 0.55);
+ --bs-navbar-hover-color: rgba(255, 255, 255, 0.75);
+ --bs-navbar-disabled-color: rgba(255, 255, 255, 0.25);
+ --bs-navbar-active-color: #fff;
+ --bs-navbar-brand-color: #fff;
+ --bs-navbar-brand-hover-color: #fff;
+ --bs-navbar-toggler-border-color: rgba(255, 255, 255, 0.1);
+ --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
+[data-bs-theme=dark] .navbar {
+.card {
+ --bs-card-spacer-y: 1rem;
+ --bs-card-spacer-x: 1rem;
+ --bs-card-title-spacer-y: 0.5rem;
+ --bs-card-title-color: ;
+ --bs-card-subtitle-color: ;
+ --bs-card-border-width: var(--bs-border-width);
+ --bs-card-border-color: var(--bs-border-color-translucent);
+ --bs-card-border-radius: var(--bs-border-radius);
+ --bs-card-box-shadow: ;
+ --bs-card-inner-border-radius: calc(var(--bs-border-radius) - (var(--bs-border-width)));
+ --bs-card-cap-padding-y: 0.5rem;
+ --bs-card-cap-padding-x: 1rem;
+ --bs-card-cap-bg: rgba(var(--bs-body-color-rgb), 0.03);
+ --bs-card-cap-color: ;
+ --bs-card-height: ;
+ --bs-card-color: ;
+ --bs-card-bg: var(--bs-body-bg);
+ --bs-card-img-overlay-padding: 1rem;
+ --bs-card-group-margin: 0.75rem;
+ height: var(--bs-card-height);
+ background-color: var(--bs-card-bg);
+ background-clip: border-box;
+ border: var(--bs-card-border-width) solid var(--bs-card-border-color);
+ border-radius: var(--bs-card-border-radius);
+.card > hr {
+.card > .list-group {
+ border-top: inherit;
+ border-bottom: inherit;
+.card > .list-group:first-child {
+ border-top-left-radius: var(--bs-card-inner-border-radius);
+ border-top-right-radius: var(--bs-card-inner-border-radius);
+.card > .list-group:last-child {
+ border-bottom-right-radius: var(--bs-card-inner-border-radius);
+ border-bottom-left-radius: var(--bs-card-inner-border-radius);
+.card > .card-header + .list-group,
+.card > .list-group + .card-footer {
+.card-body {
+ padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x);
+ color: var(--bs-card-color);
+.card-title {
+ margin-bottom: var(--bs-card-title-spacer-y);
+ color: var(--bs-card-title-color);
+.card-subtitle {
+ margin-top: calc(-0.5 * var(--bs-card-title-spacer-y));
+ color: var(--bs-card-subtitle-color);
+.card-text:last-child {
+.card-link + .card-link {
+ margin-left: var(--bs-card-spacer-x);
+.card-header {
+ padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x);
+ color: var(--bs-card-cap-color);
+ background-color: var(--bs-card-cap-bg);
+ border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color);
+.card-header:first-child {
+ border-radius: var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0;
+.card-footer {
+ border-top: var(--bs-card-border-width) solid var(--bs-card-border-color);
+.card-footer:last-child {
+ border-radius: 0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius);
+.card-header-tabs {
+ margin-right: calc(-0.5 * var(--bs-card-cap-padding-x));
+ margin-bottom: calc(-1 * var(--bs-card-cap-padding-y));
+ margin-left: calc(-0.5 * var(--bs-card-cap-padding-x));
+.card-header-tabs .nav-link.active {
+ border-bottom-color: var(--bs-card-bg);
+.card-header-pills {
+.card-img-overlay {
+ bottom: 0;
+ padding: var(--bs-card-img-overlay-padding);
+ border-radius: var(--bs-card-inner-border-radius);
+.card-img,
+.card-img-top,
+.card-img-bottom {
+.card-img-top {
+.card-group > .card {
+ margin-bottom: var(--bs-card-group-margin);
+ .card-group {
+ flex-flow: row wrap;
+ .card-group > .card {
+ .card-group > .card + .card {
+ border-left: 0;
+ .card-group > .card:not(:last-child) {
+ .card-group > .card:not(:last-child) .card-img-top,
+ .card-group > .card:not(:last-child) .card-header {
+ .card-group > .card:not(:last-child) .card-img-bottom,
+ .card-group > .card:not(:last-child) .card-footer {
+ .card-group > .card:not(:first-child) {
+ .card-group > .card:not(:first-child) .card-img-top,
+ .card-group > .card:not(:first-child) .card-header {
+ .card-group > .card:not(:first-child) .card-img-bottom,
+ .card-group > .card:not(:first-child) .card-footer {
+.accordion {
+ --bs-accordion-color: var(--bs-body-color);
+ --bs-accordion-bg: var(--bs-body-bg);
+ --bs-accordion-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease;
+ --bs-accordion-border-color: var(--bs-border-color);
+ --bs-accordion-border-width: var(--bs-border-width);
+ --bs-accordion-border-radius: var(--bs-border-radius);
+ --bs-accordion-inner-border-radius: calc(var(--bs-border-radius) - (var(--bs-border-width)));
+ --bs-accordion-btn-padding-x: 1.25rem;
+ --bs-accordion-btn-padding-y: 1rem;
+ --bs-accordion-btn-color: var(--bs-body-color);
+ --bs-accordion-btn-bg: var(--bs-accordion-bg);
+ --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
+ --bs-accordion-btn-icon-width: 1.25rem;
+ --bs-accordion-btn-icon-transform: rotate(-180deg);
+ --bs-accordion-btn-icon-transition: transform 0.2s ease-in-out;
+ --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230a58ca'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
+ --bs-accordion-btn-focus-border-color: #86b7fe;
+ --bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+ --bs-accordion-body-padding-x: 1.25rem;
+ --bs-accordion-body-padding-y: 1rem;
+ --bs-accordion-active-color: var(--bs-primary-text);
+ --bs-accordion-active-bg: var(--bs-primary-bg-subtle);
+.accordion-button {
+ padding: var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x);
+ color: var(--bs-accordion-btn-color);
+ background-color: var(--bs-accordion-btn-bg);
+ overflow-anchor: none;
+ transition: var(--bs-accordion-transition);
+ .accordion-button {
+.accordion-button:not(.collapsed) {
+ color: var(--bs-accordion-active-color);
+ background-color: var(--bs-accordion-active-bg);
+ box-shadow: inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color);
+.accordion-button:not(.collapsed)::after {
+ background-image: var(--bs-accordion-btn-active-icon);
+ transform: var(--bs-accordion-btn-icon-transform);
+.accordion-button::after {
+ width: var(--bs-accordion-btn-icon-width);
+ height: var(--bs-accordion-btn-icon-width);
+ background-image: var(--bs-accordion-btn-icon);
+ background-size: var(--bs-accordion-btn-icon-width);
+ transition: var(--bs-accordion-btn-icon-transition);
+ .accordion-button::after {
+.accordion-button:hover {
+.accordion-button:focus {
+ border-color: var(--bs-accordion-btn-focus-border-color);
+ box-shadow: var(--bs-accordion-btn-focus-box-shadow);
+.accordion-header {
+.accordion-item {
+ color: var(--bs-accordion-color);
+ background-color: var(--bs-accordion-bg);
+ border: var(--bs-accordion-border-width) solid var(--bs-accordion-border-color);
+.accordion-item:first-of-type {
+ border-top-left-radius: var(--bs-accordion-border-radius);
+ border-top-right-radius: var(--bs-accordion-border-radius);
+.accordion-item:first-of-type .accordion-button {
+ border-top-left-radius: var(--bs-accordion-inner-border-radius);
+ border-top-right-radius: var(--bs-accordion-inner-border-radius);
+.accordion-item:not(:first-of-type) {
+.accordion-item:last-of-type {
+ border-bottom-right-radius: var(--bs-accordion-border-radius);
+ border-bottom-left-radius: var(--bs-accordion-border-radius);
+.accordion-item:last-of-type .accordion-button.collapsed {
+ border-bottom-right-radius: var(--bs-accordion-inner-border-radius);
+ border-bottom-left-radius: var(--bs-accordion-inner-border-radius);
+.accordion-item:last-of-type .accordion-collapse {
+.accordion-body {
+ padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x);
+.accordion-flush .accordion-collapse {
+.accordion-flush .accordion-item {
+.accordion-flush .accordion-item:first-child {
+.accordion-flush .accordion-item:last-child {
+.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed {
+[data-bs-theme=dark] .accordion-button::after {
+ --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
+ --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%236ea8fe'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
+.breadcrumb {
+ --bs-breadcrumb-padding-x: 0;
+ --bs-breadcrumb-padding-y: 0;
+ --bs-breadcrumb-margin-bottom: 1rem;
+ --bs-breadcrumb-bg: ;
+ --bs-breadcrumb-border-radius: ;
+ --bs-breadcrumb-divider-color: var(--bs-secondary-color);
+ --bs-breadcrumb-item-padding-x: 0.5rem;
+ --bs-breadcrumb-item-active-color: var(--bs-secondary-color);
+ padding: var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x);
+ margin-bottom: var(--bs-breadcrumb-margin-bottom);
+ font-size: var(--bs-breadcrumb-font-size);
+ background-color: var(--bs-breadcrumb-bg);
+ border-radius: var(--bs-breadcrumb-border-radius);
+.breadcrumb-item + .breadcrumb-item {
+ padding-left: var(--bs-breadcrumb-item-padding-x);
+.breadcrumb-item + .breadcrumb-item::before {
+ padding-right: var(--bs-breadcrumb-item-padding-x);
+ color: var(--bs-breadcrumb-divider-color);
+ content: var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */;
+.breadcrumb-item.active {
+ color: var(--bs-breadcrumb-item-active-color);
+.pagination {
+ --bs-pagination-padding-x: 0.75rem;
+ --bs-pagination-padding-y: 0.375rem;
+ --bs-pagination-font-size: 1rem;
+ --bs-pagination-color: var(--bs-link-color);
+ --bs-pagination-bg: var(--bs-body-bg);
+ --bs-pagination-border-width: var(--bs-border-width);
+ --bs-pagination-border-color: var(--bs-border-color);
+ --bs-pagination-border-radius: var(--bs-border-radius);
+ --bs-pagination-hover-color: var(--bs-link-hover-color);
+ --bs-pagination-hover-bg: var(--bs-tertiary-bg);
+ --bs-pagination-hover-border-color: var(--bs-border-color);
+ --bs-pagination-focus-color: var(--bs-link-hover-color);
+ --bs-pagination-focus-bg: var(--bs-secondary-bg);
+ --bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+ --bs-pagination-active-color: #fff;
+ --bs-pagination-active-bg: #0d6efd;
+ --bs-pagination-active-border-color: #0d6efd;
+ --bs-pagination-disabled-color: var(--bs-secondary-color);
+ --bs-pagination-disabled-bg: var(--bs-secondary-bg);
+ --bs-pagination-disabled-border-color: var(--bs-border-color);
+.page-link {
+ padding: var(--bs-pagination-padding-y) var(--bs-pagination-padding-x);
+ font-size: var(--bs-pagination-font-size);
+ color: var(--bs-pagination-color);
+ background-color: var(--bs-pagination-bg);
+ border: var(--bs-pagination-border-width) solid var(--bs-pagination-border-color);
+ .page-link {
+.page-link:hover {
+ color: var(--bs-pagination-hover-color);
+ background-color: var(--bs-pagination-hover-bg);
+ border-color: var(--bs-pagination-hover-border-color);
+.page-link:focus {
+ color: var(--bs-pagination-focus-color);
+ background-color: var(--bs-pagination-focus-bg);
+ box-shadow: var(--bs-pagination-focus-box-shadow);
+.page-link.active, .active > .page-link {
+ color: var(--bs-pagination-active-color);
+ background-color: var(--bs-pagination-active-bg);
+ border-color: var(--bs-pagination-active-border-color);
+.page-link.disabled, .disabled > .page-link {
+ color: var(--bs-pagination-disabled-color);
+ background-color: var(--bs-pagination-disabled-bg);
+ border-color: var(--bs-pagination-disabled-border-color);
+.page-item:not(:first-child) .page-link {
+.page-item:first-child .page-link {
+ border-top-left-radius: var(--bs-pagination-border-radius);
+ border-bottom-left-radius: var(--bs-pagination-border-radius);
+.page-item:last-child .page-link {
+ border-top-right-radius: var(--bs-pagination-border-radius);
+ border-bottom-right-radius: var(--bs-pagination-border-radius);
+.pagination-lg {
+ --bs-pagination-padding-x: 1.5rem;
+ --bs-pagination-padding-y: 0.75rem;
+ --bs-pagination-font-size: 1.25rem;
+ --bs-pagination-border-radius: 0.5rem;
+.pagination-sm {
+ --bs-pagination-padding-x: 0.5rem;
+ --bs-pagination-padding-y: 0.25rem;
+ --bs-pagination-font-size: 0.875rem;
+ --bs-pagination-border-radius: 0.25rem;
+.badge {
+ --bs-badge-padding-x: 0.65em;
+ --bs-badge-padding-y: 0.35em;
+ --bs-badge-font-size: 0.75em;
+ --bs-badge-font-weight: 700;
+ --bs-badge-color: #fff;
+ --bs-badge-border-radius: 0.375rem;
+ padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x);
+ font-size: var(--bs-badge-font-size);
+ font-weight: var(--bs-badge-font-weight);
+ color: var(--bs-badge-color);
+ border-radius: var(--bs-badge-border-radius);
+.badge:empty {
+.btn .badge {
+ top: -1px;
+.alert {
+ --bs-alert-bg: transparent;
+ --bs-alert-padding-x: 1rem;
+ --bs-alert-padding-y: 1rem;
+ --bs-alert-margin-bottom: 1rem;
+ --bs-alert-color: inherit;
+ --bs-alert-border-color: transparent;
+ --bs-alert-border: var(--bs-border-width) solid var(--bs-alert-border-color);
+ --bs-alert-border-radius: 0.375rem;
+ --bs-alert-link-color: inherit;
+ padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x);
+ margin-bottom: var(--bs-alert-margin-bottom);
+ color: var(--bs-alert-color);
+ background-color: var(--bs-alert-bg);
+ border: var(--bs-alert-border);
+ border-radius: var(--bs-alert-border-radius);
+.alert-heading {
+.alert-link {
+ color: var(--bs-alert-link-color);
+.alert-dismissible {
+.alert-dismissible .btn-close {
+ padding: 1.25rem 1rem;
+.alert-primary {
+ --bs-alert-color: var(--bs-primary-text);
+ --bs-alert-bg: var(--bs-primary-bg-subtle);
+ --bs-alert-border-color: var(--bs-primary-border-subtle);
+ --bs-alert-link-color: var(--bs-primary-text);
+.alert-secondary {
+ --bs-alert-color: var(--bs-secondary-text);
+ --bs-alert-bg: var(--bs-secondary-bg-subtle);
+ --bs-alert-border-color: var(--bs-secondary-border-subtle);
+ --bs-alert-link-color: var(--bs-secondary-text);
+.alert-success {
+ --bs-alert-color: var(--bs-success-text);
+ --bs-alert-bg: var(--bs-success-bg-subtle);
+ --bs-alert-border-color: var(--bs-success-border-subtle);
+ --bs-alert-link-color: var(--bs-success-text);
+.alert-info {
+ --bs-alert-color: var(--bs-info-text);
+ --bs-alert-bg: var(--bs-info-bg-subtle);
+ --bs-alert-border-color: var(--bs-info-border-subtle);
+ --bs-alert-link-color: var(--bs-info-text);
+.alert-warning {
+ --bs-alert-color: var(--bs-warning-text);
+ --bs-alert-bg: var(--bs-warning-bg-subtle);
+ --bs-alert-border-color: var(--bs-warning-border-subtle);
+ --bs-alert-link-color: var(--bs-warning-text);
+.alert-danger {
+ --bs-alert-color: var(--bs-danger-text);
+ --bs-alert-bg: var(--bs-danger-bg-subtle);
+ --bs-alert-border-color: var(--bs-danger-border-subtle);
+ --bs-alert-link-color: var(--bs-danger-text);
+.alert-light {
+ --bs-alert-color: var(--bs-light-text);
+ --bs-alert-bg: var(--bs-light-bg-subtle);
+ --bs-alert-border-color: var(--bs-light-border-subtle);
+ --bs-alert-link-color: var(--bs-light-text);
+.alert-dark {
+ --bs-alert-color: var(--bs-dark-text);
+ --bs-alert-bg: var(--bs-dark-bg-subtle);
+ --bs-alert-border-color: var(--bs-dark-border-subtle);
+ --bs-alert-link-color: var(--bs-dark-text);
+@keyframes progress-bar-stripes {
+ 0% {
+ background-position-x: 1rem;
+.progress,
+.progress-stacked {
+ --bs-progress-height: 1rem;
+ --bs-progress-font-size: 0.75rem;
+ --bs-progress-bg: var(--bs-secondary-bg);
+ --bs-progress-border-radius: var(--bs-border-radius);
+ --bs-progress-box-shadow: var(--bs-box-shadow-inset);
+ --bs-progress-bar-color: #fff;
+ --bs-progress-bar-bg: #0d6efd;
+ --bs-progress-bar-transition: width 0.6s ease;
+ height: var(--bs-progress-height);
+ font-size: var(--bs-progress-font-size);
+ background-color: var(--bs-progress-bg);
+ border-radius: var(--bs-progress-border-radius);
+.progress-bar {
+ color: var(--bs-progress-bar-color);
+ background-color: var(--bs-progress-bar-bg);
+ transition: var(--bs-progress-bar-transition);
+ .progress-bar {
+.progress-bar-striped {
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-size: var(--bs-progress-height) var(--bs-progress-height);
+.progress-stacked > .progress {
+.progress-stacked > .progress > .progress-bar {
+.progress-bar-animated {
+ animation: 1s linear infinite progress-bar-stripes;
+ .progress-bar-animated {
+ animation: none;
+.list-group {
+ --bs-list-group-color: var(--bs-body-color);
+ --bs-list-group-bg: var(--bs-body-bg);
+ --bs-list-group-border-color: var(--bs-border-color);
+ --bs-list-group-border-width: var(--bs-border-width);
+ --bs-list-group-border-radius: var(--bs-border-radius);
+ --bs-list-group-item-padding-x: 1rem;
+ --bs-list-group-item-padding-y: 0.5rem;
+ --bs-list-group-action-color: var(--bs-secondary-color);
+ --bs-list-group-action-hover-color: var(--bs-emphasis-color);
+ --bs-list-group-action-hover-bg: var(--bs-tertiary-bg);
+ --bs-list-group-action-active-color: var(--bs-body-color);
+ --bs-list-group-action-active-bg: var(--bs-secondary-bg);
+ --bs-list-group-disabled-color: var(--bs-secondary-color);
+ --bs-list-group-disabled-bg: var(--bs-body-bg);
+ --bs-list-group-active-color: #fff;
+ --bs-list-group-active-bg: #0d6efd;
+ --bs-list-group-active-border-color: #0d6efd;
+ border-radius: var(--bs-list-group-border-radius);
+.list-group-numbered {
+ list-style-type: none;
+ counter-reset: section;
+.list-group-numbered > .list-group-item::before {
+ content: counters(section, ".") ". ";
+ counter-increment: section;
+.list-group-item-action {
+ color: var(--bs-list-group-action-color);
+.list-group-item-action:hover, .list-group-item-action:focus {
+ color: var(--bs-list-group-action-hover-color);
+ background-color: var(--bs-list-group-action-hover-bg);
+.list-group-item-action:active {
+ color: var(--bs-list-group-action-active-color);
+ background-color: var(--bs-list-group-action-active-bg);
+.list-group-item {
+ padding: var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);
+ color: var(--bs-list-group-color);
+ background-color: var(--bs-list-group-bg);
+ border: var(--bs-list-group-border-width) solid var(--bs-list-group-border-color);
+.list-group-item:first-child {
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+.list-group-item:last-child {
+ border-bottom-right-radius: inherit;
+ border-bottom-left-radius: inherit;
+.list-group-item.disabled, .list-group-item:disabled {
+ color: var(--bs-list-group-disabled-color);
+ background-color: var(--bs-list-group-disabled-bg);
+.list-group-item.active {
+ color: var(--bs-list-group-active-color);
+ background-color: var(--bs-list-group-active-bg);
+ border-color: var(--bs-list-group-active-border-color);
+.list-group-item + .list-group-item {
+.list-group-item + .list-group-item.active {
+ margin-top: calc(-1 * var(--bs-list-group-border-width));
+ border-top-width: var(--bs-list-group-border-width);
+.list-group-horizontal {
+.list-group-horizontal > .list-group-item:first-child:not(:last-child) {
+ border-bottom-left-radius: var(--bs-list-group-border-radius);
+.list-group-horizontal > .list-group-item:last-child:not(:first-child) {
+ border-top-right-radius: var(--bs-list-group-border-radius);
+.list-group-horizontal > .list-group-item.active {
+.list-group-horizontal > .list-group-item + .list-group-item {
+ border-left-width: 0;
+.list-group-horizontal > .list-group-item + .list-group-item.active {
+ margin-left: calc(-1 * var(--bs-list-group-border-width));
+ border-left-width: var(--bs-list-group-border-width);
+ .list-group-horizontal-sm {
+ .list-group-horizontal-sm > .list-group-item:first-child:not(:last-child) {
+ .list-group-horizontal-sm > .list-group-item:last-child:not(:first-child) {
+ .list-group-horizontal-sm > .list-group-item.active {
+ .list-group-horizontal-sm > .list-group-item + .list-group-item {
+ .list-group-horizontal-sm > .list-group-item + .list-group-item.active {
+ .list-group-horizontal-md {
+ .list-group-horizontal-md > .list-group-item:first-child:not(:last-child) {
+ .list-group-horizontal-md > .list-group-item:last-child:not(:first-child) {
+ .list-group-horizontal-md > .list-group-item.active {
+ .list-group-horizontal-md > .list-group-item + .list-group-item {
+ .list-group-horizontal-md > .list-group-item + .list-group-item.active {
+ .list-group-horizontal-lg {
+ .list-group-horizontal-lg > .list-group-item:first-child:not(:last-child) {
+ .list-group-horizontal-lg > .list-group-item:last-child:not(:first-child) {
+ .list-group-horizontal-lg > .list-group-item.active {
+ .list-group-horizontal-lg > .list-group-item + .list-group-item {
+ .list-group-horizontal-lg > .list-group-item + .list-group-item.active {
+ .list-group-horizontal-xl {
+ .list-group-horizontal-xl > .list-group-item:first-child:not(:last-child) {
+ .list-group-horizontal-xl > .list-group-item:last-child:not(:first-child) {
+ .list-group-horizontal-xl > .list-group-item.active {
+ .list-group-horizontal-xl > .list-group-item + .list-group-item {
+ .list-group-horizontal-xl > .list-group-item + .list-group-item.active {
+ .list-group-horizontal-xxl {
+ .list-group-horizontal-xxl > .list-group-item:first-child:not(:last-child) {
+ .list-group-horizontal-xxl > .list-group-item:last-child:not(:first-child) {
+ .list-group-horizontal-xxl > .list-group-item.active {
+ .list-group-horizontal-xxl > .list-group-item + .list-group-item {
+ .list-group-horizontal-xxl > .list-group-item + .list-group-item.active {
+.list-group-flush {
+.list-group-flush > .list-group-item {
+ border-width: 0 0 var(--bs-list-group-border-width);
+.list-group-flush > .list-group-item:last-child {
+.list-group-item-primary {
+ --bs-list-group-color: var(--bs-primary-text);
+ --bs-list-group-bg: var(--bs-primary-bg-subtle);
+ --bs-list-group-border-color: var(--bs-primary-border-subtle);
+.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {
+ --bs-list-group-action-hover-bg: var(--bs-primary-border-subtle);
+.list-group-item-primary.list-group-item-action:active {
+ --bs-list-group-active-color: var(--bs-emphasis-color);
+ --bs-list-group-active-bg: var(--bs-primary-text);
+ --bs-list-group-active-border-color: var(--bs-primary-text);
+.list-group-item-secondary {
+ --bs-list-group-color: var(--bs-secondary-text);
+ --bs-list-group-bg: var(--bs-secondary-bg-subtle);
+ --bs-list-group-border-color: var(--bs-secondary-border-subtle);
+.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {
+ --bs-list-group-action-hover-bg: var(--bs-secondary-border-subtle);
+.list-group-item-secondary.list-group-item-action:active {
+ --bs-list-group-active-bg: var(--bs-secondary-text);
+ --bs-list-group-active-border-color: var(--bs-secondary-text);
+.list-group-item-success {
+ --bs-list-group-color: var(--bs-success-text);
+ --bs-list-group-bg: var(--bs-success-bg-subtle);
+ --bs-list-group-border-color: var(--bs-success-border-subtle);
+.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {
+ --bs-list-group-action-hover-bg: var(--bs-success-border-subtle);
+.list-group-item-success.list-group-item-action:active {
+ --bs-list-group-active-bg: var(--bs-success-text);
+ --bs-list-group-active-border-color: var(--bs-success-text);
+.list-group-item-info {
+ --bs-list-group-color: var(--bs-info-text);
+ --bs-list-group-bg: var(--bs-info-bg-subtle);
+ --bs-list-group-border-color: var(--bs-info-border-subtle);
+.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {
+ --bs-list-group-action-hover-bg: var(--bs-info-border-subtle);
+.list-group-item-info.list-group-item-action:active {
+ --bs-list-group-active-bg: var(--bs-info-text);
+ --bs-list-group-active-border-color: var(--bs-info-text);
+.list-group-item-warning {
+ --bs-list-group-color: var(--bs-warning-text);
+ --bs-list-group-bg: var(--bs-warning-bg-subtle);
+ --bs-list-group-border-color: var(--bs-warning-border-subtle);
+.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {
+ --bs-list-group-action-hover-bg: var(--bs-warning-border-subtle);
+.list-group-item-warning.list-group-item-action:active {
+ --bs-list-group-active-bg: var(--bs-warning-text);
+ --bs-list-group-active-border-color: var(--bs-warning-text);
+.list-group-item-danger {
+ --bs-list-group-color: var(--bs-danger-text);
+ --bs-list-group-bg: var(--bs-danger-bg-subtle);
+ --bs-list-group-border-color: var(--bs-danger-border-subtle);
+.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {
+ --bs-list-group-action-hover-bg: var(--bs-danger-border-subtle);
+.list-group-item-danger.list-group-item-action:active {
+ --bs-list-group-active-bg: var(--bs-danger-text);
+ --bs-list-group-active-border-color: var(--bs-danger-text);
+.list-group-item-light {
+ --bs-list-group-color: var(--bs-light-text);
+ --bs-list-group-bg: var(--bs-light-bg-subtle);
+ --bs-list-group-border-color: var(--bs-light-border-subtle);
+.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {
+ --bs-list-group-action-hover-bg: var(--bs-light-border-subtle);
+.list-group-item-light.list-group-item-action:active {
+ --bs-list-group-active-bg: var(--bs-light-text);
+ --bs-list-group-active-border-color: var(--bs-light-text);
+.list-group-item-dark {
+ --bs-list-group-color: var(--bs-dark-text);
+ --bs-list-group-bg: var(--bs-dark-bg-subtle);
+ --bs-list-group-border-color: var(--bs-dark-border-subtle);
+.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {
+ --bs-list-group-action-hover-bg: var(--bs-dark-border-subtle);
+.list-group-item-dark.list-group-item-action:active {
+ --bs-list-group-active-bg: var(--bs-dark-text);
+ --bs-list-group-active-border-color: var(--bs-dark-text);
+.btn-close {
+ --bs-btn-close-color: #000;
+ --bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e");
+ --bs-btn-close-opacity: 0.5;
+ --bs-btn-close-hover-opacity: 0.75;
+ --bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
+ --bs-btn-close-focus-opacity: 1;
+ --bs-btn-close-disabled-opacity: 0.25;
+ --bs-btn-close-white-filter: invert(1) grayscale(100%) brightness(200%);
+ box-sizing: content-box;
+ padding: 0.25em 0.25em;
+ color: var(--bs-btn-close-color);
+ background: transparent var(--bs-btn-close-bg) center/1em auto no-repeat;
+ opacity: var(--bs-btn-close-opacity);
+.btn-close:hover {
+ opacity: var(--bs-btn-close-hover-opacity);
+.btn-close:focus {
+ box-shadow: var(--bs-btn-close-focus-shadow);
+ opacity: var(--bs-btn-close-focus-opacity);
+.btn-close:disabled, .btn-close.disabled {
+ opacity: var(--bs-btn-close-disabled-opacity);
+.btn-close-white {
+ filter: var(--bs-btn-close-white-filter);
+[data-bs-theme=dark] .btn-close {
+.toast {
+ --bs-toast-zindex: 1090;
+ --bs-toast-padding-x: 0.75rem;
+ --bs-toast-padding-y: 0.5rem;
+ --bs-toast-spacing: 1.5rem;
+ --bs-toast-max-width: 350px;
+ --bs-toast-font-size: 0.875rem;
+ --bs-toast-color: ;
+ --bs-toast-bg: rgba(var(--bs-body-bg-rgb), 0.85);
+ --bs-toast-border-width: var(--bs-border-width);
+ --bs-toast-border-color: var(--bs-border-color-translucent);
+ --bs-toast-border-radius: var(--bs-border-radius);
+ --bs-toast-box-shadow: var(--bs-box-shadow);
+ --bs-toast-header-color: var(--bs-secondary-color);
+ --bs-toast-header-bg: rgba(var(--bs-body-bg-rgb), 0.85);
+ --bs-toast-header-border-color: var(--bs-border-color-translucent);
+ width: var(--bs-toast-max-width);
+ font-size: var(--bs-toast-font-size);
+ color: var(--bs-toast-color);
+ pointer-events: auto;
+ background-color: var(--bs-toast-bg);
+ border: var(--bs-toast-border-width) solid var(--bs-toast-border-color);
+ box-shadow: var(--bs-toast-box-shadow);
+ border-radius: var(--bs-toast-border-radius);
+.toast.showing {
+.toast:not(.show) {
+.toast-container {
+ z-index: var(--bs-toast-zindex);
+ width: -webkit-max-content;
+ width: -moz-max-content;
+ width: max-content;
+.toast-container > :not(:last-child) {
+ margin-bottom: var(--bs-toast-spacing);
+.toast-header {
+ padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x);
+ color: var(--bs-toast-header-color);
+ background-color: var(--bs-toast-header-bg);
+ border-bottom: var(--bs-toast-border-width) solid var(--bs-toast-header-border-color);
+ border-top-left-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));
+ border-top-right-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width));
+.toast-header .btn-close {
+ margin-right: calc(-0.5 * var(--bs-toast-padding-x));
+ margin-left: var(--bs-toast-padding-x);
+.toast-body {
+ padding: var(--bs-toast-padding-x);
+.modal {
+ --bs-modal-zindex: 1055;
+ --bs-modal-width: 500px;
+ --bs-modal-padding: 1rem;
+ --bs-modal-margin: 0.5rem;
+ --bs-modal-color: ;
+ --bs-modal-bg: var(--bs-body-bg);
+ --bs-modal-border-color: var(--bs-border-color-translucent);
+ --bs-modal-border-width: var(--bs-border-width);
+ --bs-modal-border-radius: var(--bs-border-radius-lg);
+ --bs-modal-box-shadow: 0 0.125rem 0.25rem rgba(var(--bs-body-color-rgb), 0.075);
+ --bs-modal-inner-border-radius: calc(var(--bs-border-radius-lg) - (var(--bs-border-width)));
+ --bs-modal-header-padding-x: 1rem;
+ --bs-modal-header-padding-y: 1rem;
+ --bs-modal-header-padding: 1rem 1rem;
+ --bs-modal-header-border-color: var(--bs-border-color);
+ --bs-modal-header-border-width: var(--bs-border-width);
+ --bs-modal-title-line-height: 1.5;
+ --bs-modal-footer-gap: 0.5rem;
+ --bs-modal-footer-bg: ;
+ --bs-modal-footer-border-color: var(--bs-border-color);
+ --bs-modal-footer-border-width: var(--bs-border-width);
+ position: fixed;
+ z-index: var(--bs-modal-zindex);
+ overflow-x: hidden;
+.modal-dialog {
+ margin: var(--bs-modal-margin);
+.modal.fade .modal-dialog {
+ transition: transform 0.3s ease-out;
+ transform: translate(0, -50px);
+ .modal.fade .modal-dialog {
+.modal.show .modal-dialog {
+ transform: none;
+.modal.modal-static .modal-dialog {
+ transform: scale(1.02);
+.modal-dialog-scrollable {
+ height: calc(100% - var(--bs-modal-margin) * 2);
+.modal-dialog-scrollable .modal-content {
+ max-height: 100%;
+.modal-dialog-scrollable .modal-body {
+.modal-dialog-centered {
+ min-height: calc(100% - var(--bs-modal-margin) * 2);
+.modal-content {
+ color: var(--bs-modal-color);
+ background-color: var(--bs-modal-bg);
+ border: var(--bs-modal-border-width) solid var(--bs-modal-border-color);
+ border-radius: var(--bs-modal-border-radius);
+.modal-backdrop {
+ --bs-backdrop-zindex: 1050;
+ --bs-backdrop-bg: #000;
+ --bs-backdrop-opacity: 0.5;
+ z-index: var(--bs-backdrop-zindex);
+ width: 100vw;
+ height: 100vh;
+ background-color: var(--bs-backdrop-bg);
+.modal-backdrop.fade {
+.modal-backdrop.show {
+ opacity: var(--bs-backdrop-opacity);
+.modal-header {
+ padding: var(--bs-modal-header-padding);
+ border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color);
+ border-top-left-radius: var(--bs-modal-inner-border-radius);
+ border-top-right-radius: var(--bs-modal-inner-border-radius);
+.modal-header .btn-close {
+ padding: calc(var(--bs-modal-header-padding-y) * 0.5) calc(var(--bs-modal-header-padding-x) * 0.5);
+ margin: calc(-0.5 * var(--bs-modal-header-padding-y)) calc(-0.5 * var(--bs-modal-header-padding-x)) calc(-0.5 * var(--bs-modal-header-padding-y)) auto;
+.modal-title {
+ line-height: var(--bs-modal-title-line-height);
+.modal-body {
+ padding: var(--bs-modal-padding);
+.modal-footer {
+ justify-content: flex-end;
+ padding: calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * 0.5);
+ background-color: var(--bs-modal-footer-bg);
+ border-top: var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color);
+ border-bottom-right-radius: var(--bs-modal-inner-border-radius);
+ border-bottom-left-radius: var(--bs-modal-inner-border-radius);
+.modal-footer > * {
+ margin: calc(var(--bs-modal-footer-gap) * 0.5);
+ .modal {
+ --bs-modal-margin: 1.75rem;
+ --bs-modal-box-shadow: 0 0.5rem 1rem rgba(var(--bs-body-color-rgb), 0.15);
+ .modal-dialog {
+ max-width: var(--bs-modal-width);
+ .modal-sm {
+ --bs-modal-width: 300px;
+ .modal-lg,
+ .modal-xl {
+ --bs-modal-width: 800px;
+ --bs-modal-width: 1140px;
+.modal-fullscreen {
+ max-width: none;
+.modal-fullscreen .modal-content {
+.modal-fullscreen .modal-header,
+.modal-fullscreen .modal-footer {
+.modal-fullscreen .modal-body {
+ .modal-fullscreen-sm-down {
+ .modal-fullscreen-sm-down .modal-content {
+ .modal-fullscreen-sm-down .modal-header,
+ .modal-fullscreen-sm-down .modal-footer {
+ .modal-fullscreen-sm-down .modal-body {
+ .modal-fullscreen-md-down {
+ .modal-fullscreen-md-down .modal-content {
+ .modal-fullscreen-md-down .modal-header,
+ .modal-fullscreen-md-down .modal-footer {
+ .modal-fullscreen-md-down .modal-body {
+ .modal-fullscreen-lg-down {
+ .modal-fullscreen-lg-down .modal-content {
+ .modal-fullscreen-lg-down .modal-header,
+ .modal-fullscreen-lg-down .modal-footer {
+ .modal-fullscreen-lg-down .modal-body {
+ .modal-fullscreen-xl-down {
+ .modal-fullscreen-xl-down .modal-content {
+ .modal-fullscreen-xl-down .modal-header,
+ .modal-fullscreen-xl-down .modal-footer {
+ .modal-fullscreen-xl-down .modal-body {
+ .modal-fullscreen-xxl-down {
+ .modal-fullscreen-xxl-down .modal-content {
+ .modal-fullscreen-xxl-down .modal-header,
+ .modal-fullscreen-xxl-down .modal-footer {
+ .modal-fullscreen-xxl-down .modal-body {
+.tooltip {
+ --bs-tooltip-zindex: 1080;
+ --bs-tooltip-max-width: 200px;
+ --bs-tooltip-padding-x: 0.5rem;
+ --bs-tooltip-padding-y: 0.25rem;
+ --bs-tooltip-margin: ;
+ --bs-tooltip-font-size: 0.875rem;
+ --bs-tooltip-color: var(--bs-body-bg);
+ --bs-tooltip-bg: var(--bs-emphasis-color);
+ --bs-tooltip-border-radius: var(--bs-border-radius);
+ --bs-tooltip-opacity: 0.9;
+ --bs-tooltip-arrow-width: 0.8rem;
+ --bs-tooltip-arrow-height: 0.4rem;
+ z-index: var(--bs-tooltip-zindex);
+ padding: var(--bs-tooltip-arrow-height);
+ margin: var(--bs-tooltip-margin);
+ font-family: var(--bs-font-sans-serif);
+ text-shadow: none;
+ letter-spacing: normal;
+ white-space: normal;
+ word-spacing: normal;
+ line-break: auto;
+ font-size: var(--bs-tooltip-font-size);
+.tooltip.show {
+ opacity: var(--bs-tooltip-opacity);
+.tooltip .tooltip-arrow {
+ width: var(--bs-tooltip-arrow-width);
+ height: var(--bs-tooltip-arrow-height);
+.tooltip .tooltip-arrow::before {
+.bs-tooltip-top .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow {
+.bs-tooltip-top .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before {
+ border-width: var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0;
+ border-top-color: var(--bs-tooltip-bg);
+/* rtl:begin:ignore */
+.bs-tooltip-end .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow {
+ width: var(--bs-tooltip-arrow-height);
+ height: var(--bs-tooltip-arrow-width);
+.bs-tooltip-end .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before {
+ right: -1px;
+ border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0;
+ border-right-color: var(--bs-tooltip-bg);
+/* rtl:end:ignore */
+.bs-tooltip-bottom .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow {
+.bs-tooltip-bottom .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before {
+ bottom: -1px;
+ border-width: 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height);
+ border-bottom-color: var(--bs-tooltip-bg);
+.bs-tooltip-start .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow {
+.bs-tooltip-start .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before {
+ left: -1px;
+ border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height);
+ border-left-color: var(--bs-tooltip-bg);
+.tooltip-inner {
+ max-width: var(--bs-tooltip-max-width);
+ padding: var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x);
+ color: var(--bs-tooltip-color);
+ background-color: var(--bs-tooltip-bg);
+ border-radius: var(--bs-tooltip-border-radius);
+.popover {
+ --bs-popover-zindex: 1070;
+ --bs-popover-max-width: 276px;
+ --bs-popover-font-size: 0.875rem;
+ --bs-popover-bg: var(--bs-body-bg);
+ --bs-popover-border-width: var(--bs-border-width);
+ --bs-popover-border-color: var(--bs-border-color-translucent);
+ --bs-popover-border-radius: var(--bs-border-radius-lg);
+ --bs-popover-inner-border-radius: calc(var(--bs-border-radius-lg) - var(--bs-border-width));
+ --bs-popover-box-shadow: 0 0.5rem 1rem rgba(var(--bs-body-color-rgb), 0.15);
+ --bs-popover-header-padding-x: 1rem;
+ --bs-popover-header-padding-y: 0.5rem;
+ --bs-popover-header-font-size: 1rem;
+ --bs-popover-header-color: ;
+ --bs-popover-header-bg: var(--bs-secondary-bg);
+ --bs-popover-body-padding-x: 1rem;
+ --bs-popover-body-padding-y: 1rem;
+ --bs-popover-body-color: var(--bs-body-color);
+ --bs-popover-arrow-width: 1rem;
+ --bs-popover-arrow-height: 0.5rem;
+ --bs-popover-arrow-border: var(--bs-popover-border-color);
+ z-index: var(--bs-popover-zindex);
+ max-width: var(--bs-popover-max-width);
+ font-size: var(--bs-popover-font-size);
+ background-color: var(--bs-popover-bg);
+ border: var(--bs-popover-border-width) solid var(--bs-popover-border-color);
+ border-radius: var(--bs-popover-border-radius);
+.popover .popover-arrow {
+ width: var(--bs-popover-arrow-width);
+ height: var(--bs-popover-arrow-height);
+.popover .popover-arrow::before, .popover .popover-arrow::after {
+.bs-popover-top > .popover-arrow, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow {
+ bottom: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));
+.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before, .bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after {
+ border-width: var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0;
+.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before {
+ border-top-color: var(--bs-popover-arrow-border);
+.bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after {
+ bottom: var(--bs-popover-border-width);
+ border-top-color: var(--bs-popover-bg);
+.bs-popover-end > .popover-arrow, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow {
+ left: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));
+ width: var(--bs-popover-arrow-height);
+ height: var(--bs-popover-arrow-width);
+.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before, .bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after {
+ border-width: calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0;
+.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before {
+ border-right-color: var(--bs-popover-arrow-border);
+.bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after {
+ left: var(--bs-popover-border-width);
+ border-right-color: var(--bs-popover-bg);
+.bs-popover-bottom > .popover-arrow, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow {
+ top: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));
+.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before, .bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after {
+ border-width: 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height);
+.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before {
+ border-bottom-color: var(--bs-popover-arrow-border);
+.bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after {
+ top: var(--bs-popover-border-width);
+ border-bottom-color: var(--bs-popover-bg);
+.bs-popover-bottom .popover-header::before, .bs-popover-auto[data-popper-placement^=bottom] .popover-header::before {
+ left: 50%;
+ margin-left: calc(-0.5 * var(--bs-popover-arrow-width));
+ border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-header-bg);
+.bs-popover-start > .popover-arrow, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow {
+ right: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width));
+.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before, .bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after {
+ border-width: calc(var(--bs-popover-arrow-width) * 0.5) 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height);
+.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before {
+ border-left-color: var(--bs-popover-arrow-border);
+.bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after {
+ right: var(--bs-popover-border-width);
+ border-left-color: var(--bs-popover-bg);
+.popover-header {
+ padding: var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x);
+ font-size: var(--bs-popover-header-font-size);
+ color: var(--bs-popover-header-color);
+ background-color: var(--bs-popover-header-bg);
+ border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-border-color);
+ border-top-left-radius: var(--bs-popover-inner-border-radius);
+ border-top-right-radius: var(--bs-popover-inner-border-radius);
+.popover-header:empty {
+.popover-body {
+ padding: var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x);
+ color: var(--bs-popover-body-color);
+.carousel {
+.carousel.pointer-event {
+ touch-action: pan-y;
+.carousel-inner {
+.carousel-inner::after {
+.carousel-item {
+ margin-right: -100%;
+ -webkit-backface-visibility: hidden;
+ backface-visibility: hidden;
+ transition: transform 0.6s ease-in-out;
+ .carousel-item {
+.carousel-item.active,
+.carousel-item-next,
+.carousel-item-prev {
+.carousel-item-next:not(.carousel-item-start),
+.active.carousel-item-end {
+ transform: translateX(100%);
+.carousel-item-prev:not(.carousel-item-end),
+.active.carousel-item-start {
+ transform: translateX(-100%);
+.carousel-fade .carousel-item {
+ transition-property: opacity;
+.carousel-fade .carousel-item.active,
+.carousel-fade .carousel-item-next.carousel-item-start,
+.carousel-fade .carousel-item-prev.carousel-item-end {
+.carousel-fade .active.carousel-item-start,
+.carousel-fade .active.carousel-item-end {
+ z-index: 0;
+ transition: opacity 0s 0.6s;
+ .carousel-fade .active.carousel-item-start,
+ .carousel-fade .active.carousel-item-end {
+.carousel-control-prev,
+.carousel-control-next {
+ width: 15%;
+ transition: opacity 0.15s ease;
+ .carousel-control-prev,
+ .carousel-control-next {
+.carousel-control-prev:hover, .carousel-control-prev:focus,
+.carousel-control-next:hover,
+.carousel-control-next:focus {
+ opacity: 0.9;
+.carousel-control-prev {
+.carousel-control-prev-icon,
+.carousel-control-next-icon {
+ width: 2rem;
+ height: 2rem;
+ background-position: 50%;
+ background-size: 100% 100%;
+/* rtl:options: {
+ "autoRename": true,
+ "stringMap":[ {
+ "name" : "prev-next",
+ "search" : "prev",
+ "replace" : "next"
+ } ]
+} */
+.carousel-control-prev-icon {
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");
+.carousel-indicators {
+ margin-right: 15%;
+ margin-left: 15%;
+.carousel-indicators [data-bs-target] {
+ flex: 0 1 auto;
+ width: 30px;
+ height: 3px;
+ margin-right: 3px;
+ margin-left: 3px;
+ text-indent: -999px;
+ background-color: #fff;
+ border-top: 10px solid transparent;
+ border-bottom: 10px solid transparent;
+ transition: opacity 0.6s ease;
+ .carousel-indicators [data-bs-target] {
+.carousel-indicators .active {
+.carousel-caption {
+ right: 15%;
+ bottom: 1.25rem;
+ left: 15%;
+ padding-top: 1.25rem;
+ padding-bottom: 1.25rem;
+.carousel-dark .carousel-control-prev-icon,
+.carousel-dark .carousel-control-next-icon {
+ filter: invert(1) grayscale(100);
+.carousel-dark .carousel-indicators [data-bs-target] {
+ background-color: #000;
+.carousel-dark .carousel-caption {
+ color: #000;
+[data-bs-theme=dark] .carousel .carousel-control-prev-icon,
+[data-bs-theme=dark] .carousel .carousel-control-next-icon {
+[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target] {
+[data-bs-theme=dark] .carousel .carousel-caption {
+.spinner-grow,
+.spinner-border {
+ width: var(--bs-spinner-width);
+ height: var(--bs-spinner-height);
+ vertical-align: var(--bs-spinner-vertical-align);
+ animation: var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name);
+@keyframes spinner-border {
+ to {
+ transform: rotate(360deg) /* rtl:ignore */;
+ --bs-spinner-width: 2rem;
+ --bs-spinner-height: 2rem;
+ --bs-spinner-vertical-align: -0.125em;
+ --bs-spinner-border-width: 0.25em;
+ --bs-spinner-animation-speed: 0.75s;
+ --bs-spinner-animation-name: spinner-border;
+ border: var(--bs-spinner-border-width) solid currentcolor;
+ border-right-color: transparent;
+.spinner-border-sm {
+ --bs-spinner-width: 1rem;
+ --bs-spinner-height: 1rem;
+ --bs-spinner-border-width: 0.2em;
+@keyframes spinner-grow {
+ transform: scale(0);
+ 50% {
+.spinner-grow {
+ --bs-spinner-animation-name: spinner-grow;
+ background-color: currentcolor;
+.spinner-grow-sm {
+ .spinner-border,
+ .spinner-grow {
+ --bs-spinner-animation-speed: 1.5s;
+.offcanvas, .offcanvas-xxl, .offcanvas-xl, .offcanvas-lg, .offcanvas-md, .offcanvas-sm {
+ --bs-offcanvas-zindex: 1045;
+ --bs-offcanvas-width: 400px;
+ --bs-offcanvas-height: 30vh;
+ --bs-offcanvas-padding-x: 1rem;
+ --bs-offcanvas-padding-y: 1rem;
+ --bs-offcanvas-color: var(--bs-body-color);
+ --bs-offcanvas-bg: var(--bs-body-bg);
+ --bs-offcanvas-border-width: var(--bs-border-width);
+ --bs-offcanvas-border-color: var(--bs-border-color-translucent);
+ --bs-offcanvas-box-shadow: 0 0.125rem 0.25rem rgba(var(--bs-body-color-rgb), 0.075);
+ --bs-offcanvas-transition: transform 0.3s ease-in-out;
+ --bs-offcanvas-title-line-height: 1.5;
+ .offcanvas-sm {
+ z-index: var(--bs-offcanvas-zindex);
+ color: var(--bs-offcanvas-color);
+ visibility: hidden;
+ background-color: var(--bs-offcanvas-bg);
+ transition: var(--bs-offcanvas-transition);
+@media (max-width: 575.98px) and (prefers-reduced-motion: reduce) {
+ .offcanvas-sm.offcanvas-start {
+ width: var(--bs-offcanvas-width);
+ border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);
+ .offcanvas-sm.offcanvas-end {
+ border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);
+ .offcanvas-sm.offcanvas-top {
+ height: var(--bs-offcanvas-height);
+ border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);
+ transform: translateY(-100%);
+ .offcanvas-sm.offcanvas-bottom {
+ border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color);
+ transform: translateY(100%);
+ .offcanvas-sm.showing, .offcanvas-sm.show:not(.hiding) {
+ .offcanvas-sm.showing, .offcanvas-sm.hiding, .offcanvas-sm.show {
+ visibility: visible;
+ --bs-offcanvas-height: auto;
+ --bs-offcanvas-border-width: 0;
+ .offcanvas-sm .offcanvas-header {
+ .offcanvas-sm .offcanvas-body {
+ .offcanvas-md {
+@media (max-width: 767.98px) and (prefers-reduced-motion: reduce) {
+ .offcanvas-md.offcanvas-start {
+ .offcanvas-md.offcanvas-end {
+ .offcanvas-md.offcanvas-top {
+ .offcanvas-md.offcanvas-bottom {
+ .offcanvas-md.showing, .offcanvas-md.show:not(.hiding) {
+ .offcanvas-md.showing, .offcanvas-md.hiding, .offcanvas-md.show {
+ .offcanvas-md .offcanvas-header {
+ .offcanvas-md .offcanvas-body {
+ .offcanvas-lg {
+@media (max-width: 991.98px) and (prefers-reduced-motion: reduce) {
+ .offcanvas-lg.offcanvas-start {
+ .offcanvas-lg.offcanvas-end {
+ .offcanvas-lg.offcanvas-top {
+ .offcanvas-lg.offcanvas-bottom {
+ .offcanvas-lg.showing, .offcanvas-lg.show:not(.hiding) {
+ .offcanvas-lg.showing, .offcanvas-lg.hiding, .offcanvas-lg.show {
+ .offcanvas-lg .offcanvas-header {
+ .offcanvas-lg .offcanvas-body {
+ .offcanvas-xl {
+@media (max-width: 1199.98px) and (prefers-reduced-motion: reduce) {
+ .offcanvas-xl.offcanvas-start {
+ .offcanvas-xl.offcanvas-end {
+ .offcanvas-xl.offcanvas-top {
+ .offcanvas-xl.offcanvas-bottom {
+ .offcanvas-xl.showing, .offcanvas-xl.show:not(.hiding) {
+ .offcanvas-xl.showing, .offcanvas-xl.hiding, .offcanvas-xl.show {
+ .offcanvas-xl .offcanvas-header {
+ .offcanvas-xl .offcanvas-body {
+ .offcanvas-xxl {
+@media (max-width: 1399.98px) and (prefers-reduced-motion: reduce) {
+ .offcanvas-xxl.offcanvas-start {
+ .offcanvas-xxl.offcanvas-end {
+ .offcanvas-xxl.offcanvas-top {
+ .offcanvas-xxl.offcanvas-bottom {
+ .offcanvas-xxl.showing, .offcanvas-xxl.show:not(.hiding) {
+ .offcanvas-xxl.showing, .offcanvas-xxl.hiding, .offcanvas-xxl.show {
+ .offcanvas-xxl .offcanvas-header {
+ .offcanvas-xxl .offcanvas-body {
+.offcanvas {
+ .offcanvas {
+.offcanvas.offcanvas-start {
+.offcanvas.offcanvas-end {
+.offcanvas.offcanvas-top {
+.offcanvas.offcanvas-bottom {
+.offcanvas.showing, .offcanvas.show:not(.hiding) {
+.offcanvas.showing, .offcanvas.hiding, .offcanvas.show {
+.offcanvas-backdrop {
+ z-index: 1040;
+.offcanvas-backdrop.fade {
+.offcanvas-backdrop.show {
+.offcanvas-header {
+ padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x);
+.offcanvas-header .btn-close {
+ padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5);
+ margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y));
+ margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x));
+ margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y));
+.offcanvas-title {
+ line-height: var(--bs-offcanvas-title-line-height);
+.offcanvas-body {
+.placeholder {
+ min-height: 1em;
+ cursor: wait;
+.placeholder.btn::before {
+.placeholder-xs {
+ min-height: 0.6em;
+.placeholder-sm {
+ min-height: 0.8em;
+.placeholder-lg {
+ min-height: 1.2em;
+.placeholder-glow .placeholder {
+ animation: placeholder-glow 2s ease-in-out infinite;
+@keyframes placeholder-glow {
+ opacity: 0.2;
+.placeholder-wave {
+ -webkit-mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);
+ mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%);
+ -webkit-mask-size: 200% 100%;
+ mask-size: 200% 100%;
+ animation: placeholder-wave 2s linear infinite;
+@keyframes placeholder-wave {
+ 100% {
+ -webkit-mask-position: -200% 0%;
+ mask-position: -200% 0%;
+.clearfix::after {
+.text-bg-primary {
+ color: #fff !important;
+ background-color: RGBA(13, 110, 253, var(--bs-bg-opacity, 1)) !important;
+.text-bg-secondary {
+ background-color: RGBA(108, 117, 125, var(--bs-bg-opacity, 1)) !important;
+.text-bg-success {
+ background-color: RGBA(25, 135, 84, var(--bs-bg-opacity, 1)) !important;
+.text-bg-info {
+ color: #000 !important;
+ background-color: RGBA(13, 202, 240, var(--bs-bg-opacity, 1)) !important;
+.text-bg-warning {
+ background-color: RGBA(255, 193, 7, var(--bs-bg-opacity, 1)) !important;
+.text-bg-danger {
+ background-color: RGBA(220, 53, 69, var(--bs-bg-opacity, 1)) !important;
+.text-bg-light {
+ background-color: RGBA(248, 249, 250, var(--bs-bg-opacity, 1)) !important;
+.text-bg-dark {
+ background-color: RGBA(33, 37, 41, var(--bs-bg-opacity, 1)) !important;
+.link-primary {
+ color: #0d6efd !important;
+.link-primary:hover, .link-primary:focus {
+ color: #0a58ca !important;
+.link-secondary {
+ color: #6c757d !important;
+.link-secondary:hover, .link-secondary:focus {
+ color: #565e64 !important;
+.link-success {
+ color: #198754 !important;
+.link-success:hover, .link-success:focus {
+ color: #146c43 !important;
+.link-info {
+ color: #0dcaf0 !important;
+.link-info:hover, .link-info:focus {
+ color: #3dd5f3 !important;
+.link-warning {
+ color: #ffc107 !important;
+.link-warning:hover, .link-warning:focus {
+ color: #ffcd39 !important;
+.link-danger {
+ color: #dc3545 !important;
+.link-danger:hover, .link-danger:focus {
+ color: #b02a37 !important;
+.link-light {
+ color: #f8f9fa !important;
+.link-light:hover, .link-light:focus {
+ color: #f9fafb !important;
+.link-dark {
+ color: #212529 !important;
+.link-dark:hover, .link-dark:focus {
+ color: #1a1e21 !important;
+.ratio {
+.ratio::before {
+ padding-top: var(--bs-aspect-ratio);
+.ratio > * {
+.ratio-1x1 {
+ --bs-aspect-ratio: 100%;
+.ratio-4x3 {
+ --bs-aspect-ratio: 75%;
+.ratio-16x9 {
+ --bs-aspect-ratio: 56.25%;
+.ratio-21x9 {
+ --bs-aspect-ratio: 42.8571428571%;
+.fixed-top {
+ z-index: 1030;
+.fixed-bottom {
+.sticky-top {
+ position: -webkit-sticky;
+ position: sticky;
+ z-index: 1020;
+.sticky-bottom {
+ .sticky-sm-top {
+ .sticky-sm-bottom {
+ .sticky-md-top {
+ .sticky-md-bottom {
+ .sticky-lg-top {
+ .sticky-lg-bottom {
+ .sticky-xl-top {
+ .sticky-xl-bottom {
+ .sticky-xxl-top {
+ .sticky-xxl-bottom {
+.hstack {
+ align-self: stretch;
+.vstack {
+.visually-hidden,
+.visually-hidden-focusable:not(:focus):not(:focus-within) {
+ position: absolute !important;
+ width: 1px !important;
+ height: 1px !important;
+ padding: 0 !important;
+ margin: -1px !important;
+ overflow: hidden !important;
+ clip: rect(0, 0, 0, 0) !important;
+ white-space: nowrap !important;
+.stretched-link::after {
+.text-truncate {
+.vr {
+ width: 1px;
+.align-baseline {
+ vertical-align: baseline !important;
+.align-top {
+ vertical-align: top !important;
+.align-middle {
+ vertical-align: middle !important;
+.align-bottom {
+ vertical-align: bottom !important;
+.align-text-bottom {
+ vertical-align: text-bottom !important;
+.align-text-top {
+ vertical-align: text-top !important;
+.float-start {
+ float: left !important;
+.float-end {
+ float: right !important;
+.float-none {
+ float: none !important;
+.object-fit-contain {
+ -o-object-fit: contain !important;
+ object-fit: contain !important;
+.object-fit-cover {
+ -o-object-fit: cover !important;
+ object-fit: cover !important;
+.object-fit-fill {
+ -o-object-fit: fill !important;
+ object-fit: fill !important;
+.object-fit-scale {
+ -o-object-fit: scale-down !important;
+ object-fit: scale-down !important;
+.object-fit-none {
+ -o-object-fit: none !important;
+ object-fit: none !important;
+.opacity-0 {
+ opacity: 0 !important;
+.opacity-25 {
+ opacity: 0.25 !important;
+.opacity-50 {
+ opacity: 0.5 !important;
+.opacity-75 {
+ opacity: 0.75 !important;
+.opacity-100 {
+ opacity: 1 !important;
+.overflow-auto {
+ overflow: auto !important;
+.overflow-hidden {
+.overflow-visible {
+ overflow: visible !important;
+.overflow-scroll {
+ overflow: scroll !important;
+.overflow-x-auto {
+ overflow-x: auto !important;
+.overflow-x-hidden {
+ overflow-x: hidden !important;
+.overflow-x-visible {
+ overflow-x: visible !important;
+.overflow-x-scroll {
+ overflow-x: scroll !important;
+.overflow-y-auto {
+ overflow-y: auto !important;
+.overflow-y-hidden {
+ overflow-y: hidden !important;
+.overflow-y-visible {
+ overflow-y: visible !important;
+.overflow-y-scroll {
+ overflow-y: scroll !important;
+.d-inline {
+ display: inline !important;
+.d-inline-block {
+ display: inline-block !important;
+.d-block {
+ display: block !important;
+.d-grid {
+ display: grid !important;
+.d-table {
+ display: table !important;
+.d-table-row {
+ display: table-row !important;
+.d-table-cell {
+ display: table-cell !important;
+.d-flex {
+.d-inline-flex {
+ display: inline-flex !important;
+.d-none {
+.shadow {
+ box-shadow: 0 0.5rem 1rem rgba(var(--bs-body-color-rgb), 0.15) !important;
+.shadow-sm {
+ box-shadow: 0 0.125rem 0.25rem rgba(var(--bs-body-color-rgb), 0.075) !important;
+.shadow-lg {
+ box-shadow: 0 1rem 3rem rgba(var(--bs-body-color-rgb), 0.175) !important;
+.shadow-none {
+ box-shadow: none !important;
+.position-static {
+ position: static !important;
+.position-relative {
+ position: relative !important;
+.position-absolute {
+.position-fixed {
+ position: fixed !important;
+.position-sticky {
+ position: -webkit-sticky !important;
+ position: sticky !important;
+.top-0 {
+ top: 0 !important;
+.top-50 {
+ top: 50% !important;
+.top-100 {
+ top: 100% !important;
+.bottom-0 {
+ bottom: 0 !important;
+.bottom-50 {
+ bottom: 50% !important;
+.bottom-100 {
+ bottom: 100% !important;
+.start-0 {
+ left: 0 !important;
+.start-50 {
+ left: 50% !important;
+.start-100 {
+ left: 100% !important;
+.end-0 {
+ right: 0 !important;
+.end-50 {
+ right: 50% !important;
+.end-100 {
+ right: 100% !important;
+.translate-middle {
+ transform: translate(-50%, -50%) !important;
+.translate-middle-x {
+ transform: translateX(-50%) !important;
+.translate-middle-y {
+ transform: translateY(-50%) !important;
+.border {
+ border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;
+.border-0 {
+.border-top {
+ border-top: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;
+.border-top-0 {
+ border-top: 0 !important;
+.border-end {
+ border-right: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;
+.border-end-0 {
+ border-right: 0 !important;
+.border-bottom {
+ border-bottom: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;
+.border-bottom-0 {
+ border-bottom: 0 !important;
+.border-start {
+ border-left: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;
+.border-start-0 {
+ border-left: 0 !important;
+.border-primary {
+ --bs-border-opacity: 1;
+ border-color: rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important;
+.border-secondary {
+ border-color: rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important;
+.border-success {
+ border-color: rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important;
+.border-info {
+ border-color: rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important;
+.border-warning {
+ border-color: rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important;
+.border-danger {
+ border-color: rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important;
+.border-light {
+ border-color: rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important;
+.border-dark {
+ border-color: rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important;
+.border-white {
+ border-color: rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important;
+.border-primary-subtle {
+ border-color: var(--bs-primary-border-subtle) !important;
+.border-secondary-subtle {
+ border-color: var(--bs-secondary-border-subtle) !important;
+.border-success-subtle {
+ border-color: var(--bs-success-border-subtle) !important;
+.border-info-subtle {
+ border-color: var(--bs-info-border-subtle) !important;
+.border-warning-subtle {
+ border-color: var(--bs-warning-border-subtle) !important;
+.border-danger-subtle {
+ border-color: var(--bs-danger-border-subtle) !important;
+.border-light-subtle {
+ border-color: var(--bs-light-border-subtle) !important;
+.border-dark-subtle {
+ border-color: var(--bs-dark-border-subtle) !important;
+.border-1 {
+.border-2 {
+ --bs-border-width: 2px;
+.border-3 {
+ --bs-border-width: 3px;
+.border-4 {
+ --bs-border-width: 4px;
+.border-5 {
+ --bs-border-width: 5px;
+.border-opacity-10 {
+ --bs-border-opacity: 0.1;
+.border-opacity-25 {
+ --bs-border-opacity: 0.25;
+.border-opacity-50 {
+ --bs-border-opacity: 0.5;
+.border-opacity-75 {
+ --bs-border-opacity: 0.75;
+.border-opacity-100 {
+.w-25 {
+ width: 25% !important;
+.w-50 {
+ width: 50% !important;
+.w-75 {
+ width: 75% !important;
+.w-100 {
+ width: 100% !important;
+.w-auto {
+.mw-100 {
+ max-width: 100% !important;
+.vw-100 {
+ width: 100vw !important;
+.min-vw-100 {
+ min-width: 100vw !important;
+.h-25 {
+ height: 25% !important;
+.h-50 {
+ height: 50% !important;
+.h-75 {
+ height: 75% !important;
+.h-100 {
+ height: 100% !important;
+.h-auto {
+.mh-100 {
+ max-height: 100% !important;
+.vh-100 {
+ height: 100vh !important;
+.min-vh-100 {
+ min-height: 100vh !important;
+.flex-fill {
+ flex: 1 1 auto !important;
+.flex-row {
+ flex-direction: row !important;
+.flex-column {
+ flex-direction: column !important;
+.flex-row-reverse {
+ flex-direction: row-reverse !important;
+.flex-column-reverse {
+ flex-direction: column-reverse !important;
+.flex-grow-0 {
+ flex-grow: 0 !important;
+.flex-grow-1 {
+ flex-grow: 1 !important;
+.flex-shrink-0 {
+ flex-shrink: 0 !important;
+.flex-shrink-1 {
+ flex-shrink: 1 !important;
+.flex-wrap {
+ flex-wrap: wrap !important;
+.flex-nowrap {
+ flex-wrap: nowrap !important;
+.flex-wrap-reverse {
+ flex-wrap: wrap-reverse !important;
+.justify-content-start {
+ justify-content: flex-start !important;
+.justify-content-end {
+ justify-content: flex-end !important;
+.justify-content-center {
+ justify-content: center !important;
+.justify-content-between {
+ justify-content: space-between !important;
+.justify-content-around {
+ justify-content: space-around !important;
+.justify-content-evenly {
+ justify-content: space-evenly !important;
+.align-items-start {
+ align-items: flex-start !important;
+.align-items-end {
+ align-items: flex-end !important;
+.align-items-center {
+ align-items: center !important;
+.align-items-baseline {
+ align-items: baseline !important;
+.align-items-stretch {
+ align-items: stretch !important;
+.align-content-start {
+ align-content: flex-start !important;
+.align-content-end {
+ align-content: flex-end !important;
+.align-content-center {
+ align-content: center !important;
+.align-content-between {
+ align-content: space-between !important;
+.align-content-around {
+ align-content: space-around !important;
+.align-content-stretch {
+ align-content: stretch !important;
+.align-self-auto {
+ align-self: auto !important;
+.align-self-start {
+ align-self: flex-start !important;
+.align-self-end {
+ align-self: flex-end !important;
+.align-self-center {
+ align-self: center !important;
+.align-self-baseline {
+ align-self: baseline !important;
+.align-self-stretch {
+ align-self: stretch !important;
+.order-first {
+ order: -1 !important;
+.order-0 {
+ order: 0 !important;
+.order-1 {
+ order: 1 !important;
+.order-2 {
+ order: 2 !important;
+.order-3 {
+ order: 3 !important;
+.order-4 {
+ order: 4 !important;
+.order-5 {
+ order: 5 !important;
+.order-last {
+ order: 6 !important;
+.m-0 {
+ margin: 0 !important;
+.m-1 {
+ margin: 0.25rem !important;
+.m-2 {
+ margin: 0.5rem !important;
+.m-3 {
+ margin: 1rem !important;
+.m-4 {
+ margin: 1.5rem !important;
+.m-5 {
+ margin: 3rem !important;
+.m-auto {
+ margin: auto !important;
+.mx-0 {
+ margin-right: 0 !important;
+ margin-left: 0 !important;
+.mx-1 {
+ margin-right: 0.25rem !important;
+ margin-left: 0.25rem !important;
+.mx-2 {
+ margin-right: 0.5rem !important;
+ margin-left: 0.5rem !important;
+.mx-3 {
+ margin-right: 1rem !important;
+ margin-left: 1rem !important;
+.mx-4 {
+ margin-right: 1.5rem !important;
+ margin-left: 1.5rem !important;
+.mx-5 {
+ margin-right: 3rem !important;
+ margin-left: 3rem !important;
+.mx-auto {
+ margin-right: auto !important;
+ margin-left: auto !important;
+.my-0 {
+ margin-top: 0 !important;
+ margin-bottom: 0 !important;
+.my-1 {
+ margin-top: 0.25rem !important;
+ margin-bottom: 0.25rem !important;
+.my-2 {
+ margin-top: 0.5rem !important;
+ margin-bottom: 0.5rem !important;
+.my-3 {
+ margin-top: 1rem !important;
+ margin-bottom: 1rem !important;
+.my-4 {
+ margin-top: 1.5rem !important;
+ margin-bottom: 1.5rem !important;
+.my-5 {
+ margin-top: 3rem !important;
+ margin-bottom: 3rem !important;
+.my-auto {
+ margin-top: auto !important;
+ margin-bottom: auto !important;
+.mt-0 {
+.mt-1 {
+.mt-2 {
+.mt-3 {
+.mt-4 {
+.mt-5 {
+.mt-auto {
+.me-0 {
+.me-1 {
+.me-2 {
+.me-3 {
+.me-4 {
+.me-5 {
+.me-auto {
+.mb-0 {
+.mb-1 {
+.mb-2 {
+.mb-3 {
+.mb-4 {
+.mb-5 {
+.mb-auto {
+.ms-0 {
+.ms-1 {
+.ms-2 {
+.ms-3 {
+.ms-4 {
+.ms-5 {
+.ms-auto {
+.p-0 {
+.p-1 {
+ padding: 0.25rem !important;
+.p-2 {
+ padding: 0.5rem !important;
+.p-3 {
+ padding: 1rem !important;
+.p-4 {
+ padding: 1.5rem !important;
+.p-5 {
+ padding: 3rem !important;
+.px-0 {
+ padding-right: 0 !important;
+ padding-left: 0 !important;
+.px-1 {
+ padding-right: 0.25rem !important;
+ padding-left: 0.25rem !important;
+.px-2 {
+ padding-right: 0.5rem !important;
+ padding-left: 0.5rem !important;
+.px-3 {
+ padding-right: 1rem !important;
+ padding-left: 1rem !important;
+.px-4 {
+ padding-right: 1.5rem !important;
+ padding-left: 1.5rem !important;
+.px-5 {
+ padding-right: 3rem !important;
+ padding-left: 3rem !important;
+.py-0 {
+ padding-top: 0 !important;
+ padding-bottom: 0 !important;
+.py-1 {
+ padding-top: 0.25rem !important;
+ padding-bottom: 0.25rem !important;
+.py-2 {
+ padding-top: 0.5rem !important;
+ padding-bottom: 0.5rem !important;
+.py-3 {
+ padding-top: 1rem !important;
+ padding-bottom: 1rem !important;
+.py-4 {
+ padding-top: 1.5rem !important;
+ padding-bottom: 1.5rem !important;
+.py-5 {
+ padding-top: 3rem !important;
+ padding-bottom: 3rem !important;
+.pt-0 {
+.pt-1 {
+.pt-2 {
+.pt-3 {
+.pt-4 {
+.pt-5 {
+.pe-0 {
+.pe-1 {
+.pe-2 {
+.pe-3 {
+.pe-4 {
+.pe-5 {
+.pb-0 {
+.pb-1 {
+.pb-2 {
+.pb-3 {
+.pb-4 {
+.pb-5 {
+.ps-0 {
+.ps-1 {
+.ps-2 {
+.ps-3 {
+.ps-4 {
+.ps-5 {
+.gap-0 {
+ gap: 0 !important;
+.gap-1 {
+ gap: 0.25rem !important;
+.gap-2 {
+ gap: 0.5rem !important;
+.gap-3 {
+ gap: 1rem !important;
+.gap-4 {
+ gap: 1.5rem !important;
+.gap-5 {
+ gap: 3rem !important;
+.row-gap-0 {
+ row-gap: 0 !important;
+.row-gap-1 {
+ row-gap: 0.25rem !important;
+.row-gap-2 {
+ row-gap: 0.5rem !important;
+.row-gap-3 {
+ row-gap: 1rem !important;
+.row-gap-4 {
+ row-gap: 1.5rem !important;
+.row-gap-5 {
+ row-gap: 3rem !important;
+.column-gap-0 {
+ -moz-column-gap: 0 !important;
+ column-gap: 0 !important;
+.column-gap-1 {
+ -moz-column-gap: 0.25rem !important;
+ column-gap: 0.25rem !important;
+.column-gap-2 {
+ -moz-column-gap: 0.5rem !important;
+ column-gap: 0.5rem !important;
+.column-gap-3 {
+ -moz-column-gap: 1rem !important;
+ column-gap: 1rem !important;
+.column-gap-4 {
+ -moz-column-gap: 1.5rem !important;
+ column-gap: 1.5rem !important;
+.column-gap-5 {
+ -moz-column-gap: 3rem !important;
+ column-gap: 3rem !important;
+.font-monospace {
+ font-family: var(--bs-font-monospace) !important;
+.fs-1 {
+ font-size: calc(1.375rem + 1.5vw) !important;
+.fs-2 {
+ font-size: calc(1.325rem + 0.9vw) !important;
+.fs-3 {
+ font-size: calc(1.3rem + 0.6vw) !important;
+.fs-4 {
+ font-size: calc(1.275rem + 0.3vw) !important;
+.fs-5 {
+ font-size: 1.25rem !important;
+.fs-6 {
+ font-size: 1rem !important;
+.fst-italic {
+ font-style: italic !important;
+.fst-normal {
+ font-style: normal !important;
+.fw-lighter {
+ font-weight: lighter !important;
+.fw-light {
+ font-weight: 300 !important;
+.fw-normal {
+ font-weight: 400 !important;
+.fw-medium {
+ font-weight: 500 !important;
+.fw-semibold {
+ font-weight: 600 !important;
+.fw-bold {
+ font-weight: 700 !important;
+.fw-bolder {
+ font-weight: bolder !important;
+.lh-1 {
+ line-height: 1 !important;
+.lh-sm {
+ line-height: 1.25 !important;
+.lh-base {
+ line-height: 1.5 !important;
+.lh-lg {
+ line-height: 2 !important;
+.text-start {
+ text-align: left !important;
+.text-end {
+ text-align: right !important;
+.text-center {
+ text-align: center !important;
+.text-decoration-none {
+ text-decoration: none !important;
+.text-decoration-underline {
+ text-decoration: underline !important;
+.text-decoration-line-through {
+ text-decoration: line-through !important;
+.text-lowercase {
+ text-transform: lowercase !important;
+.text-uppercase {
+ text-transform: uppercase !important;
+.text-capitalize {
+ text-transform: capitalize !important;
+.text-wrap {
+ white-space: normal !important;
+.text-nowrap {
+/* rtl:begin:remove */
+.text-break {
+ word-wrap: break-word !important;
+ word-break: break-word !important;
+/* rtl:end:remove */
+.text-primary {
+ --bs-text-opacity: 1;
+ color: rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important;
+.text-secondary {
+ color: rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important;
+.text-success {
+ color: rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important;
+.text-info {
+ color: rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important;
+.text-warning {
+ color: rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important;
+.text-danger {
+ color: rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important;
+.text-light {
+ color: rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important;
+.text-dark {
+ color: rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important;
+.text-black {
+ color: rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important;
+.text-white {
+ color: rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important;
+.text-body {
+ color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important;
+.text-muted {
+ color: var(--bs-secondary-color) !important;
+.text-black-50 {
+ color: rgba(0, 0, 0, 0.5) !important;
+.text-white-50 {
+ color: rgba(255, 255, 255, 0.5) !important;
+.text-body-secondary {
+.text-body-tertiary {
+ color: var(--bs-tertiary-color) !important;
+.text-body-emphasis {
+ color: var(--bs-emphasis-color) !important;
+.text-reset {
+ color: inherit !important;
+.text-opacity-25 {
+ --bs-text-opacity: 0.25;
+.text-opacity-50 {
+ --bs-text-opacity: 0.5;
+.text-opacity-75 {
+ --bs-text-opacity: 0.75;
+.text-opacity-100 {
+.text-primary-emphasis {
+ color: var(--bs-primary-text) !important;
+.text-secondary-emphasis {
+ color: var(--bs-secondary-text) !important;
+.text-success-emphasis {
+ color: var(--bs-success-text) !important;
+.text-info-emphasis {
+ color: var(--bs-info-text) !important;
+.text-warning-emphasis {
+ color: var(--bs-warning-text) !important;
+.text-danger-emphasis {
+ color: var(--bs-danger-text) !important;
+.text-light-emphasis {
+ color: var(--bs-light-text) !important;
+.text-dark-emphasis {
+ color: var(--bs-dark-text) !important;
+.bg-primary {
+ --bs-bg-opacity: 1;
+ background-color: rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important;
+.bg-secondary {
+ background-color: rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important;
+.bg-success {
+ background-color: rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important;
+.bg-info {
+ background-color: rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important;
+.bg-warning {
+ background-color: rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important;
+.bg-danger {
+ background-color: rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important;
+.bg-light {
+ background-color: rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important;
+.bg-dark {
+ background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important;
+.bg-black {
+ background-color: rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important;
+.bg-white {
+ background-color: rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important;
+.bg-body {
+ background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important;
+.bg-transparent {
+.bg-body-secondary {
+ background-color: rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity)) !important;
+.bg-body-tertiary {
+ background-color: rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity)) !important;
+.bg-body-emphasis {
+ background-color: rgba(var(--bs-emphasis-bg-rgb), var(--bs-bg-opacity)) !important;
+.bg-opacity-10 {
+ --bs-bg-opacity: 0.1;
+.bg-opacity-25 {
+ --bs-bg-opacity: 0.25;
+.bg-opacity-50 {
+ --bs-bg-opacity: 0.5;
+.bg-opacity-75 {
+ --bs-bg-opacity: 0.75;
+.bg-opacity-100 {
+.bg-primary-subtle {
+ background-color: var(--bs-primary-bg-subtle) !important;
+.bg-secondary-subtle {
+ background-color: var(--bs-secondary-bg-subtle) !important;
+.bg-success-subtle {
+ background-color: var(--bs-success-bg-subtle) !important;
+.bg-info-subtle {
+ background-color: var(--bs-info-bg-subtle) !important;
+.bg-warning-subtle {
+ background-color: var(--bs-warning-bg-subtle) !important;
+.bg-danger-subtle {
+ background-color: var(--bs-danger-bg-subtle) !important;
+.bg-light-subtle {
+ background-color: var(--bs-light-bg-subtle) !important;
+.bg-dark-subtle {
+ background-color: var(--bs-dark-bg-subtle) !important;
+.bg-gradient {
+ background-image: var(--bs-gradient) !important;
+.user-select-all {
+ -webkit-user-select: all !important;
+ -moz-user-select: all !important;
+ user-select: all !important;
+.user-select-auto {
+ -webkit-user-select: auto !important;
+ -moz-user-select: auto !important;
+ user-select: auto !important;
+.user-select-none {
+ -webkit-user-select: none !important;
+ -moz-user-select: none !important;
+ user-select: none !important;
+.pe-none {
+ pointer-events: none !important;
+.pe-auto {
+ pointer-events: auto !important;
+.rounded {
+ border-radius: var(--bs-border-radius) !important;
+.rounded-0 {
+ border-radius: 0 !important;
+.rounded-1 {
+ border-radius: var(--bs-border-radius-sm) !important;
+.rounded-2 {
+.rounded-3 {
+ border-radius: var(--bs-border-radius-lg) !important;
+.rounded-4 {
+ border-radius: var(--bs-border-radius-xl) !important;
+.rounded-5 {
+ border-radius: var(--bs-border-radius-2xl) !important;
+.rounded-circle {
+ border-radius: 50% !important;
+.rounded-pill {
+ border-radius: var(--bs-border-radius-pill) !important;
+.rounded-top {
+ border-top-left-radius: var(--bs-border-radius) !important;
+ border-top-right-radius: var(--bs-border-radius) !important;
+.rounded-top-0 {
+ border-top-left-radius: 0 !important;
+ border-top-right-radius: 0 !important;
+.rounded-top-1 {
+ border-top-left-radius: var(--bs-border-radius-sm) !important;
+ border-top-right-radius: var(--bs-border-radius-sm) !important;
+.rounded-top-2 {
+.rounded-top-3 {
+ border-top-left-radius: var(--bs-border-radius-lg) !important;
+ border-top-right-radius: var(--bs-border-radius-lg) !important;
+.rounded-top-4 {
+ border-top-left-radius: var(--bs-border-radius-xl) !important;
+ border-top-right-radius: var(--bs-border-radius-xl) !important;
+.rounded-top-5 {
+ border-top-left-radius: var(--bs-border-radius-2xl) !important;
+ border-top-right-radius: var(--bs-border-radius-2xl) !important;
+.rounded-top-circle {
+ border-top-left-radius: 50% !important;
+ border-top-right-radius: 50% !important;
+.rounded-top-pill {
+ border-top-left-radius: var(--bs-border-radius-pill) !important;
+ border-top-right-radius: var(--bs-border-radius-pill) !important;
+.rounded-end {
+ border-bottom-right-radius: var(--bs-border-radius) !important;
+.rounded-end-0 {
+ border-bottom-right-radius: 0 !important;
+.rounded-end-1 {
+ border-bottom-right-radius: var(--bs-border-radius-sm) !important;
+.rounded-end-2 {
+.rounded-end-3 {
+ border-bottom-right-radius: var(--bs-border-radius-lg) !important;
+.rounded-end-4 {
+ border-bottom-right-radius: var(--bs-border-radius-xl) !important;
+.rounded-end-5 {
+ border-bottom-right-radius: var(--bs-border-radius-2xl) !important;
+.rounded-end-circle {
+ border-bottom-right-radius: 50% !important;
+.rounded-end-pill {
+ border-bottom-right-radius: var(--bs-border-radius-pill) !important;
+.rounded-bottom {
+ border-bottom-left-radius: var(--bs-border-radius) !important;
+.rounded-bottom-0 {
+ border-bottom-left-radius: 0 !important;
+.rounded-bottom-1 {
+ border-bottom-left-radius: var(--bs-border-radius-sm) !important;
+.rounded-bottom-2 {
+.rounded-bottom-3 {
+ border-bottom-left-radius: var(--bs-border-radius-lg) !important;
+.rounded-bottom-4 {
+ border-bottom-left-radius: var(--bs-border-radius-xl) !important;
+.rounded-bottom-5 {
+ border-bottom-left-radius: var(--bs-border-radius-2xl) !important;
+.rounded-bottom-circle {
+ border-bottom-left-radius: 50% !important;
+.rounded-bottom-pill {
+ border-bottom-left-radius: var(--bs-border-radius-pill) !important;
+.rounded-start {
+.rounded-start-0 {
+.rounded-start-1 {
+.rounded-start-2 {
+.rounded-start-3 {
+.rounded-start-4 {
+.rounded-start-5 {
+.rounded-start-circle {
+.rounded-start-pill {
+.visible {
+.invisible {
+ visibility: hidden !important;
+.z-n1 {
+ z-index: -1 !important;
+.z-0 {
+ z-index: 0 !important;
+.z-1 {
+ z-index: 1 !important;
+.z-2 {
+ z-index: 2 !important;
+.z-3 {
+ z-index: 3 !important;
+ .float-sm-start {
+ .float-sm-end {
+ .float-sm-none {
+ .object-fit-sm-contain {
+ .object-fit-sm-cover {
+ .object-fit-sm-fill {
+ .object-fit-sm-scale {
+ .object-fit-sm-none {
+ .d-sm-inline {
+ .d-sm-inline-block {
+ .d-sm-block {
+ .d-sm-grid {
+ .d-sm-table {
+ .d-sm-table-row {
+ .d-sm-table-cell {
+ .d-sm-flex {
+ .d-sm-inline-flex {
+ .d-sm-none {
+ .flex-sm-fill {
+ .flex-sm-row {
+ .flex-sm-column {
+ .flex-sm-row-reverse {
+ .flex-sm-column-reverse {
+ .flex-sm-grow-0 {
+ .flex-sm-grow-1 {
+ .flex-sm-shrink-0 {
+ .flex-sm-shrink-1 {
+ .flex-sm-wrap {
+ .flex-sm-nowrap {
+ .flex-sm-wrap-reverse {
+ .justify-content-sm-start {
+ .justify-content-sm-end {
+ .justify-content-sm-center {
+ .justify-content-sm-between {
+ .justify-content-sm-around {
+ .justify-content-sm-evenly {
+ .align-items-sm-start {
+ .align-items-sm-end {
+ .align-items-sm-center {
+ .align-items-sm-baseline {
+ .align-items-sm-stretch {
+ .align-content-sm-start {
+ .align-content-sm-end {
+ .align-content-sm-center {
+ .align-content-sm-between {
+ .align-content-sm-around {
+ .align-content-sm-stretch {
+ .align-self-sm-auto {
+ .align-self-sm-start {
+ .align-self-sm-end {
+ .align-self-sm-center {
+ .align-self-sm-baseline {
+ .align-self-sm-stretch {
+ .order-sm-first {
+ .order-sm-0 {
+ .order-sm-1 {
+ .order-sm-2 {
+ .order-sm-3 {
+ .order-sm-4 {
+ .order-sm-5 {
+ .order-sm-last {
+ .m-sm-0 {
+ .m-sm-1 {
+ .m-sm-2 {
+ .m-sm-3 {
+ .m-sm-4 {
+ .m-sm-5 {
+ .m-sm-auto {
+ .mx-sm-0 {
+ .mx-sm-1 {
+ .mx-sm-2 {
+ .mx-sm-3 {
+ .mx-sm-4 {
+ .mx-sm-5 {
+ .mx-sm-auto {
+ .my-sm-0 {
+ .my-sm-1 {
+ .my-sm-2 {
+ .my-sm-3 {
+ .my-sm-4 {
+ .my-sm-5 {
+ .my-sm-auto {
+ .mt-sm-0 {
+ .mt-sm-1 {
+ .mt-sm-2 {
+ .mt-sm-3 {
+ .mt-sm-4 {
+ .mt-sm-5 {
+ .mt-sm-auto {
+ .me-sm-0 {
+ .me-sm-1 {
+ .me-sm-2 {
+ .me-sm-3 {
+ .me-sm-4 {
+ .me-sm-5 {
+ .me-sm-auto {
+ .mb-sm-0 {
+ .mb-sm-1 {
+ .mb-sm-2 {
+ .mb-sm-3 {
+ .mb-sm-4 {
+ .mb-sm-5 {
+ .mb-sm-auto {
+ .ms-sm-0 {
+ .ms-sm-1 {
+ .ms-sm-2 {
+ .ms-sm-3 {
+ .ms-sm-4 {
+ .ms-sm-5 {
+ .ms-sm-auto {
+ .p-sm-0 {
+ .p-sm-1 {
+ .p-sm-2 {
+ .p-sm-3 {
+ .p-sm-4 {
+ .p-sm-5 {
+ .px-sm-0 {
+ .px-sm-1 {
+ .px-sm-2 {
+ .px-sm-3 {
+ .px-sm-4 {
+ .px-sm-5 {
+ .py-sm-0 {
+ .py-sm-1 {
+ .py-sm-2 {
+ .py-sm-3 {
+ .py-sm-4 {
+ .py-sm-5 {
+ .pt-sm-0 {
+ .pt-sm-1 {
+ .pt-sm-2 {
+ .pt-sm-3 {
+ .pt-sm-4 {
+ .pt-sm-5 {
+ .pe-sm-0 {
+ .pe-sm-1 {
+ .pe-sm-2 {
+ .pe-sm-3 {
+ .pe-sm-4 {
+ .pe-sm-5 {
+ .pb-sm-0 {
+ .pb-sm-1 {
+ .pb-sm-2 {
+ .pb-sm-3 {
+ .pb-sm-4 {
+ .pb-sm-5 {
+ .ps-sm-0 {
+ .ps-sm-1 {
+ .ps-sm-2 {
+ .ps-sm-3 {
+ .ps-sm-4 {
+ .ps-sm-5 {
+ .gap-sm-0 {
+ .gap-sm-1 {
+ .gap-sm-2 {
+ .gap-sm-3 {
+ .gap-sm-4 {
+ .gap-sm-5 {
+ .row-gap-sm-0 {
+ .row-gap-sm-1 {
+ .row-gap-sm-2 {
+ .row-gap-sm-3 {
+ .row-gap-sm-4 {
+ .row-gap-sm-5 {
+ .column-gap-sm-0 {
+ .column-gap-sm-1 {
+ .column-gap-sm-2 {
+ .column-gap-sm-3 {
+ .column-gap-sm-4 {
+ .column-gap-sm-5 {
+ .text-sm-start {
+ .text-sm-end {
+ .text-sm-center {
+ .float-md-start {
+ .float-md-end {
+ .float-md-none {
+ .object-fit-md-contain {
+ .object-fit-md-cover {
+ .object-fit-md-fill {
+ .object-fit-md-scale {
+ .object-fit-md-none {
+ .d-md-inline {
+ .d-md-inline-block {
+ .d-md-block {
+ .d-md-grid {
+ .d-md-table {
+ .d-md-table-row {
+ .d-md-table-cell {
+ .d-md-flex {
+ .d-md-inline-flex {
+ .d-md-none {
+ .flex-md-fill {
+ .flex-md-row {
+ .flex-md-column {
+ .flex-md-row-reverse {
+ .flex-md-column-reverse {
+ .flex-md-grow-0 {
+ .flex-md-grow-1 {
+ .flex-md-shrink-0 {
+ .flex-md-shrink-1 {
+ .flex-md-wrap {
+ .flex-md-nowrap {
+ .flex-md-wrap-reverse {
+ .justify-content-md-start {
+ .justify-content-md-end {
+ .justify-content-md-center {
+ .justify-content-md-between {
+ .justify-content-md-around {
+ .justify-content-md-evenly {
+ .align-items-md-start {
+ .align-items-md-end {
+ .align-items-md-center {
+ .align-items-md-baseline {
+ .align-items-md-stretch {
+ .align-content-md-start {
+ .align-content-md-end {
+ .align-content-md-center {
+ .align-content-md-between {
+ .align-content-md-around {
+ .align-content-md-stretch {
+ .align-self-md-auto {
+ .align-self-md-start {
+ .align-self-md-end {
+ .align-self-md-center {
+ .align-self-md-baseline {
+ .align-self-md-stretch {
+ .order-md-first {
+ .order-md-0 {
+ .order-md-1 {
+ .order-md-2 {
+ .order-md-3 {
+ .order-md-4 {
+ .order-md-5 {
+ .order-md-last {
+ .m-md-0 {
+ .m-md-1 {
+ .m-md-2 {
+ .m-md-3 {
+ .m-md-4 {
+ .m-md-5 {
+ .m-md-auto {
+ .mx-md-0 {
+ .mx-md-1 {
+ .mx-md-2 {
+ .mx-md-3 {
+ .mx-md-4 {
+ .mx-md-5 {
+ .mx-md-auto {
+ .my-md-0 {
+ .my-md-1 {
+ .my-md-2 {
+ .my-md-3 {
+ .my-md-4 {
+ .my-md-5 {
+ .my-md-auto {
+ .mt-md-0 {
+ .mt-md-1 {
+ .mt-md-2 {
+ .mt-md-3 {
+ .mt-md-4 {
+ .mt-md-5 {
+ .mt-md-auto {
+ .me-md-0 {
+ .me-md-1 {
+ .me-md-2 {
+ .me-md-3 {
+ .me-md-4 {
+ .me-md-5 {
+ .me-md-auto {
+ .mb-md-0 {
+ .mb-md-1 {
+ .mb-md-2 {
+ .mb-md-3 {
+ .mb-md-4 {
+ .mb-md-5 {
+ .mb-md-auto {
+ .ms-md-0 {
+ .ms-md-1 {
+ .ms-md-2 {
+ .ms-md-3 {
+ .ms-md-4 {
+ .ms-md-5 {
+ .ms-md-auto {
+ .p-md-0 {
+ .p-md-1 {
+ .p-md-2 {
+ .p-md-3 {
+ .p-md-4 {
+ .p-md-5 {
+ .px-md-0 {
+ .px-md-1 {
+ .px-md-2 {
+ .px-md-3 {
+ .px-md-4 {
+ .px-md-5 {
+ .py-md-0 {
+ .py-md-1 {
+ .py-md-2 {
+ .py-md-3 {
+ .py-md-4 {
+ .py-md-5 {
+ .pt-md-0 {
+ .pt-md-1 {
+ .pt-md-2 {
+ .pt-md-3 {
+ .pt-md-4 {
+ .pt-md-5 {
+ .pe-md-0 {
+ .pe-md-1 {
+ .pe-md-2 {
+ .pe-md-3 {
+ .pe-md-4 {
+ .pe-md-5 {
+ .pb-md-0 {
+ .pb-md-1 {
+ .pb-md-2 {
+ .pb-md-3 {
+ .pb-md-4 {
+ .pb-md-5 {
+ .ps-md-0 {
+ .ps-md-1 {
+ .ps-md-2 {
+ .ps-md-3 {
+ .ps-md-4 {
+ .ps-md-5 {
+ .gap-md-0 {
+ .gap-md-1 {
+ .gap-md-2 {
+ .gap-md-3 {
+ .gap-md-4 {
+ .gap-md-5 {
+ .row-gap-md-0 {
+ .row-gap-md-1 {
+ .row-gap-md-2 {
+ .row-gap-md-3 {
+ .row-gap-md-4 {
+ .row-gap-md-5 {
+ .column-gap-md-0 {
+ .column-gap-md-1 {
+ .column-gap-md-2 {
+ .column-gap-md-3 {
+ .column-gap-md-4 {
+ .column-gap-md-5 {
+ .text-md-start {
+ .text-md-end {
+ .text-md-center {
+ .float-lg-start {
+ .float-lg-end {
+ .float-lg-none {
+ .object-fit-lg-contain {
+ .object-fit-lg-cover {
+ .object-fit-lg-fill {
+ .object-fit-lg-scale {
+ .object-fit-lg-none {
+ .d-lg-inline {
+ .d-lg-inline-block {
+ .d-lg-block {
+ .d-lg-grid {
+ .d-lg-table {
+ .d-lg-table-row {
+ .d-lg-table-cell {
+ .d-lg-flex {
+ .d-lg-inline-flex {
+ .d-lg-none {
+ .flex-lg-fill {
+ .flex-lg-row {
+ .flex-lg-column {
+ .flex-lg-row-reverse {
+ .flex-lg-column-reverse {
+ .flex-lg-grow-0 {
+ .flex-lg-grow-1 {
+ .flex-lg-shrink-0 {
+ .flex-lg-shrink-1 {
+ .flex-lg-wrap {
+ .flex-lg-nowrap {
+ .flex-lg-wrap-reverse {
+ .justify-content-lg-start {
+ .justify-content-lg-end {
+ .justify-content-lg-center {
+ .justify-content-lg-between {
+ .justify-content-lg-around {
+ .justify-content-lg-evenly {
+ .align-items-lg-start {
+ .align-items-lg-end {
+ .align-items-lg-center {
+ .align-items-lg-baseline {
+ .align-items-lg-stretch {
+ .align-content-lg-start {
+ .align-content-lg-end {
+ .align-content-lg-center {
+ .align-content-lg-between {
+ .align-content-lg-around {
+ .align-content-lg-stretch {
+ .align-self-lg-auto {
+ .align-self-lg-start {
+ .align-self-lg-end {
+ .align-self-lg-center {
+ .align-self-lg-baseline {
+ .align-self-lg-stretch {
+ .order-lg-first {
+ .order-lg-0 {
+ .order-lg-1 {
+ .order-lg-2 {
+ .order-lg-3 {
+ .order-lg-4 {
+ .order-lg-5 {
+ .order-lg-last {
+ .m-lg-0 {
+ .m-lg-1 {
+ .m-lg-2 {
+ .m-lg-3 {
+ .m-lg-4 {
+ .m-lg-5 {
+ .m-lg-auto {
+ .mx-lg-0 {
+ .mx-lg-1 {
+ .mx-lg-2 {
+ .mx-lg-3 {
+ .mx-lg-4 {
+ .mx-lg-5 {
+ .mx-lg-auto {
+ .my-lg-0 {
+ .my-lg-1 {
+ .my-lg-2 {
+ .my-lg-3 {
+ .my-lg-4 {
+ .my-lg-5 {
+ .my-lg-auto {
+ .mt-lg-0 {
+ .mt-lg-1 {
+ .mt-lg-2 {
+ .mt-lg-3 {
+ .mt-lg-4 {
+ .mt-lg-5 {
+ .mt-lg-auto {
+ .me-lg-0 {
+ .me-lg-1 {
+ .me-lg-2 {
+ .me-lg-3 {
+ .me-lg-4 {
+ .me-lg-5 {
+ .me-lg-auto {
+ .mb-lg-0 {
+ .mb-lg-1 {
+ .mb-lg-2 {
+ .mb-lg-3 {
+ .mb-lg-4 {
+ .mb-lg-5 {
+ .mb-lg-auto {
+ .ms-lg-0 {
+ .ms-lg-1 {
+ .ms-lg-2 {
+ .ms-lg-3 {
+ .ms-lg-4 {
+ .ms-lg-5 {
+ .ms-lg-auto {
+ .p-lg-0 {
+ .p-lg-1 {
+ .p-lg-2 {
+ .p-lg-3 {
+ .p-lg-4 {
+ .p-lg-5 {
+ .px-lg-0 {
+ .px-lg-1 {
+ .px-lg-2 {
+ .px-lg-3 {
+ .px-lg-4 {
+ .px-lg-5 {
+ .py-lg-0 {
+ .py-lg-1 {
+ .py-lg-2 {
+ .py-lg-3 {
+ .py-lg-4 {
+ .py-lg-5 {
+ .pt-lg-0 {
+ .pt-lg-1 {
+ .pt-lg-2 {
+ .pt-lg-3 {
+ .pt-lg-4 {
+ .pt-lg-5 {
+ .pe-lg-0 {
+ .pe-lg-1 {
+ .pe-lg-2 {
+ .pe-lg-3 {
+ .pe-lg-4 {
+ .pe-lg-5 {
+ .pb-lg-0 {
+ .pb-lg-1 {
+ .pb-lg-2 {
+ .pb-lg-3 {
+ .pb-lg-4 {
+ .pb-lg-5 {
+ .ps-lg-0 {
+ .ps-lg-1 {
+ .ps-lg-2 {
+ .ps-lg-3 {
+ .ps-lg-4 {
+ .ps-lg-5 {
+ .gap-lg-0 {
+ .gap-lg-1 {
+ .gap-lg-2 {
+ .gap-lg-3 {
+ .gap-lg-4 {
+ .gap-lg-5 {
+ .row-gap-lg-0 {
+ .row-gap-lg-1 {
+ .row-gap-lg-2 {
+ .row-gap-lg-3 {
+ .row-gap-lg-4 {
+ .row-gap-lg-5 {
+ .column-gap-lg-0 {
+ .column-gap-lg-1 {
+ .column-gap-lg-2 {
+ .column-gap-lg-3 {
+ .column-gap-lg-4 {
+ .column-gap-lg-5 {
+ .text-lg-start {
+ .text-lg-end {
+ .text-lg-center {
+ .float-xl-start {
+ .float-xl-end {
+ .float-xl-none {
+ .object-fit-xl-contain {
+ .object-fit-xl-cover {
+ .object-fit-xl-fill {
+ .object-fit-xl-scale {
+ .object-fit-xl-none {
+ .d-xl-inline {
+ .d-xl-inline-block {
+ .d-xl-block {
+ .d-xl-grid {
+ .d-xl-table {
+ .d-xl-table-row {
+ .d-xl-table-cell {
+ .d-xl-flex {
+ .d-xl-inline-flex {
+ .d-xl-none {
+ .flex-xl-fill {
+ .flex-xl-row {
+ .flex-xl-column {
+ .flex-xl-row-reverse {
+ .flex-xl-column-reverse {
+ .flex-xl-grow-0 {
+ .flex-xl-grow-1 {
+ .flex-xl-shrink-0 {
+ .flex-xl-shrink-1 {
+ .flex-xl-wrap {
+ .flex-xl-nowrap {
+ .flex-xl-wrap-reverse {
+ .justify-content-xl-start {
+ .justify-content-xl-end {
+ .justify-content-xl-center {
+ .justify-content-xl-between {
+ .justify-content-xl-around {
+ .justify-content-xl-evenly {
+ .align-items-xl-start {
+ .align-items-xl-end {
+ .align-items-xl-center {
+ .align-items-xl-baseline {
+ .align-items-xl-stretch {
+ .align-content-xl-start {
+ .align-content-xl-end {
+ .align-content-xl-center {
+ .align-content-xl-between {
+ .align-content-xl-around {
+ .align-content-xl-stretch {
+ .align-self-xl-auto {
+ .align-self-xl-start {
+ .align-self-xl-end {
+ .align-self-xl-center {
+ .align-self-xl-baseline {
+ .align-self-xl-stretch {
+ .order-xl-first {
+ .order-xl-0 {
+ .order-xl-1 {
+ .order-xl-2 {
+ .order-xl-3 {
+ .order-xl-4 {
+ .order-xl-5 {
+ .order-xl-last {
+ .m-xl-0 {
+ .m-xl-1 {
+ .m-xl-2 {
+ .m-xl-3 {
+ .m-xl-4 {
+ .m-xl-5 {
+ .m-xl-auto {
+ .mx-xl-0 {
+ .mx-xl-1 {
+ .mx-xl-2 {
+ .mx-xl-3 {
+ .mx-xl-4 {
+ .mx-xl-5 {
+ .mx-xl-auto {
+ .my-xl-0 {
+ .my-xl-1 {
+ .my-xl-2 {
+ .my-xl-3 {
+ .my-xl-4 {
+ .my-xl-5 {
+ .my-xl-auto {
+ .mt-xl-0 {
+ .mt-xl-1 {
+ .mt-xl-2 {
+ .mt-xl-3 {
+ .mt-xl-4 {
+ .mt-xl-5 {
+ .mt-xl-auto {
+ .me-xl-0 {
+ .me-xl-1 {
+ .me-xl-2 {
+ .me-xl-3 {
+ .me-xl-4 {
+ .me-xl-5 {
+ .me-xl-auto {
+ .mb-xl-0 {
+ .mb-xl-1 {
+ .mb-xl-2 {
+ .mb-xl-3 {
+ .mb-xl-4 {
+ .mb-xl-5 {
+ .mb-xl-auto {
+ .ms-xl-0 {
+ .ms-xl-1 {
+ .ms-xl-2 {
+ .ms-xl-3 {
+ .ms-xl-4 {
+ .ms-xl-5 {
+ .ms-xl-auto {
+ .p-xl-0 {
+ .p-xl-1 {
+ .p-xl-2 {
+ .p-xl-3 {
+ .p-xl-4 {
+ .p-xl-5 {
+ .px-xl-0 {
+ .px-xl-1 {
+ .px-xl-2 {
+ .px-xl-3 {
+ .px-xl-4 {
+ .px-xl-5 {
+ .py-xl-0 {
+ .py-xl-1 {
+ .py-xl-2 {
+ .py-xl-3 {
+ .py-xl-4 {
+ .py-xl-5 {
+ .pt-xl-0 {
+ .pt-xl-1 {
+ .pt-xl-2 {
+ .pt-xl-3 {
+ .pt-xl-4 {
+ .pt-xl-5 {
+ .pe-xl-0 {
+ .pe-xl-1 {
+ .pe-xl-2 {
+ .pe-xl-3 {
+ .pe-xl-4 {
+ .pe-xl-5 {
+ .pb-xl-0 {
+ .pb-xl-1 {
+ .pb-xl-2 {
+ .pb-xl-3 {
+ .pb-xl-4 {
+ .pb-xl-5 {
+ .ps-xl-0 {
+ .ps-xl-1 {
+ .ps-xl-2 {
+ .ps-xl-3 {
+ .ps-xl-4 {
+ .ps-xl-5 {
+ .gap-xl-0 {
+ .gap-xl-1 {
+ .gap-xl-2 {
+ .gap-xl-3 {
+ .gap-xl-4 {
+ .gap-xl-5 {
+ .row-gap-xl-0 {
+ .row-gap-xl-1 {
+ .row-gap-xl-2 {
+ .row-gap-xl-3 {
+ .row-gap-xl-4 {
+ .row-gap-xl-5 {
+ .column-gap-xl-0 {
+ .column-gap-xl-1 {
+ .column-gap-xl-2 {
+ .column-gap-xl-3 {
+ .column-gap-xl-4 {
+ .column-gap-xl-5 {
+ .text-xl-start {
+ .text-xl-end {
+ .text-xl-center {
+ .float-xxl-start {
+ .float-xxl-end {
+ .float-xxl-none {
+ .object-fit-xxl-contain {
+ .object-fit-xxl-cover {
+ .object-fit-xxl-fill {
+ .object-fit-xxl-scale {
+ .object-fit-xxl-none {
+ .d-xxl-inline {
+ .d-xxl-inline-block {
+ .d-xxl-block {
+ .d-xxl-grid {
+ .d-xxl-table {
+ .d-xxl-table-row {
+ .d-xxl-table-cell {
+ .d-xxl-flex {
+ .d-xxl-inline-flex {
+ .d-xxl-none {
+ .flex-xxl-fill {
+ .flex-xxl-row {
+ .flex-xxl-column {
+ .flex-xxl-row-reverse {
+ .flex-xxl-column-reverse {
+ .flex-xxl-grow-0 {
+ .flex-xxl-grow-1 {
+ .flex-xxl-shrink-0 {
+ .flex-xxl-shrink-1 {
+ .flex-xxl-wrap {
+ .flex-xxl-nowrap {
+ .flex-xxl-wrap-reverse {
+ .justify-content-xxl-start {
+ .justify-content-xxl-end {
+ .justify-content-xxl-center {
+ .justify-content-xxl-between {
+ .justify-content-xxl-around {
+ .justify-content-xxl-evenly {
+ .align-items-xxl-start {
+ .align-items-xxl-end {
+ .align-items-xxl-center {
+ .align-items-xxl-baseline {
+ .align-items-xxl-stretch {
+ .align-content-xxl-start {
+ .align-content-xxl-end {
+ .align-content-xxl-center {
+ .align-content-xxl-between {
+ .align-content-xxl-around {
+ .align-content-xxl-stretch {
+ .align-self-xxl-auto {
+ .align-self-xxl-start {
+ .align-self-xxl-end {
+ .align-self-xxl-center {
+ .align-self-xxl-baseline {
+ .align-self-xxl-stretch {
+ .order-xxl-first {
+ .order-xxl-0 {
+ .order-xxl-1 {
+ .order-xxl-2 {
+ .order-xxl-3 {
+ .order-xxl-4 {
+ .order-xxl-5 {
+ .order-xxl-last {
+ .m-xxl-0 {
+ .m-xxl-1 {
+ .m-xxl-2 {
+ .m-xxl-3 {
+ .m-xxl-4 {
+ .m-xxl-5 {
+ .m-xxl-auto {
+ .mx-xxl-0 {
+ .mx-xxl-1 {
+ .mx-xxl-2 {
+ .mx-xxl-3 {
+ .mx-xxl-4 {
+ .mx-xxl-5 {
+ .mx-xxl-auto {
+ .my-xxl-0 {
+ .my-xxl-1 {
+ .my-xxl-2 {
+ .my-xxl-3 {
+ .my-xxl-4 {
+ .my-xxl-5 {
+ .my-xxl-auto {
+ .mt-xxl-0 {
+ .mt-xxl-1 {
+ .mt-xxl-2 {
+ .mt-xxl-3 {
+ .mt-xxl-4 {
+ .mt-xxl-5 {
+ .mt-xxl-auto {
+ .me-xxl-0 {
+ .me-xxl-1 {
+ .me-xxl-2 {
+ .me-xxl-3 {
+ .me-xxl-4 {
+ .me-xxl-5 {
+ .me-xxl-auto {
+ .mb-xxl-0 {
+ .mb-xxl-1 {
+ .mb-xxl-2 {
+ .mb-xxl-3 {
+ .mb-xxl-4 {
+ .mb-xxl-5 {
+ .mb-xxl-auto {
+ .ms-xxl-0 {
+ .ms-xxl-1 {
+ .ms-xxl-2 {
+ .ms-xxl-3 {
+ .ms-xxl-4 {
+ .ms-xxl-5 {
+ .ms-xxl-auto {
+ .p-xxl-0 {
+ .p-xxl-1 {
+ .p-xxl-2 {
+ .p-xxl-3 {
+ .p-xxl-4 {
+ .p-xxl-5 {
+ .px-xxl-0 {
+ .px-xxl-1 {
+ .px-xxl-2 {
+ .px-xxl-3 {
+ .px-xxl-4 {
+ .px-xxl-5 {
+ .py-xxl-0 {
+ .py-xxl-1 {
+ .py-xxl-2 {
+ .py-xxl-3 {
+ .py-xxl-4 {
+ .py-xxl-5 {
+ .pt-xxl-0 {
+ .pt-xxl-1 {
+ .pt-xxl-2 {
+ .pt-xxl-3 {
+ .pt-xxl-4 {
+ .pt-xxl-5 {
+ .pe-xxl-0 {
+ .pe-xxl-1 {
+ .pe-xxl-2 {
+ .pe-xxl-3 {
+ .pe-xxl-4 {
+ .pe-xxl-5 {
+ .pb-xxl-0 {
+ .pb-xxl-1 {
+ .pb-xxl-2 {
+ .pb-xxl-3 {
+ .pb-xxl-4 {
+ .pb-xxl-5 {
+ .ps-xxl-0 {
+ .ps-xxl-1 {
+ .ps-xxl-2 {
+ .ps-xxl-3 {
+ .ps-xxl-4 {
+ .ps-xxl-5 {
+ .gap-xxl-0 {
+ .gap-xxl-1 {
+ .gap-xxl-2 {
+ .gap-xxl-3 {
+ .gap-xxl-4 {
+ .gap-xxl-5 {
+ .row-gap-xxl-0 {
+ .row-gap-xxl-1 {
+ .row-gap-xxl-2 {
+ .row-gap-xxl-3 {
+ .row-gap-xxl-4 {
+ .row-gap-xxl-5 {
+ .column-gap-xxl-0 {
+ .column-gap-xxl-1 {
+ .column-gap-xxl-2 {
+ .column-gap-xxl-3 {
+ .column-gap-xxl-4 {
+ .column-gap-xxl-5 {
+ .text-xxl-start {
+ .text-xxl-end {
+ .text-xxl-center {
+ .fs-1 {
+ font-size: 2.5rem !important;
+ .fs-2 {
+ font-size: 2rem !important;
+ .fs-3 {
+ font-size: 1.75rem !important;
+ .fs-4 {
+ font-size: 1.5rem !important;
+@media print {
+ .d-print-inline {
+ .d-print-inline-block {
+ .d-print-block {
+ .d-print-grid {
+ .d-print-table {
+ .d-print-table-row {
+ .d-print-table-cell {
+ .d-print-flex {
+ .d-print-inline-flex {
+ .d-print-none {
+/*# sourceMappingURL=bootstrap.css.map */
@@ -0,0 +1,14 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+@layer components {
+ .btn-cancel {
+ @apply py-2 px-4 bg-amber-50 text-black font-semibold rounded-lg shadow-md hover:bg-amber-200 focus:outline-none focus:ring-2 focus:ring-amber-400 focus:ring-opacity-75;
+ .btn-primary {
+ @apply py-2 px-4 bg-blue-500 text-white font-semibold rounded-lg shadow-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75;
+const selectAllCheckboxes = document.querySelector("#select-all");
+const checkboxes = document.querySelectorAll(".checkbox");
+const deleteBtn = document.querySelector("#delete");
+const redriveBtn = document.querySelector("#redrive");
+selectAllCheckboxes.addEventListener("click", eventCheckBox);
+/**
+ * @description Event handler for select all checkbox
+function eventCheckBox() {
+ if (selectAllCheckboxes.checked) {
+ checkboxes.forEach((checkbox) => {
+ checkbox.checked = true;
+ });
+ checkbox.checked = false;
+ * @description Event handler for checkbox to enable/disable delete button
+function handleCheckboxChange() {
+ var atLeastOneChecked = false;
+ checkboxes.forEach(function (checkbox) {
+ if (checkbox.checked) {
+ atLeastOneChecked = true;
+ deleteBtn.disabled = !atLeastOneChecked;
+ redriveBtn.disabled = !atLeastOneChecked;
+checkboxes.forEach((checkbox) => {
+ checkbox.addEventListener("change", handleCheckboxChange);
+});
+selectAllCheckboxes.addEventListener("click", handleCheckboxChange);
@@ -0,0 +1,9 @@
+#home-table caption {
+/* See flashes.gohtml */
+#warning-emj {
+ font-size: 1.7rem;
+ font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M2 42 24 4l22 38Zm5.2-3h33.6L24 10Zm17-2.85q.65 0 1.075-.425.425-.425.425-1.075 0-.65-.425-1.075-.425-.425-1.075-.425-.65 0-1.075.425Q22.7 34 22.7 34.65q0 .65.425 1.075.425.425 1.075.425Zm-1.5-5.55h3V19.4h-3Zm1.3-6.1Z"/></svg>
@@ -0,0 +1,23 @@
+package front
+ "embed"
+//go:embed assets
+var Assets embed.FS
+//go:embed templates/confirm.gohtml
+var Confirm string
+//go:embed templates/flashes.gohtml
+var Flashes string
+//go:embed templates/home.gohtml
+var Home string
+//go:embed templates/queue.gohtml
+var QueueGet string
+//go:embed templates/500.gohtml
+var Err500 string
@@ -0,0 +1,6 @@
+{
+ "devDependencies": {
+ "prettier": "^2.8.4"
+ "dependencies": {}
@@ -0,0 +1,16 @@
+{{define "500" }}
+ <!DOCTYPE html>
+ <html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <title>Erreur serveur</title>
+ <link rel="stylesheet" href="/assets/bootstrap.min.css">
+ </head>
+ <body>
+ <main class="container mt-5">
+ <div class="row">Erreur serveur. Impossible de poursuivre</div>
+ <div class="row"><a href="/">Retour à l'accueil</a></div>
+ </main>
+ </body>
+ </html>
+{{end}}
@@ -0,0 +1,46 @@
+{{define "confirm" -}}
+ <title>Confirmation needed</title>
+ <link rel="stylesheet" href="/assets/styles.css"/>
+ <h1 class="display-4 py-4">Confirmation needed</h1>
+ {{ template "flashes" .flashes }}
+ <div class="border border-{{ .level }} rounded p-3">
+ {{ if .question }}
+ <div>
+ <h3 class="text-{{ .level }}">{{ .question }}</h3>
+ </div>
+ {{ end }}
+ <div class="mt-3">
+ <p class="fs-4 text">{{ .description }}</p>
+ <form method="post" action="{{ .action }}">
+ {{ if .messages }}
+ <ul>
+ {{range $id, $message := .messages}}
+ <li>
+ {{ $message.MessageId }}
+ <input type="hidden" name="id-{{ $id }}" value="on"/>
+ <input type="hidden" name="mid-{{ $id }}" value="{{ $message.MessageId }}"/>
+ <input type="hidden" name="rh-{{ $id }}" value="{{ $message.ReceiptHandle }}"/>
+ </li>
+ {{end}}
+ </ul>
+ <input type="hidden" name="_csrf" value="{{ .csrf }}"/>
+ <a href="{{ .cancelURL }}" class="btn btn-outline-dark btn-lg px-4">{{ .cancel }}</a>
+ {{ if .confirm -}}
+ <button class="btn btn-{{ .level }} btn-lg px-5">{{ .confirm }}</button>{{- end }}
+ </form>
+ <script src="/assets/bootstrap.js"></script>
+{{ define "flashes" -}}
+ {{ if . }}
+ <div class="alert alert-secondary d-flex align-items-center p-3 bg-warning bg-opacity-10 border border-warning rounded mb-4 alert-dismissible fade show">
+ <span id="warning-emj">⚠️</span>
+ <ul class="m-0">
+ {{ range . }}
+ <li>{{ . }}</li>
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+{{ end }}
@@ -0,0 +1,93 @@
+{{ define "home" -}}
+ <title>SQS Redriver</title>
+ <main class="container">
+ <div class="container mt-5">
+ <nav aria-label="breadcrumb">
+ <ol class="breadcrumb">
+ <li class="breadcrumb-item">Home</li>
+ </ol>
+ </nav>
+ <h1 class="text-3xl font-bold underline">SQS Queues
+ {{ if .prefix }}
+ matching prefix: {{ .prefix }}
+ {{ end}}
+ </h1>
+ <table id="home-table" class="table">
+ <caption>Having DLQs not allowing any source queue is a configuration anomaly.
+ So is having a DLQ allowing a source queue and that source using another DLQ instead.
+ </caption>
+ <thead>
+ <th scope="col">Queue name</th>
+ <th scope="col">DLQ name</th>
+ </thead>
+ <tbody>
+ {{ range .rows }}
+ {{ template "home-row" . }}
+ </tbody>
+ </table>
+{{ define "home-row" }}
+ <tr>
+ <td>{{ template "home-cell" (index . 0) }}</td>
+ <td>{{ template "home-cell" (index . 1) }}</td>
+ </tr>
+{{ define "home-cell" }}
+ {{ if .Message }}
+ <p class="lead">{{ .Message }}</p>
+ {{else if .Link }}
+ <a href="{{ .Link.URL }}">{{ .Link.Text }}</a>
+ {{ else }}
+ {{/* Bound queues: 3 named lists */}}
+ {{ if eq (len .Links) 3 }}
+ {{ if index .Links 0 }}
+ <p class="lead">Bound queues</p>
+ {{end }}
+ {{ range index .Links 0 }}
+ <li><a href="{{ .URL }}">{{ .Text }}</a></li>
+ {{ if index .Links 1 }}
+ <p class="lead">Allowed source queues using another DLQ</p>
+ {{ range index .Links 1 }}
+ {{ if index .Links 2 }}
+ <p class="lead">Allowed source queues not declaring a DLQ or with another prefix</p>
+ {{ range index .Links 2 }}
+ {{ range .Links }}
@@ -0,0 +1,147 @@
+{{define "queue-get" -}}
+ <title>File {{ .info.Name }}</title>
+ <li class="breadcrumb-item"><a href="/">Home</a></li>
+ <li class="breadcrumb-item active" aria-current="page">{{ .info.Name }}</li>
+ <h1 class="text-3xl font-bold underline">Queue view for {{ .info.Name }}</h1>
+ <div class="alert alert-secondary alert-dismissible fade show row">
+ <div class="col col-2">SQS info latency</div>
+ <div class="col col-4">{{ .latency.info }}</div>
+ <div class="col col-2">SQS messages latency</div>
+ <div class="col col-4">{{ .latency.items }}</div>
+ {{ $timeFormat := "2006-01-02 03:04:05 -0700" }}
+ <table class="table">
+ {{ $attr := .info.Attributes }}
+ <th scope="row">Info</th>
+ <td>Link: <a href="{{ .info.URL }}">{{ .info.Name }}</a></td>
+ <td>ARN: {{ $attr.QueueARN }}</td>
+ <th scope="row">Times</th>
+ <td>Created: {{ timestamp $attr.CreatedTimestamp }}</td>
+ <td>Last modified: {{ timestamp $attr.LastModifiedTimestamp }}</td>
+ <th scope="row" rowspan="2">Durations</th>
+ <td>Queue delay: {{ $attr.DelaySeconds }} seconds</td>
+ <td>Visibility Timeout: {{ $attr.VisibilityTimeout }} seconds</td>
+ <td>Retention period: {{ $attr.MessageRetentionPeriod }} seconds</td>
+ <td>Receive wait time: {{ $attr.ReceiveMessageWaitTimeSeconds }} seconds</td>
+ <th scope="row">Counts</th>
+ <td>Approx. messages: {{ $attr.ApproximateNumberOfMessages }}</td>
+ <td>Approx. delayed: {{ $attr.ApproximateNumberOfMessagesDelayed }}</td>
+ <th scope="row">Messages</th>
+ <td>Approx. not visible: {{ $attr.ApproximateNumberOfMessagesNotVisible }}</td>
+ <td>Max. size: {{ $attr.MaximumMessageSize }}</td>
+ {{ if $attr.RedrivePolicy }}
+ <th scope="row">DeadLetter queue</th>
+ <td>
+ <a href="/queue/{{ nameFromARN $attr.RedrivePolicy.DeadLetterTargetARN }}">{{ $attr.RedrivePolicy.DeadLetterTargetARN }}</a>
+ </td>
+ <td>Max. receive count: {{ $attr.RedrivePolicy.MaxReceiveCount }}</td>
+ {{ if .isDLQ }}
+ <th scope="row">Source queues for DLQ</th>
+ <td>Redrive Permission: {{ $attr.RedriveAllowPolicy.RedrivePermission }}</td>
+ <td>Queues:
+ {{ range $attr.RedriveAllowPolicy.SourceQueueARNs }}
+ <li><a href="/queue/{{- nameFromARN . -}}">{{ . }}</a></li>
+ <section class="container mt-5">
+ <form method="get" id="msg-form" action="/queue/{{ .info.Name }}/confirm">
+ <div class="d-flex align-items-center justify-content-between">
+ <h2 class="my-5">Messages</h2>
+ <button type="submit" form="msg-form" id="redrive" name="redrive"
+ class="btn btn-outline-warning bg-warning text-white" disabled>
+ Redrive selection
+ </button>
+ <button type="submit" form="msg-form" id="delete" name="delete"
+ class="btn btn-outline-danger bg-danger-subtle text-danger" disabled>
+ Delete selection
+ <button type="submit" form="msg-form" id="purge" name="purge"
+ class="btn btn-outline-danger bg-danger text-white">
+ Purge whole queue
+ <th scope="col" class="d-flex align-items-center">
+ <input id="select-all" class="form-check-input mt-0 me-3" type="checkbox">
+ <span>Select all</span>
+ </th>
+ <th scope="col">ID</th>
+ <th scope="col">Body</th>
+ <th scope="col">Attributes</th>
+ {{ range $i, $message := .items }}
+ {{ $row := dict "message" $message "i" $i }}
+ {{ template "queue-item" $row }}
+ </section>
+ <script src="/assets/script.js"></script>
+{{ define "queue-item" -}}
+ <th scope="row"><input class="form-check-input checkbox" type="checkbox" name="id-{{ .i }}"/>
+ <input type="hidden" name="rh-{{ .i }}" value="{{ .message.ReceiptHandle }}"/></th>
+ <input type="hidden" name="mid-{{ .i }}" value="{{ .message.MessageId }}"/></th>
+ <td>{{ .message.MessageId }}</td>
+ <td>{{ abbrev 80 .message.Body }}</td>
+ <td>{{ toJson .message.MessageAttributes | abbrev 80 }}</td>
@@ -0,0 +1,8 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+prettier@^2.8.4:
+ version "2.8.4"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.4.tgz#34dd2595629bfbb79d344ac4a91ff948694463c3"
+ integrity sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==
@@ -0,0 +1,63 @@
+module code.osinet.fr/fgm/sqs_demo
+go 1.19
+require (
+ github.com/Masterminds/sprig/v3 v3.2.3
+ github.com/aws/aws-sdk-go-v2 v1.17.4
+ github.com/aws/aws-sdk-go-v2/config v1.18.12
+ github.com/aws/aws-sdk-go-v2/service/sqs v1.20.2
+ github.com/davecgh/go-spew v1.1.1
+ github.com/fgm/izidic v0.0.2
+ github.com/gin-contrib/sessions v0.0.5
+ github.com/gin-gonic/gin v1.8.2
+ github.com/google/uuid v1.3.0
+ github.com/gorilla/mux v1.8.0
+ github.com/utrack/gin-csrf v0.0.0-20190424104817-40fb8d2c8fca
+ golang.org/x/exp v0.0.0-20230206171751-46f607a40771
+ gopkg.in/yaml.v3 v3.0.1
+ github.com/Masterminds/goutils v1.1.1 // indirect
+ github.com/Masterminds/semver/v3 v3.2.0 // indirect
+ github.com/aws/aws-sdk-go-v2/credentials v1.13.12 // indirect
+ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22 // indirect
+ github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29 // indirect
+ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sso v1.12.1 // indirect
+ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1 // indirect
+ github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 // indirect
+ github.com/aws/smithy-go v1.13.5 // indirect
+ github.com/dchest/uniuri v1.2.0 // indirect
+ github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.11.2 // indirect
+ github.com/goccy/go-json v0.10.0 // indirect
+ github.com/gorilla/context v1.1.1 // indirect
+ github.com/gorilla/securecookie v1.1.1 // indirect
+ github.com/gorilla/sessions v1.2.1 // indirect
+ github.com/huandu/xstrings v1.3.3 // indirect
+ github.com/imdario/mergo v0.3.11 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/kr/text v0.2.0 // indirect
+ github.com/leodido/go-urn v1.2.1 // indirect
+ github.com/mattn/go-isatty v0.0.17 // indirect
+ github.com/mitchellh/copystructure v1.0.0 // indirect
+ github.com/mitchellh/reflectwalk v1.0.0 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/pelletier/go-toml/v2 v2.0.6 // indirect
+ github.com/shopspring/decimal v1.2.0 // indirect
+ github.com/spf13/cast v1.3.1 // indirect
+ github.com/ugorji/go/codec v1.2.9 // indirect
+ golang.org/x/crypto v0.5.0 // indirect
+ golang.org/x/net v0.5.0 // indirect
+ golang.org/x/sys v0.5.0 // indirect
+ golang.org/x/text v0.6.0 // indirect
+ google.golang.org/protobuf v1.28.1 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
@@ -0,0 +1,202 @@
+github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
+github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
+github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
+github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
+github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
+github.com/aws/aws-sdk-go-v2 v1.17.4 h1:wyC6p9Yfq6V2y98wfDsj6OnNQa4w2BLGCLIxzNhwOGY=
+github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
+github.com/aws/aws-sdk-go-v2/config v1.18.12 h1:fKs/I4wccmfrNRO9rdrbMO1NgLxct6H9rNMiPdBxHWw=
+github.com/aws/aws-sdk-go-v2/config v1.18.12/go.mod h1:J36fOhj1LQBr+O4hJCiT8FwVvieeoSGOtPuvhKlsNu8=
+github.com/aws/aws-sdk-go-v2/credentials v1.13.12 h1:Cb+HhuEnV19zHRaYYVglwvdHGMJWbdsyP4oHhw04xws=
+github.com/aws/aws-sdk-go-v2/credentials v1.13.12/go.mod h1:37HG2MBroXK3jXfxVGtbM2J48ra2+Ltu+tmwr/jO0KA=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22 h1:3aMfcTmoXtTZnaT86QlVaYh+BRMbvrrmZwIQ5jWqCZQ=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.22/go.mod h1:YGSIJyQ6D6FjKMQh16hVFSIUD54L4F7zTGePqYMYYJU=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28 h1:r+XwaCLpIvCKjBIYy/HVZujQS9tsz5ohHG3ZIe0wKoE=
+github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.28/go.mod h1:3lwChorpIM/BhImY/hy+Z6jekmN92cXGPI1QJasVPYY=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22 h1:7AwGYXDdqRQYsluvKFmWoqpcOQJ4bH634SkYf3FNj/A=
+github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.22/go.mod h1:EqK7gVrIGAHyZItrD1D8B0ilgwMD1GiWAmbU4u/JHNk=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29 h1:J4xhFd6zHhdF9jPP0FQJ6WknzBboGMBNjKOv4iTuw4A=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.3.29/go.mod h1:TwuqRBGzxjQJIwH16/fOZodwXt2Zxa9/cwJC5ke4j7s=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22 h1:LjFQf8hFuMO22HkV5VWGLBvmCLBCLPivUAmpdpnp4Vs=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.22/go.mod h1:xt0Au8yPIwYXf/GYPy/vl4K3CgwhfQMYbrH7DlUUIws=
+github.com/aws/aws-sdk-go-v2/service/sqs v1.20.2 h1:CSNIo1jiw7KrkdgZjCOnotu6yuB3IybhKLuSQrTLNfo=
+github.com/aws/aws-sdk-go-v2/service/sqs v1.20.2/go.mod h1:1ttxGjUHZliCQMpPss1sU5+Ph/5NvdMFRzr96bv8gm0=
+github.com/aws/aws-sdk-go-v2/service/sso v1.12.1 h1:lQKN/LNa3qqu2cDOQZybP7oL4nMGGiFqob0jZJaR8/4=
+github.com/aws/aws-sdk-go-v2/service/sso v1.12.1/go.mod h1:IgV8l3sj22nQDd5qcAGY0WenwCzCphqdbFOpfktZPrI=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1 h1:0bLhH6DRAqox+g0LatcjGKjjhU6Eudyys6HB6DJVPj8=
+github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1/go.mod h1:O1YSOg3aekZibh2SngvCRRG+cRHKKlYgxf/JBF/Kr/k=
+github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 h1:s49mSnsBZEXjfGBkRfmK+nPqzT7Lt3+t2SmAKNyHblw=
+github.com/aws/aws-sdk-go-v2/service/sts v1.18.3/go.mod h1:b+psTJn33Q4qGoDaM7ZiOVVG8uVjGI6HaZ8WBHdgDgU=
+github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
+github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
+github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
+github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
+github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
+github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g=
+github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY=
+github.com/fgm/izidic v0.0.2 h1:xpIr9sEVE2xVMlUPu7zcQhumxPB0mjW2/j8Cm598sMw=
+github.com/fgm/izidic v0.0.2/go.mod h1:HSUQlWnf88mpvaovrffBQnjqug3WLjTisLZl5g2RaEc=
+github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
+github.com/gin-contrib/sessions v0.0.0-20190101140330-dc5246754963/go.mod h1:4lkInX8nHSR62NSmhXM3xtPeMSyfiR58NaEz+om1lHM=
+github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
+github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
+github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
+github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY=
+github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398=
+github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
+github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
+github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
+github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
+github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
+github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
+github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
+github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
+github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
+github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
+github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
+github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
+github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
+github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
+github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
+github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
+github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
+github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
+github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
+github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/utrack/gin-csrf v0.0.0-20190424104817-40fb8d2c8fca h1:lpvAjPK+PcxnbcB8H7axIb4fMNwjX9bE4DzwPjGg8aE=
+github.com/utrack/gin-csrf v0.0.0-20190424104817-40fb8d2c8fca/go.mod h1:XXKxNbpoLihvvT7orUZbs/iZayg1n4ip7iJakJPAwA8=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
+golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
+golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg=
+golang.org/x/exp v0.0.0-20230206171751-46f607a40771/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
+golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
+golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
+golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
+gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
+gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=